力扣Hot100

力扣Hot100

1.两数之和

for

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var twoSum = function(nums, target) {
const map = new Map();// 创建一个哈希表用于存储已遍历的数字及其索引

for (let i = 0; i < nums.length; i++) {
const now = nums[i];
const oth = target - now;// 目标值的补数

if (map.has(oth)) {// 检查哈希表
return [map.get(oth), i];//返回当前索引和补数的索引
}

map.set(now, i);// 将当前数字及其索引存入哈希表
}
};

forEach

1
>forEach((element, index, array) => { /* … */ })
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var twoSum = function(nums, target) {
const map = new Map();
let res;

nums.forEach((now, i) => {
const oth = target - now;

if (map.has(oth)) {
res = [map.get(oth), i];
}

map.set(now, i);
});

if (res) { return result; }
};

3.无重复字符最长字串

const map = new Set()

双指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var lengthOfLongestSubstring = function(s) {
let ans = 0
let l = 0
const map = new Set()

for(let r=0; r<s.length; r++){
let c=s[r]
while(map.has(c)){
map.delete(s[l])
l++
}
map.add(c)
if(ans<r-l+1){
ans=r-l+1
}
}
return ans
};

17.电话号码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var letterCombinations = function(digits) {
if (digits.length == 0) return [];
const res = [];
const map = { '2': 'abc', '3': 'def', '4': 'ghi', '5': 'jkl', '6': 'mno', '7': 'pqrs', '8': 'tuv', '9': 'wxyz' };

const dfs = (now, i) => { // now是当前字符串,i是扫描的指针
if (i > digits.length - 1) { // 指针越界,递归出口
res.push(now); // 将解推入res
return; // 结束当前递归分支
}
const strs = map[digits[i]]; // 当前数字对应的字母
for (const str of strs) { // 选中str
dfs(now + str, i + 1); // now + str 合并字符串并传递
}
};
dfs('', 0); // 递归的入口,初始字符串为'',从按的第一个数开始
return res;
};

20.括号栈

入栈右括号

([)]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var isValid = function(s) {
if (s.length % 2) { // s 长度必须是偶数
return false;
}
const map = {'(': ')', '[': ']', '{': '}'};
const strs = [];
for (const str of s) {
if (map[str]) { // str 是左括号
strs.push(map[str]); // 入栈右括号
} else if (strs.length === 0 || strs.pop() !== str) { // str 是右括号
return false; // 没有左括号,或者左括号类型不对
}
}
return strs.length === 0; // 所有左括号必须匹配完毕
};

22.括号生成

dfs (填入括号数左括号数)

注意填入右括号的判断逻辑

join() 将一个数组的所有元素 连接成一个字符串

1
2
join() 
join(符号)
1
2
3
4
5
6
7
8
9
10
const array = ["A", "B", "C"];

console.log(array.join()); //逗号分
// "A,B,C"

console.log(array.join(""));//不分
// "ABC"

console.log(array.join("-"));//-分
// "A-B-C"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var generateParenthesis = function(n) {
const res = []
const max = 2*n
const path = Array(max)

const dfs = ((sum,l)=>{
if(sum===max){ //填满了
res.push(path.join(""))
return
}
if(l<n){ //填'('
path[sum]='('
dfs(sum+1,l+1)
}
if(sum-l<l){ //填')'
path[sum]=')'
dfs(sum+1,l)
}
})

dfs(0,0)

return res
}

46.全排列

array.slice (start, end)

若省略则为 0-end

JavaScript 中数组是引用类型

.slics 是一个 浅拷贝 操作

创建一个新的数组副本,并将这个副本加入到结果

本题如果写 res.push(path):是将 path引用(而不是副本)push 到 res 中

  1. 初始状态:path = [], res = []

  2. 第一次递归:

    • 选择 1,更新 path = [1]
  3. 第二次递归:

    • 选择 2,更新 path = [1, 2]

    • 当前路径长度等于数组长度,执行

      1
      res.push(path)
      • 此时,res = [[1, 2]],但 res[0] 是对 path 的引用。
  4. 回溯:

    • 撤销选择 2,更新 path = [1]
    • 撤销选择 1,更新 path = []
  5. 最终状态:

    • path = [],而 res 中存储的是对 path 的引用。
    • 因此,res = [[], []]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const permute = (nums) => {
const res = [];
const isused = {};

const dfs = (path) => {
if (path.length == nums.length) { // 选够了
res.push(path.slice()); // 拷贝一份path,加入解集res
return; // 结束当前递归分支
}
for (const num of nums) { // for枚举出每个可选的选项
// if (path.includes(num)) continue; // 不行!查找是O(n)
if (isused[num]) continue; // 使用过的,跳过
path.push(num); // 选择当前的数,加入path
isused[num] = true; // 记录一下 使用了
dfs(path); // 基于选了当前的数,递归
path.pop(); // 上一句的递归结束,回溯,将最后选的数pop出来
isused[num] = false; // 撤销这个记录
}
}

dfs([]); // 递归的入口,空path传进去
return res;
};

70.爬楼梯

1
2
3
4
5
var climbStairs = function(n) {
const sqrt_5 = Math.sqrt(5);
const fib_n = Math.pow((1 + sqrt_5) / 2, n + 1) - Math.pow((1 - sqrt_5) / 2,n + 1);
return Math.round(fib_n / sqrt_5);
};

78.子集

遍历树

dfs里的两种状态

不选 / 选

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var subsets = function(nums) {
const n = nums.length;
const ans = []
const path = []
function dfs(i) {
if (i === n) { // 子集构造完毕
ans.push(path.slice()); // 复制 path
return;
}

// 不选 nums[i]
dfs(i + 1);

// 选 nums[i]
path.push(nums[i]);
dfs(i + 1);
path.pop(); // 恢复现场
}
dfs(0);
return ans;
};

94.二叉树中序遍历

先递归左子树,再访问根节点,接着递归右子树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var inorderTraversal = function (root) {
const ans = [];

const dfs = root => {
if (!root) {
return;
}
dfs(root.left);
ans.push(root.val);
dfs(root.right);
};

dfs(root);
return ans;
};

101.对称二叉树

1
2
3
4
5
6
7
8
9
var isSymmetric = function(root) {
function isSameTree(p, q) {
if (p === null || q === null) {
return p === q;
}
return p.val === q.val && isSameTree(p.left, q.right) && isSameTree(p.right, q.left);
}
return isSameTree(root.left, root.right);
};

104.二叉树最大深度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var maxDepth = function(root) {
let ans = 0;

const dfs = ((root,deep) => {
if (deep>ans)ans=deep
if (!root) {
return;
}
dfs(root.left,deep+1);
dfs(root.right,deep+1);
});

dfs(root, 0);
return ans;
};

136.只出现一次的数

异或 ^

a⊕0 = a

a⊕a = 0

满足交换律和结合律:a⊕b⊕a = (a⊕a)⊕b = 0⊕b = b

1
2
3
4
5
6
7
var singleNumber = function(nums) {
let ans = 0
nums.forEach((num) => {
ans ^= num
})
return ans
};

148.排序链表(?)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 876. 链表的中间结点(快慢指针)
function middleNode(head) {
let pre = head, slow = head, fast = head;
while (fast && fast.next) {
pre = slow; // 记录 slow 的前一个节点
slow = slow.next;
fast = fast.next.next;
}
pre.next = null; // 断开 slow 的前一个节点和 slow 的连接
return slow;
}

// 21. 合并两个有序链表(双指针)
function mergeTwoLists(list1, list2) {
const dummy = new ListNode(); // 用哨兵节点简化代码逻辑
let cur = dummy; // cur 指向新链表的末尾
while (list1 && list2) {
if (list1.val < list2.val) {
cur.next = list1; // 把 list1 加到新链表中
list1 = list1.next;
} else { // 注:相等的情况加哪个节点都是可以的
cur.next = list2; // 把 list2 加到新链表中
list2 = list2.next;
}
cur = cur.next;
}
cur.next = list1 ?? list2; // 拼接剩余链表
return dummy.next;
}

var sortList = function(head) {
// 如果链表为空或者只有一个节点,无需排序
if (head === null || head.next === null) {
return head;
}
// 找到中间节点,并断开 head2 与其前一个节点的连接
// 比如 head=[4,2,1,3],那么 middleNode 调用结束后 head=[4,2] head2=[1,3]
let head2 = middleNode(head);
// 分治
head = sortList(head);
head2 = sortList(head2);
// 合并
return mergeTwoLists(head, head2);
};

153.旋转排序最小值

1

160.相交链表

初始化两个指针 p=headA, q=headB。不断循环,直到 p=q。

每次循环,p 和 q 各向后走一步。

如果 p 不是空节点,那么更新 p 为 p.next,否则更新 p 为 headB;

如果 q 不是空节点,那么更新 q 为 q.next,否则更新 q 为 headA。

循环结束时,如果两条链表相交,那么此时 p 和 q 都在相交的起始节点处,返回 p;

如果两条链表不相交,那么 p 和 q 都走到空节点,所以也可以返回 p,即空节点。

1
2
3
4
5
6
7
8
var getIntersectionNode = function(headA, headB) {
let p = headA, q = headB;
while (p !== q) {
p = p ? p.next : headB;
q = q ? q.next : headA;
}
return p;
};

226.翻转二叉树

1
2
3
4
5
6
7
8
9
10
var invertTree = function(root) {
if (root === null) {
return null;
}
const left = invertTree(root.left); // 翻转左子树
const right = invertTree(root.right); // 翻转右子树
root.left = right; // 交换左右儿子
root.right = left;
return root;
};

283.移动零

快慢双指针

解构赋值交换变量值 [a, b] = [b, a];

快指针位非0 => 交换

1
2
3
4
5
6
7
8
9
var moveZeroes = function(nums) {
let i0 = 0;
for (let i = 0; i < nums.length; i++) {
if (nums[i] !== 0) { //快指针位!=0
[nums[i], nums[i0]] = [nums[i0], nums[i]];
i0++;
}
}
};

543.二叉树直径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var diameterOfBinaryTree = function(root) {
let ans = 1;

function depth(rootNode) {
if (!rootNode) {
return 0;
}

let L = depth(rootNode.left);
let R = depth(rootNode.right);

ans = Math.max(ans, L + R + 1);
return Math.max(L, R) + 1;
}

depth(root);

return ans - 1;
};