1、两数之和
点评:最初的梦想
var twoSum = function (nums, target) {
let map = new Map();
let result = [];
for (let i = 0; i < nums.length; i++) {
let left = target - nums[i];
if (map.has(left)) {
return [map.get(left), i];
}
map.set(nums[i], i);
}
};
49、字母异位词分组
点评:然后梦想就破灭了
var groupAnagrams = function (strs) {
if (strs === null) return [['']];
let map = new Map();
let result = [];
for (let i = 0; i < strs.length; i++) {
let words = strs[i].split('').sort().join('');
if (!map.has(words)) {
map.set(words, [strs[i]]);
} else {
map.get(words).push(strs[i])
}
}
for (let [key, value] of map) {
result.push(value)
}
return result
};
128、最长连续序列
点评:指定一个动态的dp值随时记录
var longestConsecutive = function (nums) {
if(nums.length === 0) return 0;
nums.sort((a, b) => a - b);
let len = 1;
let max = 1;
let dp = nums[0];
for (let i = 1; i < nums.length; i++) {
if (nums[i] === (dp + 1)) {
len++;
dp = nums[i];
} else if (nums[i] === dp) {
continue;
} else {
max = Math.max(max, len);
len = 1;
dp = nums[i];
}
}
max = Math.max(max, len);
return max
};
283、移动零
点评:其实单指针也可以,主要是记录0的索引。
var moveZeroes = function (nums) {
let left;
let flag = true;
for (let i = 0; i < nums.length; i++) {
if (nums[i] === 0 && flag) {
left = i;
flag = false
} else {
if (nums[i] === 0) continue
if (left || left === 0) {
[nums[left], nums[i]] = [nums[i], nums[left]];
left++;
}
}
}
return nums
};
11、盛最多水的容器
点评:双指针yyds,记住维护最小值
var maxArea = function (height) {
let left = 0, right = height.length - 1;
let square = 0;
while (left <= right) {
let [min, max] = [Math.min(height[left], height[right]), Math.max(height[left], height[right])];
square = Math.max(square, min * (right - left));
if (height[left] <= height[right]) {
left++;
} else {
right--
}
}
return square
};
15、三数之和
点评:哈希表能免去一轮for循环,记得去重
var threeSum = function (nums) {
nums.sort((a, b) => a - b);
let result = [];
for (let i = 0; i < nums.length; i++) {
if (nums[i] === nums[i - 1]) continue;
let map = new Map();
let repeat = {};
for (let j = i + 1; j < nums.length; j++) {
let x = 0 - nums[i] - nums[j];
if (map.has(x)) {
let key = [nums[i], nums[j], x].join('');
if (!repeat[key]) {
result.push([nums[i], nums[j], x]);
repeat[key] = (repeat[key] || 0) + 1;
}
} else {
map.set(nums[j]);
}
}
}
return result
};
42、接雨水
点评:据说是某宇宙厂最爱出的题?应用单调栈
const len = height.length;
if(len <= 2) return 0; // 可以不加
const st = [];// 存着下标,计算的时候用下标对应的柱子高度
st.push(0);
let sum = 0;
for(let i = 1; i < len; i++){
if(height[i] < height[st[st.length - 1]]){ // 情况一
st.push(i);
}
if (height[i] == height[st[st.length - 1]]) { // 情况二
st.pop(); // 其实这一句可以不加,效果是一样的,但处理相同的情况的思路却变了。
st.push(i);
} else { // 情况三
while (st.length !== 0 && height[i] > height[st[st.length - 1]]) { // 注意这里是while
let mid = st[st.length - 1];
st.pop();
if (st.length !== 0) {
let h = Math.min(height[st[st.length - 1]], height[i]) - height[mid];
let w = i - st[st.length - 1] - 1; // 注意减一,只求中间宽度
sum += h * w;
}
}
st.push(i);
}
}
return sum;
3、无重复字符的最长子串
点评:滑动窗不解释
var lengthOfLongestSubstring = function (s) {
let ans = 0;
let left = 0;
const window = new Set();
for(let right = 0;right<s.length;right++) {
const c = s[right];
while(window.has(c)) {
window.delete(s[left]);
left++;
}
window.add(c);
ans = Math.max(ans,right-left+1);
}
return ans
};
438、找到字符串中所有字母异位词
点评:更复杂的滑动窗
var findAnagrams = function(s, p) {
let pObj = {};
for(let key of p) {
pObj[key] = (pObj[key] || 0) + 1;
}
let left=0,right=0,valid=0;
let window = {};
let result = [];
while(right<s.length) {
let c = s[right];
right++;
if(pObj[c]) {
window[c] = (window[c] || 0) + 1;
if(window[c] === pObj[c]) {
valid++;
}
}
while(right-left>=p.length){
if(valid === Object.keys(pObj).length) {
result.push(left)
}
let d = s[left];
left++;
if(pObj[d]) {
if(pObj[d] === window[d]) {
valid--
}
} window[d]--
}
}
return result
};
560、和为K的子数组
点评:额外空间换时间,创造前缀和额外数组
var subarraySum = function (nums, k) {
const hashmap = {};
let acc = 0;
let count = 0;
for (let i = 0; i < nums.length; i++) {
acc += nums[i];
if (acc === k) count++;
if (hashmap[acc - k] !== undefined) {
count += hashmap[acc - k];
}
if (hashmap[acc] === undefined) {
hashmap[acc] = 1;
} else {
hashmap[acc] += 1;
}
}
return count;
}
239、滑动窗口最大值
点评:
53、最大子数组和
点评:维护前i项和,如果sum<0,将sum重置为0
var maxSubArray = function (nums) {
let sum = 0;
let max = -Infinity;
for (let i = 0; i < nums.length; i++) {
sum += nums[i];
if(sum > max) {
max= sum;
} if(sum<0) {
sum =0;
continue;
}
}
return max
};
56、合并区间
点评:对二维数组排序,维护左边界和右边界。
var merge = function (intervals) {
intervals.sort((a, b) => {
if (a[0] !== b[0]) {
return a[0] - b[0]
} else {
return b[1] - a[1]
}
})//[[1,3],[2,6],[8,10],[15,18]]
let left = intervals[0][0];
let right = intervals[0][1];
let result = [];
for (let i = 1; i < intervals.length; i++) {
if (intervals[i][0] <= right && intervals[i][0] >= left) {
if (intervals[i][1] > right) {
right = intervals[i][1];
}
} else {
result.push([left, right]);
left = intervals[i][0];
right = intervals[i][1];
}
}
result.push([left, right]);
return result
};
189、轮转数组
点评:注意不允许有返回值,直接在原数组上修改。三部曲:先反转原数组,再翻转0 - k-1;最后翻转k - n-1
var rotate = function (nums, k) {
function reverse(i,j) {
while(i<j) {
[nums[i],nums[j]] = [nums[j],nums[i]];
i++;
j--;
}
}
const n = nums.length;
k %= n;
reverse(0,n-1);
reverse(0,k-1);
reverse(k,n-1);
};
238、除自身以外数组的乘积
点评:前遍历一次,后遍历一次,最后两个结果相乘就是结果
var productExceptSelf = function (nums) {
let ans = [1]
//前项积
for (let i = 1; i < nums.length; i++) {
ans[i] = ans[i - 1] * nums[i - 1]
}
// 当前项等于前项积乘以后项积
let end = 1
for (let j = nums.length - 1; j >= 0; j--) {
ans[j] *= end
end *= nums[j]
}
return ans
};
73、矩阵置零
点评:用两个额外的数组空间row和col,一个大小为n,一个为m,分别记录矩阵为0的行和列,共计遍历两次原矩阵。
var setZeroes = function(matrix) {
let m = matrix.length;
let n = matrix[0].length;
let row = new Array(m).fill(0);
let col = new Array(n).fill(0);
for(let i = 0;i<m;i++) {
for(let j = 0;j<n;j++) {
if(matrix[i][j] === 0) {
row[i] = col[j] = 1;
}
}
}
for(let i =0;i<m;i++) {
for(let j = 0;j<n;j++) {
if(row[i] === 1 || col[j] === 1) {
matrix[i][j] = 0;
}
}
}
return matrix
};
54、螺旋矩阵
点评:四个边每个边遍历一次,再向内收缩
var spiralOrder = function(matrix) {
let left = 0;
let right = matrix[0].length-1;
let top = 0;
let bottom = matrix.length-1;
let res = [];
while(true){
for(let i = left;i<=right;i++){
res.push(matrix[top][i]);
}
top++;
if(top>bottom) break;
for(let i = top;i<=bottom;i++){
res.push(matrix[i][right]);
}
right--;
if(right<left) break;
for(let i = right;i>=left;i--){
res.push(matrix[bottom][i]);
}
bottom--;
if(bottom<top) break;
for(let i = bottom;i>=top;i--){
res.push(matrix[i][left]);
}
left++;
if(left>right) break;
}
return res;
};
48、旋转图像
点评:沿着水平对称轴翻转,再沿对角线翻转
var rotate = function(matrix) {
let n = matrix.length;
let start = Math.floor(n / 2);
for (let i = 0; i < start; i++) {
for (let j = 0; j < n; j++) {
[matrix[i][j], matrix[n - i - 1][j]] = [matrix[n - i - 1][j], matrix[i][j]];
}
}
for (let i = 0; i < n; i++) {
for (let j = 0; j < i; j++) {
[matrix[i][j], matrix[j][i]] = [matrix[j][i], matrix[i][j]];
}
}
return matrix
};
240、搜索二维矩阵II
点评:从右上角开始遍历,target比cur大,就向下,比cur小,就向左。
var searchMatrix = function(matrix, target) {
if(matrix.length==0) return false // 判空
let [left, up]=[matrix[0].length-1, 0]; // 初始化位置
while(left>=0 && up<matrix.length){
if(matrix[up][left]>target){
left--;
}else if(matrix[up][left]<target){
up++;
}else{
return true;
}
}
return false;
};
160、相交链表
点评:两个步幅相等的指针。相交链表总有相等的子节点
var getIntersectionNode = function(headA, headB) {
if(headA === null || headB === null) return null;
let pA = headA, pB = headB;
while(pA!==pB) {
pA = pA === null?headB:pA.next;
pB = pB === null ?headA:pB.next;
}
return pA;
};
206、反转链表
点评:先保存住当前节点的下一个节点。让cur指向pre, 将pre的值赋为cur,然后再移动cur。
var reverseList = function (head) {
let pre = null,
tmp = null,
cur = head;
if (!head || !head.next)
return head;
while (cur) {
tmp = cur.next;
cur.next = pre;
pre = cur;
cur = tmp;
}
return pre
};
234、回文链表
点评:存成数组用双指针判断
var isPalindrome = function(head) {
let arr = [];
while(head){
arr.push(head.val);
head = head.next;
}
let start = 0,end = arr.length -1;
while(start<end){
if(arr[start] !== arr[end]){
return false
}
start++;
end--
}
return true;
};
141、环形链表
点评:快慢指针,有环必相交
var hasCycle = function(head) {
let fast = head;
let slow = head;
while(fast) {
if(fast.next == null) return false;
slow = slow.next;
fast = fast.next.next;
if(slow === fast) return true;
}
return false
};
142、环形链表II
点评:快慢指针,相遇时只是证明有环。需要把slow指针重新放在头节点,再次相遇证明相交点。
var detectCycle = function (head) {
if (!head || !head.next) return null;
let slow = head.next, fast = head.next.next;
while (fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
if (fast === slow) {
slow = head;
while (slow != fast) {
slow = slow.next;
fast = fast.next;
}
return slow;
}
}
return null;
};
21、合并两个有序链表
点评:
var mergeTwoLists = function(list1, list2) {
if(list1 === null) {
return list2;
}
if(list2 === null) {
return list1
}
if(list1.val < list2.val) {
list1.next = mergeTwoLists(list1.next,list2);
return list1;
} else {
list2.next = mergeTwoLists(list1,list2.next);
return list2
}
};
2、两数相加
点评:
var addTwoNumbers = function(l1, l2) {
let addOne = 0
let sum = new ListNode('0')
let head = sum
while (addOne || l1 || l2) {
let val1 = l1 !== null ? l1.val : 0
let val2 = l2 !== null ? l2.val : 0
let r1 = val1 + val2 + addOne
addOne = r1 >= 10 ? 1 : 0
sum.next = new ListNode(r1 % 10)
sum = sum.next
if (l1) l1 = l1.next
if (l2) l2 = l2.next
}
return head.next
};
19、删除链表的倒数第N个节点
点评:找两指针的差,让slow指针的next指向next.next,最后返回ret.next即可
var removeNthFromEnd = function(head, n) {
const ret = new ListNode(0,head);
let fast = ret, slow = ret;
while(n--) {
fast = fast.next;
}
while(fast.next != null) {
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return ret.next;
};
24、两两交换链表中的节点
点评:利用虚拟头节点
var swapPairs = function (head) {
let ret = new ListNode(0, head), temp = ret;
while (temp.next && temp.next.next) {
let cur = temp.next.next, pre = temp.next;
pre.next = cur.next;
cur.next = pre;
temp.next = cur;
temp = pre;
}
return ret.next;
};
138、随机链表的复制
点评: 第一次遍历生成具有val属性的链表,第二次遍历根据map映射关系,将random和next指针指向对应节点或者null;
var copyRandomList = function(head) {
let cloneTarget = {};
let cur = head;
const map = new Map();
while(cur){
cur.set(cur,new Node(cur.val));
cur = cur.next;
}
cur = head;
while(cur) {
map.get(cur).next = map.get(cur.next) || null;
map.get(cur).random = map.get(cur.random) || null;
cur = cur.next
}
return map.get(head);
};
148、排序链表
点评: 转成数组再排序再生成链表
var sortList = function(head) {
let arr = [];
while(head) {
arr.push(head.val)
head = head.next;
}
arr.sort((a,b) => a-b);
let dummpy = new ListNode(0);
let prev = dummpy;
for(let i = 0;i<arr.length;i++) {
prev.next = new ListNode(arr[i]);
prev = prev.next
}
return dummpy.next
};
146、LRU缓存
点评:利用map的数据结构。三步:1、对LRU类添加limit,和以map为结构的cache变量。
2、在get方法中,首先检查cache中有没有key,如果有,调用get方法返回值,并且要把key-value删除重新添加进cache。这一步的作用主要是为了解决太久没用需要删除这一功能。如果没有,就直接返回-1 3、对于put方法,首先检查cache中有没有key,如果有也是删了重set。如果没有,检查size是否超过limit,,超过了就删除最早的键值对。
var LRUCache = function(capacity) {
this.limit = capacity;
this.cache = new Map();
};
LRUCache.prototype.get = function(key) {
let tmp;
if(this.cache.has(key)){
tmp = this.cache.get(key);
this.cache.delete(key);
this.cache.set(key,tmp)
}
return tmp ?? -1;
};
LRUCache.prototype.put = function(key, value) {
if(this.cache.has(key)) {
this.cache.delete(key);
}
this.cache.set(key,value);
if(this.cache.size > this.limit) {
this.cache.delete(this.cache.keys().next().value);
}
};
94、二叉树的中序遍历
点评:左中右
var inorderTraversal = function(root) {
const res = [];
inorder(root,res)
return res
};
const inorder = (root,res) => {
if(root === null) return;
inorder(root.left,res);
res.push(root.val);
inorder(root.right,res);
}
104、二叉树的最大深度
点评:层序遍历,遍历完一层count++
var maxDepth = function(root) {
let queue = [root];
let count = 0;
while(queue.length && root !== null) {
let length = queue.length;
while(length--){
let node = queue.shift();
node.left && queue.push(node.left);
node.right && queue.push(node.right);
}
count++;
}
return count;
};
226、翻转二叉树
点评: 层序遍历 + 交换左右值
var invertTree = function(root) {
let queue = [root];
const exchange = (root,left,right) => {
[left,right] = [right,left];
root.left = left;
root.right = right;
}
while(queue.length && root !== null) {
let n = queue.length;
for(let i = 0; i<n; i++){
let node = queue.shift();
exchange(node,node.left,node.right);
node.left && queue.push(node.left);
node.right && queue.push(node.right);
}
}
return root;
};
101、对称二叉树
点评:left.left和right.right比较,left.right和right.left比较
var isSymmetric = function(root) {
var compare = function(left,right){
if(left !== null &&right === null || left === null && right!==null){
return false;
} else if (left === null && right === null) {
return true;
} else if (left.val !== right.val) {
return false;
}
let incompare = compare(left.left,right.right);
let outcompare = compare(left.right,right.left);
return incompare && outcompare;
}
return compare(root.left,root.right)
};
543、二叉树的直径
点评:
var diameterOfBinaryTree = function (root) {
let ans = 1;
function depth(node){
if(!node) return 0
let L = depth(node.left);
let R = depth(node.right);
ans = Math.max(ans,L+R+1)
return Math.max(L,R) + 1
}
depth(root)
return ans - 1
};
102、二叉树的层序遍历
点评:
var levelOrder = function(root) {
let res = [],queue = [];
queue.push(root);
if(root === null) {
return res;
}
while(queue.length !== 0) {
let length = queue.length;
let curLevel = [];
for(i = 0;i<length;i++){
let node = queue.shift();
curLevel.push(node.val);
node.left && queue.push(node.left);
node.right && queue.push(node.right);
}
res.push(curLevel);
}
return res
};
108、将有序数组转化为二叉搜索树
点评: 从中间索引劈开,左边为左子树,右边为右子树,直接递归
var sortedArrayToBST = function (nums) {
const dfs = (nums) => {
if(nums.length === 0) return null;
let mid = Math.floor(nums.length/2);
const root = new TreeNode(nums[mid]);
root.left = dfs(nums.slice(0,mid));
root.right = dfs(nums.slice(mid+1));
return root
}
return dfs(nums)
};
98、验证二叉搜索树
点评:中序遍历得到的结果应正好为递增数组
var isValidBST = function(root) {
let arr = [];
var addNumber = function(root) {
if(root) {
addNumber(root.left);
arr.push(root.val);
addNumber(root.right);
}
}
addNumber(root);
for(let i = 0; i<arr.length; i++) {
if(arr[i] >= arr[i+1]) {
return false;
}
}
return true;
};
230、二叉搜索树中第K小的元素
点评:还是中序遍历
var kthSmallest = function(root, k) {
let nums=[];
function dfs(root){
if(!root) return;
dfs(root.left);
nums.push(root.val);
dfs(root.right);
}
dfs(root);
return nums[k-1];
};
199、二叉树的右视图
点评: 层序遍历取每一层最后一个元素
var rightSideView = function(root) {
let res = [], queue=[];
queue.push(root);
while(queue.length && root!==null) {
let length = queue.length;
while(length--) {
let node = queue.shift()
node.left && queue.push(node.left)
node.right && queue.push(node.right)
if(!length) {
res.push(node.val)
}
}
}
return res
};
114、二叉树展开为链表
点评: 展开的链表节点顺序为二叉树前序遍历的结果 / 也可以将最左边的左子树接到右边,再将原右子树接到末尾。
var flatten = function (root) {
/*
函数的定义:给 flatten 函数输入一个节点 root,那么以 root 为根的二叉树就会被拉平为一条链表。
*/
// base case
if (root == null) return;
flatten(root.left);
flatten(root.right);
// 1、左右子树已经被拉平成一条链表
// 先用两个变量把原先的左右子树保存起来
let left = root.left;
let right = root.right;
// 2、将左子树作为右子树
root.left = null;
root.right = left;
// 3、将原先的右子树接到当前右子树的末端
while (root.right != null) {
root = root.right;
}
root.right = right;
};
105、从前序和中序构造二叉树
点评:分割
var buildTree = function(preorder, inorder) {
if(preorder === -1 && inorder === -1) {
return -1;
}
if(!preorder.length) {
return null
}
let head = preorder.shift();
let headIndex = inorder.indexOf(head);
const root = new TreeNode(head);
root.left = buildTree(preorder.slice(0,headIndex),inorder.slice(0,headIndex));
root.right = buildTree(preorder.slice(headIndex),inorder.slice(headIndex+1));
return root;
};
437、路径总和III
点评:前缀和的思想,利用哈希表
var pathSum = function (root, targetSum) {
let count = 0;
const prefixSum = new Map();
const dfs = (node, currSum) => {
if (node === null) return;
currSum += node.val;
if (currSum === targetSum) {
count++;
}
count += prefixSum.get(currSum - targetSum) || 0;
prefixSum.set(currSum, (prefixSum.get(currSum) || 0) + 1);
dfs(node.left, currSum);
dfs(node.right, currSum);
// 回溯:移除当前路径的和
prefixSum.set(currSum, prefixSum.get(currSum) - 1);
};
dfs(root, 0);
return count;
};
236、二叉树的最近公共祖先
点评: 妙啊我只能说。如果根节点是pq中的一个,直接返回根节点。如若不然,先看左子树,再看右子树。如果不是根节点,则肯定在一边。看看left和right哪个是null,就返回另一个。都不是null,直接返回根节点
var lowestCommonAncestor = function(root, p, q) {
if(root === null || root === p || root === q){
return root;
};
let left = lowestCommonAncestor(root.left,p,q);
let right = lowestCommonAncestor(root.right,p,q);
if(left === null) {
return right;
}
if(right === null) {
return left;
}
return root;
};
200、岛屿数量
点评:图论第一题,梦想开始的地方 dfs和bfs都可
const numIslands = (grid) => {
let count = 0
for (let i = 0; i < grid.length; i++) {
for (let j = 0; j < grid[0].length; j++) {
if (grid[i][j] === '1') {
count++
turnZero(i, j, grid)
}
}
}
return count
}
function turnZero(i, j, grid) {
if (i < 0 || i >= grid.length || j < 0
|| j >= grid[0].length || grid[i][j] === '0') return
grid[i][j] = '0'
turnZero(i, j + 1, grid)
turnZero(i, j - 1, grid)
turnZero(i + 1, j, grid)
turnZero(i - 1, j, grid)
}
994、烂橘子
点评: 先遍历一遍grid,统计新鲜橘子个数和初始的烂橘子。创建队列放初始的这些烂橘子。然后以队列的长度为判断条件,设置初始值为-1,对于每个烂橘子四个方向都让他变烂,再吧新的烂的放队列里,直到队列为空。
var orangesRotting = function(grid) {
const dir = [[1,0],[-1,0],[0,1],[0,-1]];
let n = grid.length;
let m = grid[0].length;
let fresh = 0;
let q = [];
for(let i = 0;i<n;i++){
for(let j = 0;j<m;j++){
if(grid[i][j] === 1){
fresh++;
} else if(grid[i][j] === 2) {
q.push([i,j]);
}
}
}
let ans = -1;
while(q.length) {
ans++;
const tmp = q;
q = [];
for(const [x,y] of tmp){
for(const [i,j] of [[x-1,y],[x+1,y],[x,y-1],[x,y+1]]) {
if(0<=i && i<m && 0<=j && j<n && grid[i][j] === 1){
fresh--;
grid[i][j] = 2;
q.push([i,j]);
}
}
}
}
return fresh ? -1 : Math.max(ans,0)
};
207、课程表
点评:拓扑排序
var canFinish = function(numCourses, prerequisites) {
let n = prerequisites.length;
if(n<=1) return true;
let indegrees = new Array(numCourses).fill(0);
let map = new Map();
for(let i = 0;i<n;i++){
let [x,y] = prerequisites[i];
indegrees[y]++;
if(!map.has(x)) {
map.set(x,[y]);
}else {
map.get(x).push(y);
}
}
const queue = [];
for(let i = 0;i<indegrees.length;i++){
if(indegrees[i] === 0) queue.push(i)
}
let count = 0;
while(queue.length) {
const selected = queue.shift();
count++;
const back = map.get(selected);
if(back && back.length) {
for(let i = 0;i<back.length;i++){
indegrees[back[i]]--;
if(indegrees[back[i]] === 0) {
queue.push(back[i])
}
}
}
}
return count === numCourses
};
208、前缀树
点评:
function Node(val, isEnd){
this.val = val
this.child = {}
this.isEnd = isEnd|| false
}
var Trie = function() {
this.root = new Node()
};
Trie.prototype.insert = function(word) {
let cur = this.root
for (let c of word){
if (cur.child[c] == null) cur.child[c] = new Node(c)
cur = cur.child[c]
}
cur.isEnd = true;
};
Trie.prototype.search = function(word) {
let cur = this.root
for (let c of word){
if (cur.child[c] == null) return false
cur = cur.child[c]
}
return cur.isEnd
};
Trie.prototype.startsWith = function(prefix) {
let cur = this.root
for (let c of prefix){
if (cur.child[c] == null) return false
cur = cur.child[c]
}
return true
};
46、全排列
点评:回溯经典
var permute = function(nums) {
let path = [],res = [];
const backtracking = function(arr) {
if(path.length === nums.length) {
res.push(Array.from(path));
return;
}
for(let i = 0;i< nums.length; i++) {
if(arr[i]) {
continue;
}
path.push(nums[i]);
arr[i] = 1;
backtracking(arr);
path.pop();
arr[i] = 0;
}
}
backtracking([]);
return res
};
78、子集
点评:
var subsets = function(nums) {
const n = nums.length;
const ans = [];
const path = [];
function dfs(i) {
if(i === n) {
ans.push(path.slice());
return
}
dfs(i+1);
path.push(nums[i]);
dfs(i+1);
path.pop()
}
dfs(0);
return ans;
};
39、组合总和
点评:
var combinationSum = function (candidates, target) {
let count = 0;
let path = [];
let result = [];
const backtracking = function (index, sum) {
for (let i = index; i < candidates.length; i++) {
if (sum === target) {
result.push(Array.from(path));
count++;
return
}
if (!(target / candidates[i])) {
count++;
result.push([candidates[i]]);
continue;
}
if (sum > target) {
return;
}
sum += candidates[i];
path.push(candidates[i]);
backtracking(i, sum);
sum -= candidates[i];
path.pop(candidates[i]);
}
}
backtracking(0, 0);
return result;
};
22、括号生成
点评:
var generateParenthesis = function(n) {
const m = n * 2;
const ans = [];
const path = Array(m);
// i=目前填了多少个括号
// open=左括号个数,i-open=右括号个数
function dfs(i, open) {
if (i === n * 2) {
ans.push(path.join(""));
return;
}
if (open < n) { // 可以填左括号
path[i] = '(';
dfs(i + 1, open + 1);
}
if (i - open < open) { // 可以填右括号
path[i] = ')';
dfs(i + 1, open);
}
}
dfs(0, 0);
return ans;
};
79、单词搜索
点评:dfs+回溯
var exist = function (board, word) {
//越界处理
board[-1] = []; // 这里处理比较比较巧妙,利用了js的特性
board.push([])
//寻找首个字母
for (let y = 0; y < board.length; y++) {
for (let x = 0; x < board[0].length; x++) {
if (word[0] === board[y][x] && dfs(word,board,y, x, 0)) return true
}
}
return false
};
const dfs = function(word,board,y, x, i){
if (i + 1 === word.length) return true
var tmp = board[y][x];
// 标记该元素已使用
board[y][x] = false
if (board[y][x + 1] === word[i + 1] && dfs(word,board,y, x + 1, i + 1)) return true
if (board[y][x - 1] === word[i + 1] && dfs(word,board,y, x - 1, i + 1)) return true
if (board[y + 1][x] === word[i + 1] && dfs(word,board,y + 1, x, i + 1)) return true
if (board[y - 1][x] === word[i + 1] && dfs(word,board,y - 1, x, i + 1)) return true
// 回溯
board[y][x] = tmp
}
131、分割回文串
点评: 和子集那道题类似,只不过增加一个回文串的判断。
var isPalindrome = function(s, left, right) {
while (left < right) {
if (s.charAt(left++) !== s.charAt(right--)) {
return false;
}
}
return true;
}
var partition = function(s) {
const n = s.length;
const ans = [];
const path = [];
function dfs(i) {
if (i === n) {
ans.push(path.slice()); // 复制 path
return;
}
for (let j = i; j < n; j++) { // 枚举子串的结束位置
if (isPalindrome(s, i, j)) {
path.push(s.substring(i, j + 1));
dfs(j + 1);
path.pop(); // 恢复现场
}
}
}
dfs(0);
return ans;
};
35、搜索插入位置
点评:二分法
var searchInsert = function(nums, target) {
let left = 0, right = nums.length - 1, ans = nums.length;
while (left <= right) {
let mid = Math.floor((right - left) / 2) + left;
const num = nums[mid];
if (target === num){
return mid;
} else {
if (target > num) {
left = mid + 1;
} else {
ans = mid;
right = mid - 1;
}
}
}
return ans;
};
74、搜索二维矩阵
点评:可以避开二分法,直接从右上角搜索
var searchMatrix = function(matrix, target) {
let n = matrix.length;
let m = matrix[0].length;
let [i,j] = [0,m-1];
while(i<n && j>=0) {
if(target < matrix[i][j]) {
j--;
}else if(target === matrix[i][j]) {
return true;
}else if(target > matrix[i][j]) {
i++;
}
}
return false
};
34、在排序数组中查找元素的第一个位置和最后一个位置
点评: 二分,遍历两次
var searchRange = function(nums, target) {
let left = 0,
right = nums.length - 1,
first = -1,
last = -1;
while (left <= right) {
let mid = Math.floor((right - left) / 2) + left;
if (target === nums[mid]) {
first = mid;
right = mid - 1;
} else if (target > nums[mid]) {
left = mid + 1;
} else {
right = mid - 1;
}
}
left = 0;
right = nums.length - 1;
while (left <= right){
let mid = Math.floor((right - left) / 2) + left;
if (target === nums[mid]) {
last = mid;
left = mid + 1;
} else if (target > nums[mid]) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return [first ,last];
};
33、搜索旋转排序数组
点评:二分法,判断mid和left和right值
var search = function (nums, target) {
let start = 0;
let end = nums.length - 1;
while (start <= end) {
// >> 1 相当于除以2向下取整
let mid = (start + end) >> 1;
if (nums[mid] === target) {
return mid;
}
// 如果中间数小于最右边数,则右半段是有序的
// 如果中间数大于最右边数,则左半段是有序的
if (nums[mid] < nums[end]) {
// 判断target是否在(mid, end]之间
if (nums[mid] < target && target <= nums[end]) {
// 如果在,则中间数右移即start增大
start = mid + 1;
} else {
// 如果不在,则中间数左移即end减小
end = mid - 1;
}
} else {
// [start, mid)
if (nums[start] <= target && target < nums[mid]) {
end = mid - 1;
} else {
start = mid + 1;
}
}
}
return -1;
};
153、寻找排序数组中的最小值
点评:
var findMin = function(nums) {
var left = 0;
var right = nums.length - 1;
while (left < right) {
var mid = (left + right) >> 1;
if (nums[mid] > nums[right]) {
left = mid + 1;
} else {
right = mid;
}
}
return nums[left];
};
17、电话号码随机组合
点评:创建对象
var letterCombinations = function (digits) {
let obj = {
'2': ['a', 'b', 'c'],
'3': ['d', 'e', 'f'],
'4': ['g', 'h', 'i'],
'5': ['j', 'k', 'l'],
'6': ['m', 'n', 'o'],
'7': ['p', 'q', 'r', 's'],
'8': ['t', 'u', 'v'],
'9': ['w', 'x', 'y', 'z']
};
let res = [];
let path = [];
if (digits.length === 0) return res;
let arrNum = [];
for (let i = 0; i < digits.length; i++) {
arrNum.push(obj[digits[i]]);
}
const backtracking = (index) => {
if (path.length === digits.length) {
res.push(path.join(''));
return;
}
for (let j = 0; j < arrNum[index].length; j++) {
path.push(arrNum[index][j]);
backtracking(index + 1);
path.pop();
}
};
backtracking(0);
return res;
};
20、有效括号
点评: 栈
var isValid = function(s) {
if (s.length % 2) { // s 长度必须是偶数
return false;
}
const mp = {')': '(', ']': '[', '}': '{'};
const st = [];
for (const c of s) {
if (!mp.hasOwnProperty(c)) { // c 是左括号
st.push(c); // 入栈
} else if (st.length === 0 || st.pop() !== mp[c]) { // c 是右括号
return false; // 没有左括号,或者左括号类型不对
}
}
return st.length === 0; // 所有左括号必须匹配完毕
};
155、最小栈
点评:
var MinStack = function () {
// 主栈指针
this.head = null
// 辅助栈指针
this.subHead = null
};
/**
* @param {number} x
* @return {void}
*/
MinStack.prototype.push = function (x) {
const newItem = {
data: x,
next: null
}
const subNewItem = {
data: x,
next: null
}
// 主栈判断逻辑
if (!this.head) {
this.head = newItem
} else {
newItem.next = this.head
this.head = newItem
}
// 辅助栈判断逻辑
if (!this.subHead) {
this.subHead = subNewItem
} else {
if (this.subHead.data >= x) {
subNewItem.next = this.subHead
this.subHead = subNewItem
}
}
};
/**
* @return {void}
*/
MinStack.prototype.pop = function () {
if (!this.head) return false
const result = this.head.data
if (this.subHead && result === this.subHead.data) {
this.subHead = this.subHead.next
}
this.head = this.head.next
return result
};
/**
* @return {number}
*/
MinStack.prototype.top = function () {
if (!this.head) return false
return this.head.data
};
/**
* @return {number}
*/
MinStack.prototype.getMin = function () {
if (!this.head || !this.subHead) return false
return this.subHead.data
};
394、字符串解码
点评:维护两个栈,一个数字栈,一个字符串栈。遇到数字先计算倍数,遇见字母先计算result。直到遇到[, 将倍数入数字栈,将result入字符串栈。并且都清空。遇到]时,意味着要清算了(cry)。取出数字栈顶,取出字符串栈顶元素 拼接 当前result * 倍数。
var decodeString = function (s) {
let numStack = [];
let strStack = [];
let num = 0;
let result = '';
for (const c of s) {
if (!isNaN(c)) {
num = num * 10 + Number(c);
} else if (c === '[') {
strStack.push(result);
result = '';
numStack.push(num);
num = 0;
} else if (c === ']') {
let repeatTimes = numStack.pop();
result = strStack.pop() + result.repeat(repeatTimes);
} else {
result += c;
}
}
return result
};
739、每日温度
点评:单调栈
var dailyTemperatures = function (temperatures) {
let arr = [];
let result = new Array(temperatures.length).fill(0);
let index = [0];
arr.push(temperatures[0])
for (let i = 1; i < temperatures.length; i++) {
while(temperatures[i] > arr[arr.length - 1]) {
result[index[index.length-1]] = i - index[index.length-1];
arr.pop();
index.pop()
}
arr.push(temperatures[i]);
index.push(i);
if (temperatures[i] < arr[arr.length- 1]) {
arr.push(temperatures[i]);
index.push(i);
}
}
return result
};
215、数组中的第K大元素
点评:
121、买卖股票的最佳时机
点评:直接从后面遍历
var maxProfit = function(prices) {
let result = 0;
let max = prices[prices.length - 1];
for(let i = prices.length - 2;i>=0;i--){
if(prices[i] > max) {
max = prices[i];
}else {
result = Math.max(max - prices[i],result);
}
}
return result;
};
55、跳跃游戏
点评:技巧在于重复刷新cover的值。
var canJump = function(nums) {
if(nums.length <= 1) {
return true;
} else if (nums[0] === 0) {
return false;
}
let cover = 0;
for(let i = 0;i<= cover;i++) {
cover = Math.max(cover,nums[i]+i);
if(cover>= nums.length-1) {
return true;
}
}
return false;
};
45、跳跃游戏II
点评:
var jump = function(nums) {
let count = 0, curIndex = 0,maxIndex = 0;
for(let i = 0;i<nums.length-1;i++) {
maxIndex = Math.max(maxIndex,nums[i] + i);
if(i===curIndex) {
curIndex = maxIndex;
count++;
}
}
return count;
};
763、划分字母区间
点评:先利用哈希表整体遍历一遍,统计各个字符的出现位置。再遍历一次s,设置left,right为0;使right更新为遍历到的字母最远的位置。这样就可以防止一个字母在多个片段中出现。
var partitionLabels = function(s) {
let hash = {}
for(let i = 0; i < s.length; i++) {
hash[s[i]] = i
}
let result = []
let left = 0
let right = 0
for(let i = 0; i < s.length; i++) {
right = Math.max(right, hash[s[i]])
if(right === i) {
result.push(right - left + 1)
left = i + 1
}
}
return result
};
70、爬楼梯
点评:就是斐波那契数列
var climbStairs = function (n) {
let dp = [];
dp[0] = 1;
dp[1] = 2;
if (n === 1) {
return 1
}
const climbFun = (n) => {
return dp[n - 1] + dp[n - 2]
}
for (let i = 2; i < n; i++) {
dp.push(climbFun(i))
}
return dp[dp.length - 1]
};
118、杨辉三角
点评:
var generate = function (numRows) {
if (numRows === 1) {
return [[1]]
} else if (numRows === 2) {
return [[1], [1, 1]]
} else {
let i = 3;
let result = [[1], [1, 1]];
while (i <= numRows) {
let arr = new Array(i);
arr[0] = 1;
arr[arr.length - 1] = 1;
for (let j = 1; j < arr.length - 1; j++) {
arr[j] = result[i - 2][j] + result[i - 2][j - 1]
}
result.push(arr);
i++;
}
return result
}
};
198、打家劫舍
点评
var rob = function(nums) {
let dp = new Array(nums.length + 1).fill(0);
dp[1] = nums[0];
for(let i = 1 ;i<nums.length;i++) {
dp[i + 1] = Math.max(dp[i-1] +nums[i],dp[i])
}
return dp[nums.length]
};
279、完全平方数
点评
var numSquares = function(n) {
let dp = new Array(n + 1).fill(Infinity)
dp[0] = 0
for(let i = 1; i**2 <= n; i++) {
let val = i**2
for(let j = val; j <= n; j++) {
dp[j] = Math.min(dp[j], dp[j - val] + 1)
}
}
return dp[n]
};
322、零钱兑换
点评:
var coinChange = function(coins, amount) {
let dp = new Array(amount+1).fill(Infinity);
dp[0] = 0;
for(let i = 0;i<coins.length;i++) {
for(let j = 1;j<=amount;j++) {
if(j >= coins[i] ) {
dp[j] = Math.min(dp[j-coins[i]]+1,dp[j])
}
}
}
if(dp[amount] === Infinity) {
return -1
}
return dp[amount]
};
139、单词拆分
点评:
var wordBreak = function(s, wordDict) {
let dp = new Array(s.length+1).fill(0);
dp[0] = 1;
for(let i = 0;i<=s.length;i++) {
for(let j = 0;j<wordDict.length;j++) {
if(i>=wordDict[j].length) {
if(s.slice(i-wordDict[j].length,i) === wordDict[j] && dp[i-wordDict[j].length]) {
dp[i] = 1;
}
}
}
}
return dp[dp.length-1]
};
300、最长递增子序列
点评:
var lengthOfLIS = function(nums) {
let dp = new Array(nums.length).fill(1);
let result = 1;
for(let i = 1;i<nums.length;i++) {
for(let j = 0;j<=i-1;j++) {
if(nums[i] > nums[j]) {
dp[i] = Math.max(dp[i],dp[j] + 1)
}
}
result = Math.max(result,dp[i])
}
return result
};
152、乘积最大子数组
点评:
let max = min = nums[0], dp = [nums[0]]
for (let i=1; i<nums.length; i++) {
if (nums[i] < 0) {
[max, min] = [min, max]
}
max = Math.max(max*nums[i], nums[i])
min = Math.min(min*nums[i], nums[i])
dp[i] = max
}
return Math.max(...dp)
};
416、分割等和子集
点评:
var canPartition = function(nums) {
const sum = (nums.reduce((p, v) => p + v));
if (sum & 1) return false;
const dp = Array(sum / 2 + 1).fill(0);
for(let i = 0; i < nums.length; i++) {
for(let j = sum / 2; j >= nums[i]; j--) {
dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]);
if (dp[j] === sum / 2) {
return true;
}
}
}
return dp[sum / 2] === sum / 2;
};
62、不同路径
点评:
var uniquePaths = function(m, n) {
let dp = new Array(m).fill(1).map((item) => new Array(n).fill(1));
for(let i = 1; i< m;i++) {
for(let j = 1;j<n;j++) {
dp[i][j] = dp[i-1][j] + dp[i][j-1];
}
}
return dp[m-1][n-1]
};
64、最小路径和
点评:多维dp
var minPathSum = function(grid) {
let n = grid.length;
let m = grid[0].length;
let dp = new Array(n).fill(0).map(()=>new Array(m).fill(0));
dp[0][0] = grid[0][0];
for(let i = 1;i<n;i++){
dp[i][0] = grid[i][0] + dp[i-1][0];
}
for(let j = 1;j<m;j++){
dp[0][j] = grid[0][j] + dp[0][j-1];
}
for(let i = 1;i<n;i++){
for(let j = 1;j<m;j++){
dp[i][j] = Math.min(dp[i-1][j],dp[i][j-1]) + grid[i][j];
}
}
return dp[n-1][m-1]
};
5、最长回文子串
点评:双指针
let max = '';
let s = 'abbac';
const helper = function (l, r) {
while (l >= 0 && r < s.length && s[l] === s[r]) {
l--;
r++;
}
const maxStr = s.slice(l + 1, r + 1 - 1);
if (maxStr.length > max.length) {
max = maxStr;
}
}
for (let i = 0; i < s.length; i++) {
helper(i, i);
helper(i, i + 1);
}
console.log(max)
1143、最长公共子序列
点评:二维dp用来表示text1的前i个字符与text2的前j个字符的最大公共子序列。明白这一点这题就做明白了
var longestCommonSubsequence = function (text1, text2) {
let dp = Array.from(Array(text1.length + 1), () => Array(text2.length + 1).fill(0));
let max = 0;
for (let i = 1; i <= text1.length; i++) {
for (let j = 1; j <= text2.length; j++) {
if (text1[i - 1] === text2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
max = Math.max(max, dp[i][j])
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1])
}
}
}
return max;
};
72、编辑距离
点评:初始化多维dp,要点就是取左上角,左边,上边,中的最小值+1
var minDistance = function(word1, word2) {
const len1 = word1.length;
const len2 = word2.length;
const dp = Array.from(Array(len1 + 1), () => Array(len2 + 1))
dp[0][0] = 0
for (let i = 1; i <= len1; i++) dp[i][0] = dp[i - 1][0] + 1
for (let i = 1; i <= len2; i++) dp[0][i] = dp[0][i - 1] + 1
for (let i = 1; i <= len1; i++) {
for (let j = 1; j <= len2; j++) {
if (word1[i - 1] == word2[j - 1])
dp[i][j] = dp[i - 1][j - 1]
else
dp[i][j] = Math.min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j-1]) + 1
}
}
return dp[len1][len2]
};
136、只出现一次的数字
点评:异或大法好
var singleNumber = function(nums) {
let ans = 0;
for(const x of nums) {
ans ^= x;
}
return ans
};
169、多数元素
点评:
var majorityElement = function(nums) {
let count = 1;
let majority = nums[0];
for(let i = 1;i<nums.length;i++){
if(count === 0){
majority = nums[i]
}
if(nums[i] === majority) {
count++
}else {
count--
}
}
return majority
};
75、颜色分类
点评:双指针
var sortColors = function (nums) {
let L = 0,
ans = 0,
R = nums.length - 1;
for (i = 0; i <= R; i++) {
if (nums[i] === 0) {
ans = nums[L];
nums[L] = nums[i];
nums[i] = ans;
L++;
} else if (nums[i] === 2) {
ans = nums[R];
nums[R] = nums[i];
nums[i] = ans;
R--;
i--;
}
}
return nums;
};
31、下一个排列
点评:
287、寻找重复数
点评:
var findDuplicate = function(nums) {
let slowPointer = 0
let fastPointer = 0
while (true) {
slowPointer = nums[slowPointer]
fastPointer = nums[nums[fastPointer]]
if (slowPointer == fastPointer) {
let _slowPointer = 0
while (nums[_slowPointer] !== nums[slowPointer]) {
slowPointer = nums[slowPointer]
_slowPointer = nums[_slowPointer]
}
return nums[_slowPointer]
}
}
};