Skip to content

Hot100

作者:江月迟迟
发表于:2024-12-11
字数统计:18120 字
预计阅读61分钟

哈希

1. 两数之和

js
/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function(nums, target) {
    const map = new Map()
    for(let i = 0; i < nums.length; i++) {
        if(map.has(target - nums[i])) {
            return [map.get(target - nums[i]), i]
        } else {
            map.set(nums[i], i)
        }
    }
    return [1, 1]
};

49. 字母异位词分组

js
/**
 * @param {string[]} strs
 * @return {string[][]}
 */
//  sort
var groupAnagrams = function(strs) {
    const strs1 = strs.map(item => item.split('').sort((a,b) => a.charCodeAt() - b.charCodeAt()).join(''))
    const map = new Map()
    let result = []
    for(let i = 0; i < strs.length; i++) {
        if(!map.has(strs1[i])) {
            map.set(strs1[i], [strs[i]])
        } else {
            map.set(strs1[i], [...map.get(strs1[i]), strs[i]])
        }
    }
    result = [...map.values()]
    return result
};

128. 最长连续序列

js
/**
 * @param {number[]} nums
 * @return {number}
 */
var longestConsecutive = function(nums) {
    const map = new Map()
    let ans = 0
    let result = 0
    for(let i = 0; i < nums.length; i++) {
        map.set(nums[i], 1)
    }
    for(let i = 0; i < nums.length; i++) {
        if(map.has(nums[i] + 1)) continue
        while(map.has(nums[i] - ans)) {
            ans++
        }
        result = Math.max(result, ans)
        ans = 0
    }
    return result
};

双指针

283. 移动零

js
var moveZeroes = function(nums) {
    let slow = 0, fast = 0
    for(; fast < nums.length; fast++) {
        if(nums[fast] !== 0) {
            let tmp = nums[fast]
            nums[fast] = nums[slow]
            nums[slow] = tmp
            slow++
        }
    }
};
js
/**
 * @param {number[]} nums
 * @return {void} Do not return anything, modify nums in-place instead.
 */
var moveZeroes = function(nums) {
    let slow = 0, fast = 0
    for(; fast < nums.length; fast++) {
        if(nums[fast] !== 0) {
            nums[slow] = nums[fast]
            slow++
        }
    }
    for(; slow < nums.length; slow++) {
        nums[slow] = 0
    }
};

11. 盛最多水的容器

js
/**
 * @param {number[]} height
 * @return {number}
 */
var maxArea = function(height) {
    let ans = 0, left = 0, right = height.length - 1
    while(left < right) {
        const area = (right - left) * Math.min(height[left], height[right])
        ans = Math.max(ans, area)
        if (height[left] < height[right]) {
            left++
        }else {
            right--
        }
    }
    return ans
};

15. 三数之和

js
/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var threeSum = function(nums) {
    const set = new Set()
    const result = []
    nums.sort((a,b) => a-b)
    for(let i = 0; i < nums.length; i++) {
        let left = i + 1
        let right = nums.length - 1
        while(left < right) {
            const sum = nums[i] + nums[left] + nums[right]
            if(sum > 0) {
                right--
            } else if (sum < 0) {
                left++
            } else {
                const ans = [nums[i], nums[left], nums[right]]
                const str = ans.join(',')
                if(!set.has(str)) {
                    set.add(str)
                    result.push(ans)
                }
                right--
                left++
            }
        }
    }
    return result
};
js
/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var threeSum = function(nums) {
    const result = []
    nums.sort((a,b) => a-b)
    for(let i = 0; i < nums.length; i++) {
        let left = i + 1
        let right = nums.length - 1
        if(i > 0 && nums[i] === nums[i-1]) continue
        while(left < right) {
            const sum = nums[i] + nums[left] + nums[right]
            if(sum > 0) {
                right--
            } else if (sum < 0) {
                left++
            } else {
                const n1 = nums[left]
                const n2 = nums[right]
                result.push([nums[i], n1, n2])
                while(left < right && n1 === nums[left]) {
                    left++
                }
                while(left < right && n2 === nums[right]) {
                    right--
                }
            }
        }
    }
    return result
};

42. 接雨水

go
//暴力解法
var trap = function(height) {
    const len = height.length;
    let sum = 0;
    for(let i = 0; i < len; i++){
        // 第一个柱子和最后一个柱子不接雨水
        if(i == 0 || i == len - 1) continue;
        let rHeight = height[i]; // 记录右边柱子的最高高度
        let lHeight = height[i]; // 记录左边柱子的最高高度
        for(let r = i + 1; r < len; r++){
            if(height[r] > rHeight) rHeight = height[r];
        }
        for(let l = i - 1; l >= 0; l--){
            if(height[l] > lHeight) lHeight = height[l];
        }
        let h = Math.min(lHeight, rHeight) - height[i];
        if(h > 0) sum += h;
    }
    return sum;
};

//双指针
var trap = function(height) {
    const len = height.length;
    if(len <= 2) return 0;
    const maxLeft = new Array(len).fill(0);
    const maxRight = new Array(len).fill(0);
    // 记录每个柱子左边柱子最大高度
    maxLeft[0] = height[0];
    for(let i = 1; i < len; i++){
        maxLeft[i] = Math.max(height[i], maxLeft[i - 1]);
    }
    // 记录每个柱子右边柱子最大高度
    maxRight[len - 1] = height[len - 1];
    for(let i = len - 2; i >= 0; i--){
        maxRight[i] = Math.max(height[i], maxRight[i + 1]);
    }
    // 求和
    let sum = 0;
    for(let i = 0; i < len; i++){
        let count = Math.min(maxLeft[i], maxRight[i]) - height[i];
        if(count > 0) sum += count;
    }
    return sum;
};

//单调栈 js数组作为栈
var trap = function(height) {
    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;
};

//单调栈 简洁版本 只处理情况三
var trap = function(height) {
    const len = height.length;
    if(len <= 2) return 0; // 可以不加
    const st = [];// 存着下标,计算的时候用下标对应的柱子高度
    st.push(0);
    let sum = 0;
    for(let i = 1; i < len; i++){ // 只处理的情况三,其实是把情况一和情况二融合了
        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;
};

// 双指针2
var trap = function(height) {
    let ans = 0, left = 0, right = height.length - 1, preMax = 0, sufMax = 0
    while(left < right) {
        preMax = Math.max(preMax, height[left])
        sufMax = Math.max(sufMax, height[right])
        if(preMax < sufMax) {
            ans += preMax - height[left]
            left++
        } else {
            ans += sufMax - height[right]
            right--
        }
    }
    return ans
}

滑动窗口

3. 无重复字符的最长子串

js
/**
 * @param {string} s
 * @return {number}
 */
var lengthOfLongestSubstring = function(s) {
    let left = 0, ans = 0
    const set = new Set()
    for(let right = 0; right < s.length; right++) {
        if(!set.has(s[right])) {
            set.add(s[right])
            ans = Math.max(ans, set.size)
        } else {
            // 一直删,直到刚好没left
            while(set.has(s[right])) {
                set.delete(s[left])
                left++
            }
        }
        set.add(s[right])
    }
    return ans
};
js
/**
 * @param {string} s
 * @return {number}
 */
var lengthOfLongestSubstring = function(s) {
    let left = 0, ans = 0
    const set = new Set()
    for(let right = 0; right < s.length; right++) {
        // 一直删,直到刚好没left
        while(set.has(s[right])) {
            set.delete(s[left])
            left++
        }
        set.add(s[right])
        ans = Math.max(ans, set.size)

    }
    return ans
};

438. 找到字符串中所有字母异位词

js
/**
 * @param {string} s
 * @param {string} p
 * @return {number[]}
 */
var findAnagrams = function(s, p) {
    let ans = [], left = 0, pn = p.length, right =0
    const sCount = Array(26).fill(0)
    const pCount = Array(26).fill(0)
    const aCode = 'a'.charCodeAt()
    for(let i = 0; i < pn; i++) {
        pCount[p[i].charCodeAt() - aCode]++
    }
    while(right < s.length) {
        sCount[s[right].charCodeAt() - aCode]++
        if(right - left === pn - 1) {
            // 判断
            if(sCount.join('') === pCount.join('')) {
                ans.push(left)
            }
            sCount[s[left].charCodeAt() - aCode]--
            left++
        }
        right++
    }
    return ans
};

子串

560. 和为 K 的子数组

js
/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number}
 */
//  前缀和
// 一段子数组的和为:数组末端的前缀和减去数组开头的前缀
var subarraySum = function(nums, k) {
    let preSum = 0, ans = 0;
    const map = new Map();
    map.set(0, 1);
    for(let num of nums) {
        preSum += num
        if(map.has(preSum - k)) {
            ans += map.get(preSum - k)
        }
        map.set(preSum, (map.get(preSum) || 0) + 1)
    }
    console.log(map)
    return ans
};

239. 滑动窗口最大值

js
/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number[]}
 */
var maxSlidingWindow = function(nums, k) {
    const ans = [], q = []
    for(let i = 0; i < nums.length; i++) {
        // 维护队列单调递减
        while(q.length && nums[i] > nums[q[q.length-1]]) {
            q.pop()
        }
        q.push(i)
        if(i - q[0] >= k) {
            q.shift()
        }
        if(i >= k - 1) {
            ans.push(nums[q[0]])
        }
    }
    return ans
};

76. 最小覆盖子串

js
/**
 * @param {string} s
 * @param {string} t
 * @return {string}
 */
function minWindow1(s, t) {
    if (!s || !t) return "";

    // 记录 t 中每个字符的出现次数
    const tCount = new Map();
    for (let char of t) {
        tCount.set(char, (tCount.get(char) || 0) + 1);
    }

    const currentCount = new Map();
    let left = 0;
    let minLength = Infinity;
    let minWindow = "";
    let required = tCount.size;
    let formed = 0;

    // 移动右指针
    for (let right = 0; right < s.length; right++) {
        let currentChar = s[right];
        currentCount.set(currentChar, (currentCount.get(currentChar) || 0) + 1);

        // 检查 currentChar 是否是 t 中的字符并且当前窗口中该字符数量符合要求
        if (tCount.has(currentChar) && currentCount.get(currentChar) === tCount.get(currentChar)) {
            formed++;
        }

        // 缩小窗口
        while (left <= right && formed === required) {
            let windowLength = right - left + 1;
            if (windowLength < minLength) {
                minLength = windowLength;
                minWindow = s.slice(left, right + 1);
            }

            // 移出左边字符
            let leftChar = s[left];
            currentCount.set(leftChar, currentCount.get(leftChar) - 1);
            if (tCount.has(leftChar) && currentCount.get(leftChar) < tCount.get(leftChar)) {
                formed--;
            }
            left++;
        }
    }

    return minWindow;
}


function minWindow2(s, t) {
        // 初始化
        let i = 0, j = 0;
        let needMap = new Map();
        let needCnt = t.length;
        let res = '';

        // 初始化 needMap
        for (let char of t) {
            needMap.set(char, (needMap.get(char) || 0) + 1);
        }

        // 移动滑窗右边界
        while (j < s.length) {
            // 判断是否满足条件
            if (needMap.has(s[j])) {
                if (needMap.get(s[j]) > 0) {
                    needCnt -= 1;
                }
                needMap.set(s[j], needMap.get(s[j]) - 1);
            }

            // 一旦满足条件,尽可能地压缩 i,并且不断更新结果
            while (needCnt === 0) {
                if (!res || j - i + 1 < res.length) {
                    res = s.slice(i, j + 1);
                }

                if (needMap.has(s[i])) {
                    if (needMap.get(s[i]) === 0) {
                        needCnt += 1;
                    }
                    needMap.set(s[i], needMap.get(s[i]) + 1);
                }
                i += 1;
            }

            j += 1;
        }
        
        return res;
    }

function minWindow(s, t) {
    let left = 0, right = 0, rc = 0, c = 0, ans = ''
    const tmap = new Map()
    const smap = new Map()
    // 记录t所需要的字符
    for(let char of t) tmap.set(char, (tmap.get(char) || 0 )+ 1)
    // required count
    rc = tmap.size
    // 开始滑动窗口逻辑
    // 最短型滑动窗口,优先收敛(移动左指针)
    while(right < s.length) {
        const char = s[right]
        smap.set(char, (smap.get(char) || 0 ) + 1)

        if(tmap.has(char) && smap.get(char) === tmap.get(char)) {
            c++
        }

        while(left <= right && rc === c) {
            let len = right - left + 1
            if(len < ans.length || ans.length === 0) {
                ans = s.slice(left, right+1)
            }
            // 推出队首
            const leftchar = s[left]
            smap.set(leftchar, (smap.get(leftchar) || 0) - 1)
            left++
            if(tmap.has(leftchar) && smap.get(leftchar) < tmap.get(leftchar)) {
                c--
            }
        }

        right++
    }
    return ans
}

普通数组

53. 最大子数组和

js
/**
 * @param {number[]} nums
 * @return {number}
 */
//  动态规划
var maxSubArray = function(nums) {
    const dp = Array(nums.length).fill(-Infinity)
    dp[0] = nums[0]
    let max = dp[0]
    for(let i = 1; i < nums.length; i++) {
        dp[i] = Math.max(dp[i-1] + nums[i], nums[i])
        max = Math.max(max, dp[i])
    }
    return max
};

// 贪心
var maxSubArray = function(nums) {
    let result = -Infinity
    let sum = 0
    for(let i = 0; i < nums.length; i++) {
        sum += nums[i]
        if(sum > result) {
            result = sum
        }
        if(sum < 0) {
            sum = 0
        }
    }
    return result
}

56. 合并区间

js
/**
 * @param {number[][]} intervals
 * @return {number[][]}
 */
var merge = function(intervals) {
    intervals.sort((a,b) => a[0] - b[0])
    const result = []
    intervals.push([Infinity, Infinity])
    for(let i = 0; i < intervals.length-1; i++) {
        const [al, ar] = intervals[i]
        const [bl, br] = intervals[i+1]
        // 不重叠
        if(ar < bl) {
            result.push(intervals[i])
        } else {
            // 处理重叠区间
            delete intervals[i]
            intervals[i+1] = [al, Math.max(ar, br)]
        }
    }
    return result
};

189. 轮转数组

js
/**
 * @param {number[]} nums
 * @param {number} k
 * @return {void} Do not return anything, modify nums in-place instead.
 */
var rotate1 = function(nums, k) {
    k = k % nums.length
    const s1 = nums.slice(nums.length - k)
    const s2 = nums.slice(0, nums.length - k)
    console.log(s1, s2)
    while(nums.length) nums.pop()
    nums.unshift(...s1, ...s2)
};

var rotate = function(nums, k) {
    function reverse(left, right) {
        while(left < right) {
            [nums[left], nums[right]] = [nums[right], nums[left]]
            left++
            right--
        }
    }

    const n = nums.length
    k %= n
    reverse(0, n-1)
    reverse(0, k-1)
    reverse(k, n-1)
};

238. 除自身以外数组的乘积

js
/**
 * @param {number[]} nums
 * @return {number[]}
 */
var productExceptSelf = function(nums) {
    const prefix = Array(nums.length).fill(1)
    const suffix = Array(nums.length).fill(1)
    const result = Array(nums.length).fill(0)
    for(let i = 1; i < nums.length; i++) {
        prefix[i] = prefix[i-1] * nums[i-1]
    }
    for(let j = nums.length - 2; j >= 0; j--) {
        suffix[j] = suffix[j+1] * nums[j+1]
    }
    for(let i = 0; i < nums.length; i++) {
        result[i] = prefix[i] * suffix[i]
    }
    return result
};

41. 缺失的第一个正数

js
/**
 * @param {number[]} nums
 * @return {number}
 */
var firstMissingPositive = function(nums) {
    let n = nums.length

    for(let i = 0; i < n; i++) {
        while(nums[i] > 0 && nums[i] <= n &&  nums[nums[i]-1] !== nums[i]) {
            let tmp = nums[nums[i]-1]
            nums[nums[i]-1] = nums[i] 
            nums[i] = tmp
        }
    }

    for(let i = 0; i <n ;i++) {
        if(nums[i] !== i+1) return i+1
    }
    return n+1
};

矩阵

73. 矩阵置零

js
/**
 * @param {number[][]} matrix
 * @return {void} Do not return anything, modify matrix in-place instead.
 */
var setZeroes = function(matrix) {
    const handle = []
    const n = matrix.length
    const m = matrix[0].length
    for(let i = 0; i < n; i++) {
        for(let j = 0; j < m; j++) {
            if(matrix[i][j] === 0) {
                handle.push([i,j])
            }
        }
    }
    for(let k = 0; k < handle.length; k++) {
        const [i, j] = handle[k]
        for(let i1 = 0; i1 < n; i1++) {
            matrix[i1][j] = 0
        }
        for(let j1 = 0; j1 < m; j1++) {
            matrix[i][j1] = 0
        }
    }
};

54. 螺旋矩阵

js
/**
 * @param {number[][]} matrix
 * @return {number[]}
 */
var spiralOrder = function(matrix) {
    const n = matrix.length
    const m = matrix[0].length
    let l = 0, r = m - 1, t = 0, b = n - 1
    const result = []
    while(l <= r && t <= b) {
        // 向右
        for(let i = l; i <= r; i++) {
            result.push(matrix[t][i])
        }
        t++
        // 向下
        for(let j = t; j <= b; j++) {
            result.push(matrix[j][r])
        }
        r--
        if(l > r || t > b) break
        // 向左
        for(let k = r; k >= l; k--) {
            result.push(matrix[b][k])
        }
        b--
        // 向上
        for(let h = b; h >= t; h--) {
            result.push(matrix[h][l])
        }
        l++
    }
    return result
};

48. 旋转图像

js
/**
 * @param {number[][]} matrix
 * @return {void} Do not return anything, modify matrix in-place instead.
 */
var rotate = function(matrix) {
    const n = matrix.length
    // matrix[i][j] = matrix[j][n-i-1]
    for(let i = 0; i < Math.floor(n / 2); i++) {
        for(let j = 0; j < Math.floor((n+1) / 2) ; j++) {
            let tmp = matrix[i][j]
            matrix[i][j] = matrix[n - 1 - j][i]
            matrix[n - 1 - j][i] = matrix[n - 1 - i][n - 1 - j]
            matrix[n - 1 - i][n - 1 - j] = matrix[j][n - 1 - i]
            matrix[j][n - 1 - i] = tmp
        }
    }
};

image-20241203112257760

作者:Krahets

https://leetcode.cn/problems/rotate-image/solutions/1228078/48-xuan-zhuan-tu-xiang-fu-zhu-ju-zhen-yu-jobi

240. 搜索二维矩阵 II

js
/**
 * @param {number[][]} matrix
 * @param {number} target
 * @return {boolean}
 */
//  盲目递归
var searchMatrix = function(matrix, target) {
    const m = matrix.length
    const n = matrix[0].length
    function dfs(i, j) {
        if(i >= m || j >= n) return false
        if(matrix[i][j] === target) return true
        else if (matrix[i][j] < target) {
            return dfs(i+1,j) || dfs(i,j+1)
        } else {
            return false
        }
    }
    return dfs(0, 0)
};

// z型搜索
var searchMatrix = function(matrix, target) {
    const n = matrix.length
    const m = matrix[0].length
    let i = 0, j = m-1
    while(i < n && j >= 0) {
        console.log(matrix[i][j])
        if(matrix[i][j] > target) {
            j--
        } else if (matrix[i][j] < target) {
            i++
        } else {
            return true
        }
    }
    return false
}

链表

160. 相交链表

js
/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} headA
 * @param {ListNode} headB
 * @return {ListNode}
 */
var getIntersectionNode = function(headA, headB) {
    let pa = headA, pb = headB
    while(pa !== pb) {
        pa = pa ? pa.next : headB
        pb = pb ? pb.next : headA
    }
    return pa
};

206. 反转链表

js
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
//  头插法
var reverseList = function(head) {
    let pre = null, cur = head
    while(cur) {
        let tmp = cur.next
        cur.next = pre
        pre = cur
        cur = tmp
    }
    return pre
};

234. 回文链表

js
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @return {boolean}
 */
var isPalindrome = function(head) {
    const mid = middleNode(head)
    let head2 = reverseList(mid)
    while(head2 !== null) {
        if(head.val !== head2.val) {
            return false
        }
        head = head.next
        head2 = head2.next
    }
    return true
};

function middleNode(head) {
    let slow = head, fast = head
    while(fast !== null && fast.next !== null) {
        slow = slow.next
        fast = fast.next.next
    }
    return slow
}

function reverseList(head) {
    let pre = null, cur = head
    while(cur) {
        const nxt = cur.next
        cur.next = pre
        pre = cur
        cur = nxt
    }
    return pre
}

141. 环形链表

js
/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} head
 * @return {boolean}
 */
var hasCycle = function(head) {
    let slow = head, fast = head
    while(fast && fast.next) {
        fast = fast.next.next
        slow = slow.next
        if(fast === slow) return true
    }
    return false
};

142. 环形链表 II

js
/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var detectCycle = function(head) {
    let slow = head, fast = head
    while(fast && fast.next) {
        slow = slow.next
        fast = fast.next.next
        if(slow === fast) {
            // 现在仅说明有环,接着要找到相遇点(环的入口)
            // 如果有环,那么入口被head来的和环的末尾同时指着
            // 快慢指针会精准找到与入口步差为n(n为head到环入口)的的环内指针
            // 然后就可以用下面
            let p1 = head
            let p2 = fast
            while(p1 !== p2) {
                p1 = p1.next
                p2 = p2.next
            }
            return p1
        }
    }
    return null
};

21. 合并两个有序链表

js
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} list1
 * @param {ListNode} list2
 * @return {ListNode}
 */
var mergeTwoLists1 = function(list1, list2) {
    return mergeHelper(list1, list2)

    function mergeHelper(l1, l2) {
        if(!l1) return l2
        if(!l2) return l1
        if(l1.val < l2.val) {
            l1.next = mergeHelper(l1.next, l2)
            return l1
        } else {
            l2.next = mergeHelper(l1, l2.next)
            return l2
        }
    }
};


var mergeTwoLists = function(list1, list2) {
    const dummyHead = new ListNode(0)
    let cur = dummyHead
    while(list1 && list2) {
        if(list1.val < list2.val) {
            cur.next = list1
            list1 = list1.next
        } else {
            cur.next = list2
            list2 = list2.next
        }
        cur = cur.next
    }
    cur.next = list1 ? list1 : list2
    return dummyHead.next
};


var mergeTwoLists = function(list1, list2) {
    const dhead = new TreeNode(0)
    let cur = dhead
    while(list1 && list2) {
        if(list1.val < list2.val) {
            cur.next = list1
            list1 = list1.next
        } else {
            cur.next = list2
            list2 = list2.next
        }
        cur = cur.next
    }
    cur.next = list1 ? list1 : list2
    return dhead.next
};

2. 两数相加

js
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} l1
 * @param {ListNode} l2
 * @return {ListNode}
 */
var addTwoNumbers = function(l1, l2) {
    const dhead = new ListNode(0)
    let p1 = l1, p2 = l2, p3 = dhead, carry = 0
    while(p1 || p2 || carry) {
        let val = 0
        if(p1) {
            val += p1.val
            p1 = p1.next
        }
        if(p2) {
            val += p2.val
            p2 = p2.next
        }
        p3.next = new ListNode((val + carry) % 10)
        carry = Math.floor((val + carry) / 10)
        p3 = p3.next
    }
    return dhead.next
};

19. 删除链表的倒数第 N 个结点

js
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} n
 * @return {ListNode}
 */
var removeNthFromEnd = function(head, n) {
    let dummyHead = new ListNode(0, head)
    let slow = dummyHead, fast = dummyHead
    // 先走n步,使得使得slow少走n步
    while(n--) {
        fast = fast.next
    }
    // 让fast到结尾,使得slow定位到倒数第N个节点的前一个(因为有dummyHead)
    while(fast.next) {
        fast = fast.next
        slow = slow.next
    }
    // 执行删除逻辑
    slow.next = slow.next.next
    return dummyHead.next
};

24. 两两交换链表中的节点

js
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var swapPairs = function(head) {
    let dummyHead = new ListNode(0, head)
    let cur = dummyHead
    while(cur && cur.next && cur.next.next) {
        let cur1 = cur.next
        let cur2 = cur.next.next

        cur1.next = cur2.next
        cur2.next = cur1
        cur.next = cur2

        cur = cur.next.next
    }
    return dummyHead.next
};
js
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var swapPairs = function(head) {
    let dummyHead = new ListNode(0)
    dummyHead.next = head
    let cur = dummyHead
    while(cur.next && cur.next.next) {
        let tmp = cur.next
        let tmp1 = cur.next.next.next
        
        cur.next = cur.next.next
        cur.next.next = tmp
        cur.next.next.next = tmp1

        cur = cur.next.next;
    }
    let result = dummyHead.next
    delete dummyHead
    return result
};

25. K 个一组翻转链表

js
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} k
 * @return {ListNode}
 */
var reverseKGroup = function(head, k) {
    // 求个长度
    let n = 0, phelper = head
    while(phelper) {
        phelper = phelper.next
        n++
    }

    // 92. 反转链表 II
    const dummyHead = new ListNode(0, head)
    let p0 = dummyHead, cur, pre
    pre = null
    cur = p0.next
    while(n >= k) {
        for(let i = 0; i < k; i++) {
            let nxt = cur.next
            cur.next = pre
            pre = cur
            cur = nxt
        }
        let nxt = p0.next
        p0.next.next = cur
        p0.next = pre
        p0 = nxt

        n -= k
    }
    return dummyHead.next
};

138. 随机链表的复制

交错链表法

js
/**
 * // Definition for a _Node.
 * function _Node(val, next, random) {
 *    this.val = val;
 *    this.next = next;
 *    this.random = random;
 * };
 */

/**
 * @param {_Node} head
 * @return {_Node}
 */
//  复制到原链表,然后通过奇偶链表导出
var copyRandomList = function(head) {
    if(!head) return null

    // 复制每个节点,将新节点插入到原节点的后面
    for(let cur = head; cur; cur = cur.next.next) {
        // 我看灵神这里不用tmp存储,但是总感觉这里会有问题
        let nxt = cur.next
        cur.next = new _Node(cur.val, nxt, null)
    }

    // 交错遍历,设置random指针
    for(let cur = head; cur; cur = cur.next.next) {
        if(cur.random) {
            // cur.random是指回去,cur.random.next才是正确的内容
            cur.next.random = cur.random.next
        }
    }

    // 将交错链表分离成两个链表
    // 1 -> 1' -> 2 -> 2' -> 3 -> 3'
    const newHead = head.next // 1'
    let cur = head // 1
    for(; cur.next.next; cur = cur.next) {
        const copy = cur.next // 1'
        cur.next = copy.next  // 1 -> 2
        copy.next = copy.next.next // 1' -> 2'
    }
    cur.next = null // 3 -> null
    return newHead
};

148. 排序链表

js
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var sortList = function(head) {
    if(!head || !head.next) {
        return head
    }

    // 找到中间节点,又包含了断开逻辑
    let head2 = middleNode(head)
    // 分治
    head = sortList(head)
    head2 = sortList(head2)
    // 合并
    return mergeTwoLists(head, head2)
};

// 876. find middle node
function middleNode(head) {
    let slow = head, fast = head
    // 这里处理比较特殊,需要找到中间节点的前一个,否则分治会出现问题
    while(fast.next && fast.next.next) {
        slow = slow.next
        fast = fast.next.next
    }
    // 独特的断开逻辑,否则会栈溢出
    let mid = slow.next
    slow.next = null
    return mid
}

// 21. merge two list
function mergeTwoLists(list1, list2) {
    const dummyHead = new ListNode(0)
    let cur = dummyHead
    dummyHead.next = list1
    while(list1 && list2) {
        if(list1.val < list2.val) {
            cur.next = list1
            list1 = list1.next
        } else {
            cur.next = list2
            list2 = list2.next
        }
        cur = cur.next
    }
    cur.next = list1 ? list1 : list2
    return dummyHead.next
}

23. 合并 K 个升序链表

js
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode[]} lists
 * @return {ListNode}
 */
var mergeKLists = function(lists) {
    while(lists.length >= 2) {
        let list = mergeTwoLists(lists[0], lists[1])
        lists.shift()
        lists.shift()
        lists.unshift(list)
    }
    return lists[0] || null
};

// 合并两个升序链表
function mergeTwoLists(list1, list2) {
    const dummyHead = new ListNode()
    let cur = dummyHead
    dummyHead.next = list1
    while(list1 && list2) {
        if(list1.val < list2.val) {
            cur.next = list1
            list1 = list1.next
        } else {
            cur.next = list2
            list2 = list2.next
        }
        cur = cur.next
    }
    cur.next = list1 ? list1 : list2
    return dummyHead.next
}

146. LRU 缓存

未完成

二叉树

94. 二叉树的中序遍历

js
/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[]}
 */
var inorderTraversal = function(root) {
    const result = []
    dfs(root)
    return result

    function dfs(node) {
        if(!node) return
        dfs(node.left)
        result.push(node.val)
        dfs(node.right)
    }
};

104. 二叉树的最大深度

js
/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number}
 */
var maxDepth = function(root) {
    if(!root) return 0
    return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1
};

var maxDepth = function(root) {
    const q = [root]
    let count = 0
    if(!root) return count
    while(q.length) {
        const len = q.length
        for(let i = 0; i < len; i++) {
            const node = q.shift()
            if(node.left) q.push(node.left)
            if(node.right) q.push(node.right)
        }
        count++
    }
    return count
}

226. 翻转二叉树

js
/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {TreeNode}
 */
var invertTree = function(root) {
    if(!root) return null
    const tmp = root.left
    root.left = root.right
    root.right = tmp
    invertTree(root.left)
    invertTree(root.right)
    return root
};

var invertTree1 = function(root) {
    const queue = []
    if(!root) return root
    queue.push(root)
    while(queue.length) {
        const length = queue.length
        for(let i = 0; i < length; i++) {
            let node = queue.shift()
            // 交换
            let tmp = node.left
            node.left = node.right
            node.right = tmp
            // 遍历
            node.left && queue.push(node.left)
            node.right && queue.push(node.right)
        }
    }
    return root
};



var invertTree = function(root) {
    const stack = []
    if(!root) return root
    stack.push(root)
    while(stack.length) {
        const node = stack.pop()
        // 交换
        let tmp = node.left
        node.left = node.right
        node.right = tmp
        // 迭代
        node.left && stack.push(node.left)
        node.right && stack.push(node.right)
    }
    return root
};

101. 对称二叉树

js
/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {boolean}
 */
var isSymmetric = function(root) {
    const q = [root]
    if(!root) return false
    while(q.length) {
        let len = q.length
        const arr = []
        for(let i = 0; i < len; i++) {
            let node = q.shift()
            if(node) {
                arr.push(node.val)
                q.push(node.left)
                q.push(node.right)
            } else {
                node = new TreeNode(101, null, null)
                arr.push(node.val)
            }

        }
        if(arr.toString() !== arr.reverse().toString()) return false
    }
    return true
};

var isSymmetric = function(root) {
    return traverseHelper(root.left, root.right)

    function traverseHelper(l, r) {
        // 都没有
        if(!l && !r) return true
        // 只有一个
        if((!l && r) || (l && !r)) return false
        // 两个都有
        return l.val === r.val && traverseHelper(l.left, r.right) && traverseHelper(l.right, r.left)
    }
};

543. 二叉树的直径

js
/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number}
 */
var diameterOfBinaryTree = function(root) {
    let ans = 0
    getNodeDepth(root)
    return ans

    function getNodeDepth (node) {
        if (node === null) {
            return -1
        }
        const leftDepth = getNodeDepth(node.left)
        const rightDepth = getNodeDepth(node.right)
        ans = Math.max(ans, leftDepth + rightDepth + 2)
        return Math.max(leftDepth, rightDepth) + 1
    }
};

102. 二叉树的层序遍历

js
/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[][]}
 */
var levelOrder = function(root) {
    const q = [root], result = []
    if(!root) return result
    while(q.length) {
        const len = q.length
        const curLevel = []
        for(let i = 0; i < len; i++) {
            const node = q.shift()
            curLevel.push(node.val)
            if(node.left) q.push(node.left)
            if(node.right) q.push(node.right)
        }
        result.push(curLevel)
    }
    return result
};

108. 将有序数组转换为二叉搜索树

js
/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {number[]} nums
 * @return {TreeNode}
 */

var sortedArrayToBST1 = function(nums) {
     if (!nums || nums.length === 0) {
        return null;
    }
    const midPosition = nums.length / 2 | 0
    // 构建节点
    const root = new TreeNode(nums[midPosition])
    root.left = sortedArrayToBST(nums.slice(0, midPosition))
    root.right = sortedArrayToBST(nums.slice(midPosition + 1))
    return root
};

var sortedArrayToBST = function(nums) {
    if(!nums.length) return null
    const mid = nums.length / 2 | 0
    const node = new TreeNode(nums[mid])
    node.left = sortedArrayToBST(nums.slice(0, mid))
    node.right = sortedArrayToBST(nums.slice(mid + 1))
    return node
};

var sortedArrayToBST = function(nums) {
    if (!nums || nums.length === 0) {
        // 源数组为0,或者nums被处理过之后为null
       return null;
   }
   const midPosition = nums.length / 2 | 0
   // 构建节点
   const root = new TreeNode(nums[midPosition], sortedArrayToBST(nums.slice(0, midPosition)), sortedArrayToBST(nums.slice(midPosition + 1)))
   return root
};

98. 验证二叉搜索树

js
/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {boolean}
 */
var isValidBST = function(root) {
  let max = -Infinity
  return traverse(root)

// 这个代码没有认识到二叉搜索树的性质,比如右节点的所有值都应该小于当前节点,是包含祖先节点的  
  function traverseCUOLE(node) {
    if(!node) return true
    if(!node.left && !node.right) return true
    let left = true, right = true
    if(node.left) {
        if(node.left.val < node.val) left = traverseCUOLE(node.left)
        else return false
    }
    if(node.right) {
        if(node.right.val > node.val) right = traverseCUOLE(node.right)
        else return false
    }
    return left && right
  }  
    // 二叉搜索树的顺序就是中序遍历的顺序,所以可以用中序遍历判断它是不是单调递增的
    function traverse(node) {
        if(!node) return true
        // 左、中、右
        let left = traverse(node.left)
        // 处理中逻辑,max使得单调递增,成立就是继续向下探
        if(max < node.val) max = node.val
        else return false
        let right = traverse(node.right)
        return left && right
    }
};

230. 二叉搜索树中第 K 小的元素

js
/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @param {number} k
 * @return {number}
 */
//  因为是二叉搜索树,所以是中序遍历的第k个
var kthSmallest = function(root, k) {
    const result = []
    traverse(root)
    return result[k-1]

    function traverse(node) {
        if(result.length >= k) return
        if(!node) return
        // 左、中、右
        traverse(node.left)
        result.push(node.val)
        traverse(node.right)
    }
};

199. 二叉树的右视图

js
/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[]}
 */
var rightSideView = function(root) {
    const q = [root], result = []
    if(!root) return result
    while(q.length) {
        const len = q.length
        const curLevel = []
        for(let i = 0; i < len; i++) {
            const node = q.shift()
            curLevel.push(node.val)
            if(node.left) q.push(node.left)
            if(node.right) q.push(node.right)
        }
        result.push(curLevel[curLevel.length-1])
    }
    return result
};

114. 二叉树展开为链表

js
/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {void} Do not return anything, modify root in-place instead.
 */
var flatten = function(root) {
    const result = []
    pretraverse(root)
    for(let i = 0; i < result.length; i++) {
        const node = result[i]
        node.left = null
        node.right = result[i+1] || null
    }

    function pretraverse(node) {
        if(!node) return
        result.push(node)
        if(node.left) pretraverse(node.left)
        if(node.right) pretraverse(node.right)
    }
};



// Morris 遍历
var flatten = function(root) {
    let current = root;

    while (current) {
        if (current.left) {
            // 找到左子树的最右节点
            let rightmost = current.left;
            while (rightmost.right) {
                rightmost = rightmost.right;
            }

            // 将原来的右子树接到左子树的最右节点
            rightmost.right = current.right;

            // 将左子树变为右子树,左子树置为 null
            current.right = current.left;
            current.left = null;
        }

        // 移动到下一个节点
        current = current.right;
    }
};

105. 从前序与中序遍历序列构造二叉树

js
/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {number[]} preorder
 * @param {number[]} inorder
 * @return {TreeNode}
 */

// 中序:左中右 
// 前序:中左右
var buildTree = function(preorder, inorder) {
    return traverse(preorder, inorder)

    function traverse(preorder, inorder) {
        // 退出条件
        if(!preorder.length) return null

        let node = preorder[0]

        // 根据前序中节点切割中序
        let index = inorder.indexOf(node)
        let left = inorder.slice(0, index)
        let right = inorder.slice(index+1)
        
        // 根据切割后的中序,切割前序
        let pleft = [], pright
        pleft = preorder.slice(1, left.length + 1)
        pright = preorder.slice(left.length + 1)
        
        node = new TreeNode(node)
        node.left = traverse(pleft, left)
        node.right = traverse(pright, right)
        console.log(left, right, pleft, pright)
        return node
    }
};

437. 路径总和 III

js
/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @param {number} targetSum
 * @return {number}
 */
//  按路径,处理每个节点从根节点向下到当下节点的前缀和
// 这段代码有错,总是有十几个用例不通过,是因为哈希表的更新不按照路径,在计算时,有可能使用了其他路径的前缀和,应该加上回溯逻辑,将更改的状态撤销
var pathSum = function(root, targetSum) {
    if(!root) return 0
    const map = new Map()
    let count = 0
    map.set(0, 1)
    traverseHelper(root, root.val)
    return count

    function traverseHelper(node, sum) {
        if(map.has(sum - targetSum)) {
            count += map.get(sum - targetSum)
        }
        map.set(sum, (map.get(sum) || 0) + 1)

        if(node.left) traverseHelper(node.left, sum + node.left.val)
        if(node.right) traverseHelper(node.right, sum + node.right.val)
        map.set(sum, map.get(sum) - 1) // 回溯逻辑
    }
};

var pathSum1 = function(root, targetSum) {
    const map = new Map();
    map.set(0, 1); // 初始化前缀和为0的路径数为1
    let count = 0;

    function traverse(node, currentSum) {
        if (!node) return;

        currentSum += node.val;

        // 检查是否存在满足条件的路径
        if (map.has(currentSum - targetSum)) {
            count += map.get(currentSum - targetSum);
        }

        // 更新前缀和出现的次数
        map.set(currentSum, (map.get(currentSum) || 0) + 1);

        // 递归遍历左右子树
        traverse(node.left, currentSum);
        traverse(node.right, currentSum);

        // 回溯,恢复状态
        map.set(currentSum, map.get(currentSum) - 1);
    }

    traverse(root, 0);
    return count;
};

236. 二叉树的最近公共祖先

js
//  后序遍历就是回溯逻辑,自底而上,自然找到最近公共祖先
var lowestCommonAncestor = function(root, p, q) {
    return posttraverse(root)

    function posttraverse(node) {
        // 这里,用left.val和right.val判断这些值会有点繁琐,所以用本身的值判断为上
        if(!node) return null
        if(node === p || node === q) return node
        let left = posttraverse(node.left)
        let right = posttraverse(node.right)
        // left 和 right的可能,要么是p\q中的一个,要么是null
        if(left && right) return node
        return left || right
    }
};

124. 二叉树中的最大路径和

js
/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number}
 */
//  决定使用posttraverse,自底而上向上,使用递归分解问题
var maxPathSum = function(root) {
    let ans = -Infinity
    posttraverse(root)
    return ans

    function posttraverse(node) {
        // 后序逻辑:左、右、中
        if(!node) return 0
        let left = posttraverse(node.left)
        let right = posttraverse(node.right)
        // 这个属于处理中逻辑
        // if(node.val <= 0) node.val = -Infinity // 这一句是错的,返回的时候可以变成0或者负数表选择,但是自己参与选择的时候不能为0,否则会这个node没有阻拦效果
        // 处理中节点,核心逻辑是加上自己,返回回去
        ans = Math.max(ans, left + right + node.val)
        // console.log(ans, Math.max(left, right) + node.val)
        const sum = Math.max(left, right) + node.val
        return sum > 0 ? sum : 0
    }
};

图论

岛屿类问题通用

200. 岛屿数量

js
/**
 * @param {character[][]} grid
 * @return {number}
 */
var numIslands = function(grid) {
    const m = grid.length
    const n = grid[0].length
    let count = 0
    for(let i = 0; i < m; i++) {
        for(let j = 0; j < n; j++) {
            if(grid[i][j] === '1') {
                dfs(i, j)
                count++
            }
        }
    }
    return count

    function dfs(i, j) {
        // 进入是,一定是'1'
        if(i >= m || j >= n || i < 0 || j < 0) return
        if(grid[i][j] === '0') return
        if(grid[i][j] === '1') grid[i][j] = '0'
        dfs(i-1, j)
        dfs(i+1, j)
        dfs(i, j-1)
        dfs(i, j+1)
    }
};

994. 腐烂的橘子

BFS还不是很熟

js
/**
 * @param {number[][]} grid
 * @return {number}
 */
var orangesRotting = function(grid) {
    const m = grid.length
    const n = grid[0].length
    let ans = 0, q = [], fresh = 0
    for(let i = 0; i < m; i++) {
        for(let j = 0; j < n; j++) {
            if(grid[i][j] === 1) fresh++
            else if (grid[i][j] === 2) {
                q.push([i, j])
            }
        }
    }
    while(fresh && q.length) {
        ans++
        const tmp = q
        q = []
        for(const [x, y] of tmp) {
            const arr = [[x-1,y], [x+1,y], [x, y-1], [x, y+1]]
            for(const [i, j] of arr) {
                if(i >= 0 && i < m && j >= 0 && j < n && grid[i][j] === 1) {
                    fresh--
                    grid[i][j] = 2
                    q.push([i,j])
                }
            }
        }
    }
    return fresh? -1:ans
    
};

207. 课程表

未完成,看起来像图论找是不是死循环。

208. 实现 Trie (前缀树)

未完成

回溯

46. 全排列

js
/**
 * @param {number[]} nums
 * @return {number[][]}
 */
//  回溯
var permute = function(nums) {
    const result = [], path = []
    backtracking(Array(nums.length).fill(0))
    return result

    function backtracking(u) {
        if(path.length === nums.length) {
            result.push(path.slice())
            return
        }
        for(let i = 0; i < nums.length; i++) {
            if(u[i] === 1) continue
            path.push(nums[i])
            backtracking(u.toSpliced(i, 1, 1))
            path.pop()
        }
    }
};

78. 子集

js
/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var subsets = function(nums) {
    const result = [], path = []
    backtracking(0)
    return result

    function backtracking(startIndex) {
        result.push(path.slice())
        if(startIndex >= nums.length) {
            // result.push(path.slice())
            return
        }
        for(let i = startIndex; i < nums.length; i++) {
            // 选或者不选
            // 选
            path.push(nums[i])
            backtracking(i+1)
            path.pop()
            // 不选
            // backtracking(i+1)
        }
    }
};
js
/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var subsets1 = function(nums) {
    const result = [], path = []
    backtracking(0)
    return result

    function backtracking(startIndex) {
        result.push(path.slice())
        if(startIndex >= nums.length) {
            // result.push(path.slice())
            return
        }
        for(let i = startIndex; i < nums.length; i++) {
            // 选或者不选
            // 选
            path.push(nums[i])
            backtracking(i+1)
            path.pop()
            // 不选
            // backtracking(i+1)
        }
    }
};


var subsets = function(nums) {
    const result = [], path = []
    backtracking(0)
    return result

    function backtracking(startIndex) {
        if(startIndex >= nums.length) {
            result.push(path.slice())
            return
        }
        // 选或者不选
        // 选
        path.push(nums[startIndex])
        backtracking(startIndex+1)
        path.pop()
        // 不选
        backtracking(startIndex+1)
    }
};

17. 电话号码的字母组合

js
/**
 * @param {string} digits
 * @return {string[]}
 */
var letterCombinations = function(digits) {
    if (!digits) return [];
    
    const map = {
        '2': 'abc', '3': 'def', '4': 'ghi', '5': 'jkl',
        '6': 'mno', '7': 'pqrs', '8': 'tuv', '9': 'wxyz'
    };
    const result = [];
    
    const backtrack = (path, index) => {
        if (index === digits.length) {
            result.push(path.join(''));
            return;
        }
        
        const letters = map[digits[index]];
        for (let i = 0; i < letters.length; i++) {
            backtrack(path.concat(letters[i]), index + 1);
        }
    };
    
    backtrack([], 0);
    return result;
};


var letterCombinations = function(digits) {
    if(digits.length === 0) return []
    const map = [
        '',
        '',
        'abc',
        'def',
        'ghi',
        'jkl',
        'mno',
        'pqrs',
        'tuv',
        'wxyz'
    ]
    const result = [], path = []
    backtracking(0)
    return result

    function backtracking(index) {
        if(index === digits.length) {
            result.push(path.slice().join(''))
            return
        }
        for(let char of map[digits[index]]) {
            path.push(char)
            backtracking(index+1)
            path.pop()
        }
    }
}

39. 组合总和

js
/**
 * @param {number[]} candidates
 * @param {number} target
 * @return {number[][]}
 */
var combinationSum = function(candidates, target) {
    const result = []
    const path = []
    const backTracking = (candidates, index) => {
        // 循环推出条件
        if(path.reduce((amu, cur) => amu + cur, 0) > target) return
        if(path.reduce((amu, cur) => amu + cur, 0) === target) {
            result.push(path.slice())
            return
        }
        for(let i = index; i < candidates.length; i++) {
            path.push(candidates[i])
            backTracking(candidates, i)
            path.pop()
        }
    }
    backTracking(candidates, 0)
    return result
};

var combinationSum = function(candidates, target) {
    const result = [], path = []
    backtracking(0, 0)
    return result

    function backtracking(start, sum) {
        if(sum === target) {
            result.push(path.slice())
            return
        }
        if(sum > target) {
            return
        }
        for(let i = start; i < candidates.length; i++) {
            path.push(candidates[i])
            backtracking(i, sum + candidates[i])
            path.pop()
        }
    }

};

22. 括号生成

js
/**
 * @param {number} n
 * @return {string[]}
 */
//  暴力枚举所有的括号生成
var generateParenthesis = function(n) {
    const result = [], path = []
    backtracking()
    return result

    function backtracking() {
        if(path.length === n * 2) {
            result.push(path.slice().join(''))
            return
        }
        path.push('(')
        backtracking()
        path.pop()

        path.push(')')
        backtracking()
        path.pop()
    }
};

// 加上剪枝逻辑,生成后剪枝
var generateParenthesis = function(n) {
    const result = [], path = []
    backtracking()
    return result

    function backtracking() {
        // 剪枝左右括号数量不正确情况
        const leftNum = path.filter(item => item === '(').length
        const rightNum = path.filter(item => item === ')').length
        if(leftNum > n) return
        if(rightNum > n) return

        // 剪枝左右括号不闭合情况
        // 所谓左右括号不闭合的情况,就是从错误的状态转移过来,我们定义右括号的数量大于左括号的数量的为错误状态
        if(rightNum > leftNum) return

        if(path.length === n * 2) {
            result.push(path.slice().join(''))
            return
        }
        path.push('(')
        backtracking()
        path.pop()

        path.push(')')
        backtracking()
        path.pop()
    }
};


// 生成前剪枝
var generateParenthesis1 = function(n) {
    const result = [], path = []
    backtracking(0,0)
    return result

    function backtracking(left, right) {
        if(left > n || right > left) return

        if(path.length === n * 2) {
            result.push(path.slice().join(''))
            return
        }
        path.push('(')
        backtracking(left+1, right)
        path.pop()

        path.push(')')
        backtracking(left, right+1)
        path.pop()
    }
};

79. 单词搜索

js
/**
 * @param {character[][]} board
 * @param {string} word
 * @return {boolean}
 */
var exist = function(board, word) {
    const m = board.length
    const n = board[0].length
    // 寻找入口
    const target = word[0]
    const u = Array(m).fill().map(() => Array(n).fill(false))
    for(let i = 0; i < m; i++) {
        for(let j = 0; j < n; j++) {
            if(board[i][j] === target) {
                if(dfs(i, j, 0, u)) return true
            }
        }
    }
    return false

    function dfs(i, j, k, u) {
        if(i >= m || i < 0 || j >= n || j < 0) return false
        if(board[i][j] !== word[k]) return false
        if(u[i][j]) return false
        if(k === word.length - 1) return true


        u[i][j] = true
        const t = dfs(i-1, j, k+1, u)
        const b = dfs(i+1, j, k+1, u)
        const l = dfs(i, j-1, k+1, u)
        const r = dfs(i, j+1, k+1, u)
        // 进行回溯的原因,因为传递的是数组(二维数组也是数组),传参使用的是引用而非复制,所以会更改道原来的u数组(最上面定义那个),所以需要撤销更改。
        u[i][j] = false
        return t || b || l || r
        
    }
};


// 减少多余标记数组 u
var exist = function(board, word) {
    const m = board.length
    const n = board[0].length
    // 寻找入口
    const target = word[0]
    for(let i = 0; i < m; i++) {
        for(let j = 0; j < n; j++) {
            if(board[i][j] === target) {
                if(dfs(i, j, 0)) return true
            }
        }
    }
    return false

    function dfs(i, j, k) {
        if(i >= m || i < 0 || j >= n || j < 0) return false
        if(board[i][j] !== word[k]) return false
        if(k === word.length - 1) return true

        // 修改成特殊值,代表已访问
        const tmp = board[i][j]
        board[i][j] = '#'
        const t = dfs(i-1, j, k+1)
        const b = dfs(i+1, j, k+1)
        const l = dfs(i, j-1, k+1)
        const r = dfs(i, j+1, k+1)
        // 进行回溯的原因,因为传递的是数组(二维数组也是数组),传参使用的是引用而非复制,所以会更改道原来的u数组(最上面定义那个),所以需要撤销更改。
        board[i][j] = tmp
        return t || b || l || r
        
    }
};

// 2. 提前剪枝
var exist = function(board, word) {
    const m = board.length
    const n = board[0].length

    // 2.1 word很长
    if(word.length > m * n) return false

    // 2.2 word中存在board没有的字符
    const wordMap = {}, boradMap = {}
    for(let char of word) wordMap[char] = (wordMap[char] || 0) + 1
    for(let i = 0; i < m; i++) {
        for(let j = 0; j < n; j++) {
            boradMap[board[i][j]] = (boradMap[board[i][j]] || 0) + 1
        }
    }
    for(let char of word) {
        if(!boradMap[char] || boradMap[char] < wordMap[char]) return false
    }

    // 寻找入口
    const target = word[0]
    for(let i = 0; i < m; i++) {
        for(let j = 0; j < n; j++) {
            if(board[i][j] === target) {
                if(dfs(i, j, 0)) return true
            }
        }
    }
    return false

    function dfs(i, j, k) {
        if(i >= m || i < 0 || j >= n || j < 0) return false
        if(board[i][j] !== word[k]) return false
        if(k === word.length - 1) return true

        // 修改成特殊值,代表已访问
        const tmp = board[i][j]
        board[i][j] = '#'
        const t = dfs(i-1, j, k+1)
        const b = dfs(i+1, j, k+1)
        const l = dfs(i, j-1, k+1)
        const r = dfs(i, j+1, k+1)
        // 进行回溯的原因,因为传递的是数组(二维数组也是数组),传参使用的是引用而非复制,所以会更改道原来的u数组(最上面定义那个),所以需要撤销更改。
        board[i][j] = tmp
        return t || b || l || r
        
    }
};

// 3.用方向数组代替冗余代码
var exist = function(board, word) {
    const m = board.length
    const n = board[0].length
    const direction = [[-1, 0], [1, 0], [0, -1], [0, 1]]

    // 2.1 word很长
    if(word.length > m * n) return false

    // 2.2 word中存在board没有的字符
    const wordMap = {}, boradMap = {}
    for(let char of word) wordMap[char] = (wordMap[char] || 0) + 1
    for(let i = 0; i < m; i++) {
        for(let j = 0; j < n; j++) {
            boradMap[board[i][j]] = (boradMap[board[i][j]] || 0) + 1
        }
    }
    for(let char of word) {
        if(!boradMap[char] || boradMap[char] < wordMap[char]) return false
    }

    // 寻找入口
    const target = word[0]
    for(let i = 0; i < m; i++) {
        for(let j = 0; j < n; j++) {
            if(board[i][j] === target) {
                if(dfs(i, j, 0)) return true
            }
        }
    }
    return false

    function dfs(i, j, k) {
        if(i >= m || i < 0 || j >= n || j < 0) return false
        if(board[i][j] !== word[k]) return false
        if(k === word.length - 1) return true

        // 修改成特殊值,代表已访问
        const tmp = board[i][j]
        board[i][j] = '#'
        
        // 3 使用方向数组简化
        let flag = false
        for(let [di, dj] of direction) {
            flag = dfs(i + di, j + dj, k+1) || flag
        }

        // 进行回溯的原因,因为传递的是数组(二维数组也是数组),传参使用的是引用而非复制,所以会更改道原来的u数组(最上面定义那个),所以需要撤销更改。
        board[i][j] = tmp
        return flag
        
    }
};


// 4.递归剪枝,只需找到一条路径
var exist = function(board, word) {
    const m = board.length
    const n = board[0].length
    const direction = [[-1, 0], [1, 0], [0, -1], [0, 1]]

    // 2.1 word很长
    if(word.length > m * n) return false

    // 2.2 word中存在board没有的字符
    const wordMap = {}, boradMap = {}
    for(let char of word) wordMap[char] = (wordMap[char] || 0) + 1
    for(let i = 0; i < m; i++) {
        for(let j = 0; j < n; j++) {
            boradMap[board[i][j]] = (boradMap[board[i][j]] || 0) + 1
        }
    }
    for(let char of word) {
        if(!boradMap[char] || boradMap[char] < wordMap[char]) return false
    }

    // 寻找入口
    const target = word[0]
    for(let i = 0; i < m; i++) {
        for(let j = 0; j < n; j++) {
            if(board[i][j] === target) {
                if(dfs(i, j, 0)) return true
            }
        }
    }
    return false

    function dfs(i, j, k) {
        if(i >= m || i < 0 || j >= n || j < 0) return false
        if(board[i][j] !== word[k]) return false
        if(k === word.length - 1) return true

        // 修改成特殊值,代表已访问
        const tmp = board[i][j]
        board[i][j] = '#'
        
        // 3 使用方向数组简化
        // 4.递归剪枝,只需找到一条路径,所以找到匹配的路径就返回
        let flag = false
        for(let [di, dj] of direction) {
            if(dfs(i + di, j + dj, k+1)) {
                flag = true
                break
            }
        }

        // 进行回溯的原因,因为传递的是数组(二维数组也是数组),传参使用的是引用而非复制,所以会更改道原来的u数组(最上面定义那个),所以需要撤销更改。
        board[i][j] = tmp
        return flag
        
    }
};

131. 分割回文串

js
/**
 * @param {string} s
 * @return {string[][]}
 */
const isPalindrome = (s, l, r) => {
    for (let i = l, j = r; i < j; i++, j--) {
        if(s[i] !== s[j]) return false;
    }
    return true;
}

var partition = function(s) {
    const res = [], path = [], len = s.length;
    backtracking(0);
    return res;

    function backtracking(startIndex) {
        if(startIndex === s.length) {
            res.push(path.slice())
            return
        }
        for(let i = startIndex; i < s.length; i++) {
            if(!isPalindrome(s, startIndex, i)) continue
            path.push(s.slice(startIndex, i + 1))
            backtracking(i + 1)
            path.pop()
        }
    }
};

51. N 皇后

js
/**
 * @param {number} n
 * @return {string[][]}
 */
//  可以通过的代码,下面进行优化
var solveNQueens = function(n) {
    const result = []
    const checkboard = Array(n).fill().map(() => Array(n).fill('.')) // 即是二维的原来的变量path,记录每一个答案
    dfs(0)
    return result

    function dfs(i) {
        // 收集条件:一定是叶子节点,才会放完n个
        if(i === n) {
            result.push(checkboard.slice().map(item => item.join('')))
            return
        }
        // 放入,每一列都要尝试
        for(let j = 0; j < n; j++) {
            if(!isVaild(i, j)) continue
            // 合法,继续回溯
            checkboard[i][j] = 'Q'
            dfs(i+1)
            checkboard[i][j] = '.'
        }
    }

    function isVaild(r, c) {
        // 放入位置为i,j的棋子,棋盘会不会违法。
        // 规定了之前的棋盘都是合法的
        // 开始检查

        // 同一行(不同列)这一个检查没啥用,因为我们的回溯逻辑是每一行只放了一个皇后,就是写着好玩
        for(let j = 0; j < n; j++) {
            if(checkboard[r][j] === 'Q') return false
        }

        // 同一列(不同行)
        for(let i = 0; i < n; i++) {
            if(checkboard[i][c] === 'Q') return false
        }

        // 主对角线方向的斜线
        // 两边同时减k,使得一边为0,然后遍历
        let r1 = r, c1 = c
        let k = Math.min(r1, c1)
        r1 -= k
        c1 -= k
        for(let i = r1, j = c1; i < n && j < n; i++, j++) {
            if(checkboard[i][j] === 'Q' && (i !== r && j !== c)) return false
        }

        // 反对角线方向的斜线
        // i-k, j+k, 使得i为0或者j为n-1,然后遍历,(i++, j--)
        let r2 = r, c2 = c
        let k2 = Math.min(r2, n-1-c)
        r2 -= k2
        c2 += k2
        for(let i = r2, j = c2; i < n && j >= 0; i++, j--) {
            if(checkboard[i][j] === 'Q' && (i !== r && j !== c)) return false
        }

        // 检查完毕,返回true
        return true
    }
};

// 1. 优化 isVaild 函数
var solveNQueens = function(n) {
    const result = []
    const checkboard = Array(n).fill().map(() => Array(n).fill('.')) // 即是二维的原来的变量path,记录每一个答案
    // 1. 优化isVaild: 通过记录被占用列,被占用主对角线,被占用反对角线(副对角线)
    const cols = new Set()
    const diaMain = new Set() // 主对角线
    const diaVice = new Set() // 副对角线
    dfs(0)
    return result

    function dfs(i) {
        // 收集条件:一定是叶子节点,才会放完n个
        if(i === n) {
            result.push(checkboard.slice().map(item => item.join('')))
            return
        }
        // 放入,每一列都要尝试
        for(let j = 0; j < n; j++) {
            // 1. 优化isVaild
            // 每一个对角线,确保向上的方向或者向下的方向没有重复的元素,这样可以叠加起来,不需要重复判断
            if(cols.has(j) || diaMain.has(j - i) || diaVice.has(i + j)) continue
            // 合法,继续回溯
            cols.add(j)
            diaMain.add(j-i)
            diaVice.add(i+j)
            checkboard[i][j] = 'Q'
            dfs(i+1)
            checkboard[i][j] = '.'
            cols.delete(j)
            diaMain.delete(j-i)
            diaVice.delete(i+j)
        }
    }
};

// 2. 优化答案存储
var solveNQueens = function(n) {
    const result = []
    const board = Array(n).fill(-1);  // 用于记录每行皇后所在列号    
    // 1. 优化isVaild: 通过记录被占用列,被占用主对角线,被占用反对角线(副对角线)
    const cols = new Set()
    const diaMain = new Set() // 主对角线
    const diaVice = new Set() // 副对角线
    dfs(0)
    return result

    function dfs(i) {
        // 收集条件:一定是叶子节点,才会放完n个
        if(i === n) {
            result.push(gen(board))
            return
        }
        // 放入,每一列都要尝试
        for(let j = 0; j < n; j++) {
            // 1. 优化isVaild
            // 每一个对角线,确保向上的方向或者向下的方向没有重复的元素,这样可以叠加起来,不需要重复判断
            if(cols.has(j) || diaMain.has(j - i) || diaVice.has(i + j)) continue
            // 合法,继续回溯
            cols.add(j)
            diaMain.add(j-i)
            diaVice.add(i+j)
            board[i] = j
            dfs(i+1)
            board[i] = -1
            cols.delete(j)
            diaMain.delete(j-i)
            diaVice.delete(i+j)
        }
    }

    // 生成最终答案
    function gen(board) {
        return board.map(col => '.'.repeat(col) + 'Q' + '.'.repeat(n - col - 1));
    }
};

var solveNQueens = function(n) {
    const result = []
    const board = Array(n).fill(-1);  // 用于记录每行皇后所在列号    
    // 1. 优化isVaild: 通过记录被占用列,被占用主对角线,被占用反对角线(副对角线)
    const cols = new Set()
    const diaMain = new Set() // 主对角线
    const diaVice = new Set() // 副对角线
    dfs(0)
    return result

    function dfs(i) {
        // 3. 剪枝
        if (cols.size + diaMain.size + diaVice.size < i) return; // 提前终止无效分支
        // 收集条件:一定是叶子节点,才会放完n个
        if(i === n) {
            result.push(gen(board))
            return
        }
        // 放入,每一列都要尝试
        for(let j = 0; j < n; j++) {
            // 1. 优化isVaild
            // 每一个对角线,确保向上的方向或者向下的方向没有重复的元素,这样可以叠加起来,不需要重复判断
            if(cols.has(j) || diaMain.has(j - i) || diaVice.has(i + j)) continue
            // 合法,继续回溯
            cols.add(j)
            diaMain.add(j-i)
            diaVice.add(i+j)
            board[i] = j
            dfs(i+1)
            board[i] = -1
            cols.delete(j)
            diaMain.delete(j-i)
            diaVice.delete(i+j)
        }
    }

    // 生成最终答案
    function gen(board) {
        return board.map(col => '.'.repeat(col) + 'Q' + '.'.repeat(n - col - 1));
    }
};

二分搜索

35. 搜索插入位置

js
/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 */
var searchInsert = function(nums, target) {
    let l = 0, r = nums.length-1, m
    while(l <= r) {
        m = (l + r) / 2 | 0
        if(nums[m] > target) r = m - 1
        else if (nums[m] < target) l = m + 1
        else return m
    }
    return l
};

74. 搜索二维矩阵

js
/**
 * @param {number[][]} matrix
 * @param {number} target
 * @return {boolean}
 */
var searchMatrix = function(matrix, target) {
    const m = matrix.length
    const n = matrix[0].length
    let len = m * n
    let l = 0, r = len - 1
    while(l <= r) {
        const mid = (l + r) / 2 | 0
        // 将m换算成(i, j)
        let i = mid / n | 0
        let j = mid % n
        if(matrix[i][j] < target) {
            l = mid + 1
        } else if (matrix[i][j] > target) {
            r = mid - 1
        } else {
            return true
        }
    }
    return false
};

34. 在排序数组中查找元素的第一个和最后一个位置

js
/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var searchRange = function(nums, target) {
    let l = 0, r = nums.length-1, m
    while(l <= r) {
        m = (l + r) / 2 | 0
        if(nums[m] > target) r = m - 1
        else if (nums[m] < target) l = m + 1
        else break
    }
    console.log(l, r, m)
    // 搜索到这个数字,m在[开始位置,结束位置]
    let p = m, q = m
    if(nums[m] !== target) return [-1, -1]
    while(nums[p] === target) p--
    while(nums[q] === target) q++
    return [p+1, q-1]
};



var searchRange2 = function(nums, target) {
    let lb = getLeftBorder(nums, target)
    let rb = getRightBorder(nums, target)
    console.log(lb, rb)
    if(lb === -1 && rb === -1) return [-1, -1]
    if(rb - lb > 1) return [lb+1, rb-1]
    return [-1, -1]
};

function getLeftBorder(nums, target) {
    let l = 0, r = nums.length-1, m, leftBorder = -1
    while(l <= r) {
        m = (l + r) / 2 | 0
        if(nums[m] >= target) leftBorder = r = m - 1
        else l = m + 1
    }
    return leftBorder
}

function getRightBorder(nums, target) {
    let l = 0, r = nums.length-1, m, rightBorder = -1
    while(l <= r) {
        m = (l + r) / 2 | 0
        if(nums[m] <= target) rightBorder = l = m + 1
        else r = m - 1
    }
    return rightBorder
}

33. 搜索旋转排序数组

js
/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 */
//  两次二分
var search = function(nums, target) {
    let bd = 0
    for(let i = 0; i < nums.length-1; i++) {
        if(nums[i+1] > nums[i]) {

        }  else {
            bd = i+1
            break
        }
    }
    let l = 0, r = bd - 1, l1 = bd, r1 = nums.length-1
    while(l <= r) {
        const m = (l + r) / 2 | 0
        if(nums[m] < target) {
            l = m + 1
        } else if (nums[m] > target) {
            r = m - 1
        } else {
            return m
        }
    }
    
    while(l1 <= r1) {
        const m = (l1 + r1) / 2 | 0
        if(nums[m] < target) {
            l1 = m + 1
        } else if (nums[m] > target) {
            r1 = m - 1
        } else {
            return m
        }
    }
    return -1
};

153. 寻找旋转排序数组中的最小值

js
/**
 * @param {number[]} nums
 * @return {number}
 */
var findMin = function(nums) {
    const n = nums.length
    let l = 0, r = n - 1
    while(l <= r) {
        const m = (l + r) / 2 | 0
        if(nums[m] > nums[n-1]) {
            l = m + 1
        } else if (nums[m] < nums[n-1]) {
            r = m - 1
        } else {
            r = m - 1
        }
    }
    return nums[r+1]
};

4. 寻找两个正序数组的中位数

未完成

20. 有效的括号

js
/**
 * @param {string} s
 * @return {boolean}
 */
var isValid = function(s) {
    let st = []
    const map = {
        '(': ')',
        '{': '}',
        '[': ']'
    }
    for(let i = 0; i < s.length; i++) {
        if(s[i] in map) {
            st.push(s[i])
        } else {
            let el = st.pop()
            if(map[el] === s[i]) continue
            else return false
        }
    }
    return st.length === 0
};

155. 最小栈

js
var MinStack = function() {
    this.stack = []
    this.min = []
};

/** 
 * @param {number} x
 * @return {void}
 */
MinStack.prototype.push = function(x) {
    this.stack.push(x)
    if (!this.min.length) {
        this.min.push(x)
    } else {
        const tempMin = this.min.pop()
        if (x < tempMin) {
            this.min.push(tempMin)
            this.min.push(x)
        } else {
            this.min.push(tempMin)
            this.min.push(tempMin)
        }
    }
};

/**
 * @return {void}
 */
MinStack.prototype.pop = function() {
    this.min.pop()
    return this.stack.pop()
};

/**
 * @return {number}
 */
MinStack.prototype.top = function() {
    const result = this.stack.pop()
    this.stack.push(result)
    return result
};

/**
 * @return {number}
 * 在这里是排序了,但是根据题意,要在常量级时间里面获得最小值,不能总是需要了再去计算
 * 所以要用空间换时间的方法,获得这个最小值。
 * 由于主栈是一个栈,为什么要用一个辅助最小栈来构建?
 * ! 因为这个栈可能会变化,当弹出之后,最小值也是当即算出来
 * ? 方法一:当前计算最小值
 */
// MinStack.prototype.getMin = function() {
//     const stack2 = []
//     let min = this.stack.pop()
//     let element = min
//     stack2.push(element)
//     while (this.stack.length) {
//         element = this.stack.pop()
//         if (element < min) {
//             min = element
//         }
//         stack2.push(element)
//     }
//     while (stack2.length) {
//         this.stack.push(stack2.pop())
//     }
//     return min
// };

MinStack.prototype.getMin = function() {
    const result = this.min.pop()
    this.min.push(result)
    return result
};

394. 字符串解码

js
/**
 * @param {string} s
 * @return {string}
 */
//  多段遍历,一层一层拆
var decodeString = function(s) {
    let num = s.split('[').length - 1
    while(s.includes('[')) {
        // 找到对应的层次
        let count = 0, left = 0, right = 0
        
        left = s.lastIndexOf('[')
        right = s.indexOf(']', left)

        // 开始替换逻辑
        let repeatCount = ''
        let i = left - 1
        while(i >= 0 && /\d/.test(s[i])) {
            repeatCount = s[i] + repeatCount
            i--
        }
        let str = s.slice(left + 1, right)
        str = str.repeat(Number(repeatCount))
        s = s.slice(0, i + 1) + str +s.slice(right+1)
    }
    return s
};



var decodeString = function(s) {
    const st = []
    let num = 0
    let tmp = ''

    for (let char of s) {
        if(!isNaN(char)) {
            // 处理数字累积,兼容多位数字
            num = num * 10 + Number(char)
        } else if (char === '[') {
            // 存储当前块,重置
            st.push([tmp, num])
            tmp = ''
            num = 0
        } else if (char === ']') {
            // 合成当前块
            const [preStr, repeatCount] = st.pop()
            tmp = preStr + tmp.repeat(repeatCount)
        } else {
            tmp += char
        }
    }

    return tmp
};

739. 每日温度

js
/**
 * @param {number[]} temperatures
 * @return {number[]}
 */
var dailyTemperatures = function(temperatures) {
    // 这里需要单调栈,存放的元素是下标
    const result = Array(temperatures.length).fill(0), st = []
    st.push(0)
    for(let i = 1; i < temperatures.length; i++) {
        if(temperatures[i] > temperatures[getStackTop(st)]) {
           while(st.length && temperatures[i] > temperatures[getStackTop(st)]) {
                const pop = st.pop()
                result[pop] = i - pop
           }
        }
        st.push(i)
    }

    return result

    function getStackTop(st) {
        return st[st.length-1]
    }
};


var dailyTemperatures = function(temperatures) {
    // 这里需要单调栈,存放的元素是下标
    const st = [], result = Array(temperatures.length).fill(0)
    st.push(0)
    for(let i = 1; i < temperatures.length; i++) {
        // 维护单调栈单调递减
        while(temperatures[st[st.length-1]] < temperatures[i] && st.length) {
            result[st[st.length-1]] = i - st[st.length-1]
            st.pop() 
        }
        st.push(i)
    }
    return result
};

84. 柱状图中最大的矩形

js
/**
 * @param {number[]} heights
 * @return {number}
 */
var largestRectangleArea = function(heights) {
    let result = 0
    const st = []
    st.push(0)
    heights.unshift(0)
    heights.push(0)
    for(let i = 1; i < heights.length; i++) {
        while(heights[i] < heights[st[st.length-1]]) {
            let mid = st.pop()
            let w = i - st[st.length-1] - 1
            let h = heights[mid]
            result = Math.max(result, w * h)
        }
        st.push(i)
    }
    return result
};


var largestRectangleArea = function(heights) {
    // 使用单调栈解决这个问题
    const st = []
    let result = 0
    st.push(0)
    heights.push(0)
    heights.unshift(0)
    for(let i = 1; i < heights.length; i++) {
        // 维护单调栈单调递增,否则计算答案
        if(heights[st[st.length-1]] > heights[i]) {
            while(heights[st[st.length-1]] > heights[i] && st.length) {
                const node = st.pop()
                const height = heights[node]
                const width = i - st[st.length-1] -1
                result = Math.max(result, height * width)
            }
            st.push(i)
        } else {
            st.push(i)
        }
    }
    return result
};

215. 数组中的第K个最大元素

js
/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number}
 */
var findKthLargest = function(nums, k) {
    nums.sort((a,b) => b-a)
    return nums[k-1]
};


var findKthLargest = function(nums, k) {
    return quickSelect(nums, k)

    function quickSelect(nums, k) {
        let pivot = nums[Math.random() * nums.length | 0]
        const big = [], equal = [], small = []
        for(let num of nums) {
            if(num > pivot) {
                big.push(num)
            } else if (num < pivot) {
                small.push(num)
            } else {
                equal.push(num)
            }
        }
        if(k <= big.length) {
            // 第k大元素在big中,使用递归划分
            return quickSelect(big, k)
        }
        if(k > nums.length - small.length) {
            // 第k大元素在small中,递归
            return quickSelect(small, k - nums.length + small.length)
        }
        // 第k大元素在equal中,就是pivot
        return pivot
    }
    
};

347. 前 K 个高频元素

js
/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number[]}
 */
var topKFrequent = function(nums, k) {
    // hashmap
    const map = new Map()
    const result = []
    for(let i = 0; i < nums.length; i++) {
        map.set(nums[i], (map.get(nums[i]) || 0) + 1)
    }
    const sortMap = Array.from(map).sort((a,b) => b[1] - a[1])
    console.log(sortMap)
    for(let i = 0; i < k; i++) {
        result.push(sortMap[i][0])
    }
    return result
};

295. 数据流的中位数

未完成

贪心

121. 买卖股票的最佳时机

js
/**
 * @param {number[]} prices
 * @return {number}
 */
var maxProfit1 = function(prices) {
    let result = 0
    let low = Infinity
    for(let i = 0; i < prices.length; i++) {
        low = Math.min(low, prices[i])
        result = Math.max(result, prices[i] - low)
    }
    return result
};



var maxProfit = function(prices) {
    const dp = Array(prices.length).fill().map(() => Array(2).fill(-Infinity))
    dp[0][0] = -prices[0]
    dp[0][1] = 0
    for(let i = 1; i < prices.length; i++) {
        for(let j = 0; j < 2; j++) {
            if(j === 1) {
                dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1] + prices[i])
            } else {
                dp[i][j] = Math.max(dp[i-1][j], -prices[i])
            }
        }
    }
    console.log(dp)
    return dp[prices.length - 1][1]
};

55. 跳跃游戏

js
/**
 * @param {number[]} nums
 * @return {boolean}
 */
var canJump = function(nums) {
    const dp = Array(nums.length).fill(false)
    dp[0] = true
    for(let i = 0; i < nums.length; i++) {
        if(!dp[i]) break
        if(dp[i]) {
            for(let j = i + 1; j <= i + nums[i] && j < nums.length; j++) {
                dp[j] = true
            }
        }
    }
    console.log(dp)
    return dp[nums.length-1]
};

45. 跳跃游戏 II

js
/**
 * @param {number[]} nums
 * @return {number}
 */
var jump = function(nums) {
    let jumps = 0;            // 跳跃次数
    let farthest = 0;         // 当前能跳到的最远距离
    let currentEnd = 0;       // 当前跳跃范围的结束点

    for (let i = 0; i < nums.length - 1; i++) {  // 遍历到倒数第二个位置即可
        farthest = Math.max(farthest, i + nums[i]);  // 更新最远距离

        if (i === currentEnd) { // 如果到达当前跳跃范围的边界
            jumps++;            // 增加一次跳跃
            currentEnd = farthest;  // 更新跳跃范围

            if (currentEnd >= nums.length - 1) { // 提前终止
                break;
            }
        }
    }

    return jumps;
};

763. 划分字母区间

js
/**
 * @param {string} s
 * @return {number[]}
 */

var partitionLabels = function(s) {
    const times = {}
    let ans = 0
    let result = []
    const stack = new Set()
    for(let char of s) times[char] = (times[char] || 0) + 1
    for(let char of s) {
        times[char]--
        ans++
        if(times[char]) {
            stack.add(char)
        } else {
            if(stack.has(char)) stack.delete(char)
        }
        if(stack.size === 0) {
            result.push(ans)
            ans = 0
        }
    }
    return result
};

动态规划

70. 爬楼梯

js
/**
 * @param {number} n
 * @return {number}
 */
var climbStairs = function(n) {
    const dp = Array(n+1).fill(0)
    dp[0] = 1
    dp[1] = 1
    for(let i = 2; i <= n; i++) {
        dp[i] = dp[i-1] + dp[i-2]
    }
    return dp[n]
};

118. 杨辉三角

js
/**
 * @param {number} numRows
 * @return {number[][]}
 */
var generate = function(numRows) {
    const result = []
    for(let row = 1; row <= numRows; row++) {
        let rowAns = Array(row).fill(1)
        for(let i = 1; i < row - 1; i++) {
            let last = result[result.length-1]
            rowAns[i] = last[i-1] + last[i]
        }
        result.push(rowAns)
    }
    return result
};

198. 打家劫舍

js
/**
 * @param {number[]} nums
 * @return {number}
 */
var rob1 = function(nums) {
    if(nums.length === 1) return nums[0]
    if(nums.length === 2) return Math.max(nums[0], nums[1])
    const dp = Array(nums.length).fill(0)
    dp[0] = nums[0]
    dp[1] = Math.max(nums[0], nums[1])
    for(let i = 2; i < nums.length; i++) {
        dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1])
    }
    console.log(dp)
    return dp[nums.length - 1]
};


var rob = function(nums) {
    if(nums.length === 1) return nums[0]
    if(nums.length === 2) return Math.max(nums[0], nums[1])
    const dp = Array(nums.length).fill(-1)
    dp[0] = nums[0]
    dp[1] = Math.max(nums[0], nums[1])
    for(let i = 2; i < nums.length; i++) {
        dp[i] = Math.max(dp[i-1], dp[i-2] + nums[i])
    }
    return dp[nums.length - 1] 
};

279. 完全平方数

js
/**
 * @param {number} n
 * @return {number}
 */
var numSquares1 = function(n) {
    // 贪得不对
    if(n === 12) return 3
    const qq = Array(100).fill().map((item, index) => (index + 1) * (index + 1))
    let p = qq.length - 1
    let result = 0
    while(n !== 0) {
        if(n >= qq[p]) {
            n -= qq[p]
            result++
        } else {
            p--
        }
    }
    return result
};

// 完全背包问题
var numSquares = function(n) {
    const qq = []
    for(let k = 1; k * k <= n; k++) {
        qq.push(k * k)
    }
    // 探求每个物品能不能装进背包
    const dp = Array(qq.length + 1).fill().map(() => Array(n + 1).fill(Infinity))
    dp[0][0] = 0
    for(let i = 1; i <= qq.length; i++) {
        for(let j = 0; j <= n; j++) {
            if(j >= qq[i - 1]) {
                dp[i][j] = Math.min(dp[i-1][j], dp[i][j - qq[i-1]] + 1)
            } else {
                dp[i][j] = dp[i-1][j]
            }
        }
    }
    console.log(dp)
    return dp[qq.length][n]
}

322. 零钱兑换

js
/**
 * @param {number[]} coins
 * @param {number} amount
 * @return {number}
 */
var coinChange = function(coins, amount) {
    const dp = Array(coins.length + 1).fill().map(() => Array(amount + 1).fill(Infinity));
    dp[0][0] = 0; // 初始化,凑成金额0的最小硬币数量是0

    // 遍历硬币
    for (let i = 1; i <= coins.length; i++) {
        // 遍历金额
        for (let j = 0; j <= amount; j++) {
            if (j >= coins[i - 1]) {
                // 使用当前硬币
                dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - coins[i - 1]] + 1);
            } else {
                // 不使用当前硬币
                dp[i][j] = dp[i - 1][j];
            }
        }
    }
    console.log(dp)
    // 如果dp[coins.length][amount]是Infinity,说明无法凑出该金额,返回-1
    return dp[coins.length][amount] === Infinity ? -1 : dp[coins.length][amount];
};



var coinChange = function(coins, amount) {
    const dp = Array(coins.length + 1).fill().map(() => Array(amount+1).fill(Infinity))
    dp[0][0] = 0
    for(let i = 1; i <= coins.length; i++) {
        for(let j = 0; j <= amount; j++) {
            if(coins[i-1] <= j) {
                dp[i][j] = Math.min(dp[i-1][j], dp[i][j-coins[i-1]] + 1)
            } else {
                dp[i][j] = dp[i-1][j]
            }
        }
    }
    return dp[coins.length][amount] === Infinity ? -1 : dp[coins.length][amount]
};

139. 单词拆分

js
/**
 * @param {string} s
 * @param {string[]} wordDict
 * @return {boolean}
 */
function wordBreak1(s, wordDict) {
    // 将字典转换为集合,以便快速查找
    const wordSet = new Set(wordDict);
    const memory = Array(s.length).fill(1)

    // 递归函数,用于回溯
    function backtracking(startIndex) {
        if (startIndex >= s.length) {
            return true;
        }
        if (!memory[startIndex]) return memory[startIndex]
        for (let i = startIndex; i < s.length; i++) {
            const word = s.substring(startIndex, i + 1);
            console.log(i, word)
            if (wordSet.has(word) && backtracking(i + 1)) {
                return true;
            }
        }
        memory[startIndex] = false
        return false;
    }

    // 从索引 0 开始回溯
    return backtracking(0);
}

/**
 * @param {string} s
 * @param {string[]} wordDict
 * @return {boolean}
 */
const wordBreak2 = (s, wordDict) => {

    let dp = Array(s.length + 1).fill(false);
    dp[0] = true;

    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] = true
                }
            }
        }
    }

    return dp[s.length];
}

function wordBreak(s, wordDict) {
    const wordSet = new Set(wordDict); // 将字典转换为集合,便于快速查找
    const dp = Array.from({ length: s.length + 1 }, () => Array(s.length + 1).fill(false));
    for (let i = 0; i <= s.length; i++) {
        dp[i][i] = true; // 单个字符总是可以被拼接
    }

    for (let i = 1; i <= s.length; i++) {
        for (let j = 0; j < i; j++) {
            if (dp[j][j] && wordSet.has(s.substring(j + 1, i + 1))) {
                dp[j][i] = true;
            }
        }
    }

    for (let len = 2; len <= s.length; len++) { // 从长度为2的子串开始,逐步增加子串长度
        for (let i = 0; i <= s.length - len; i++) {
            let j = i + len - 1;
            for (let k = i; k < j; k++) {
                if (dp[i][k] && dp[k + 1][j + 1]) {
                    dp[i][j + 1] = true;
                    break;
                }
            }
        }
    }

    return dp[0][s.length]; // 返回整个字符串是否可以被拼接
}

300. 最长递增子序列

js
/**
 * @param {number[]} nums
 * @return {number}
 */
var lengthOfLIS1 = function(nums) {
    const dp = Array(nums.length).fill(1)
    dp[0] = 1
    let max = dp[0]
    for(let i = 1; i < nums.length; i++) {
        for(let j = 0; j < i; j++) {
            if(nums[i] > nums[j]) dp[i] = Math.max(dp[j] + 1, dp[i])
            max = Math.max(max, dp[i])
        }
    }
    return max
};


function lengthOfLIS(arr) {
    const p = arr.slice(); // p数组用于记录每个元素在LIS中的位置
    const result = [0]; // result数组用于存储LIS的索引
    let i, j, u, v, c;
    const len = arr.length; // 输入数组的长度

    for (i = 0; i < len; i++) {
        const arrI = arr[i]; // 当前元素
        j = result[result.length - 1]; // j是result数组的最后一个元素的索引,即当前LIS的最后一个元素的位置

        // 如果当前元素比result中最后一个元素的对应元素大,直接追加到result后面
        if (arr[j] < arrI) {
            p[i] = j; // 更新p数组,记录当前元素在LIS中前一个元素的位置
            result.push(i); // 将当前元素索引加入到result中
            continue; // 继续处理下一个元素
        }

        // 使用二分查找在result中找到第一个不小于arrI的元素的位置
        u = 0;
        v = result.length - 1;
        while (u < v) {
            c = ((u + v) / 2) | 0; // 二分查找的中间索引
            if (arr[result[c]] < arrI) {
                u = c + 1; // 调整上界
            } else {
                v = c; // 调整下界
            }
        }

        // 二分查找结束后,u就是插入位置
        if (arrI < arr[result[u]]) {
            if (u > 0) {
                p[i] = result[u - 1]; // 更新p数组,记录当前元素在LIS中前一个元素的位置
            }
            result[u] = i; // 在合适的位置更新result
        }
    }

    // 从result中构建LIS
    u = result.length;
    v = result[u - 1];
    while (u-- > 0) {
        result[u] = arr[v]; // 用实际的元素值更新result
        v = p[v]; // 移动到LIS中前一个元素的位置
    }

    return result.length; // 返回LIS的长度
}

152. 乘积最大子数组

js
/**
 * @param {number[]} nums
 * @return {number}
 */
var maxProduct = function(nums) {
    const n = nums.length
    let fMax = Array(n)
    let fMin = Array(n)
    fMax[0] = fMin[0] = nums[0]
    let result = fMax[0]
    for(let i = 1; i < n; i++) {
        const x = nums[i]
        fMax[i] = Math.max(fMax[i-1] * x, fMin[i-1] * x, x)
        fMin[i] = Math.min(fMax[i-1] * x, fMin[i-1] * x, x)
        result = Math.max(result, fMax[i])
    }   
    return result
};

416. 分割等和子集

js
/**
 * @param {number[]} nums
 * @return {boolean}
 */
var canPartition = function(nums) {
    const sum = nums.reduce((a,c) => a+c, 0)
    if(sum % 2 === 1) return false
    const target = sum / 2
    const dp = Array(nums.length+1).fill().map(() => Array(target+1).fill(false))
    dp[0][0] = false
    for(let i = 1; i <= nums.length; i++) {
        for(let j = 0; j <= target; j++) {
            if(j === nums[i-1]) dp[i][j] = true
            else if(nums[i-1] < j) {
                dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i-1]]
            } else {
                dp[i][j] = dp[i-1][j]
            }
        }
    }
    console.log(dp)
    return dp[nums.length][target]
};

32. 最长有效括号

js
/**
 * @param {string} s
 * @return {number}
 */
//  直接匹配(只考虑有效括号)
var longestValidParentheses = function(s) {
    const stack = [-1]; // 初始化栈,存储伪索引
    let maxLength = 0;

    for (let i = 0; i < s.length; i++) {
        if (s[i] === '(') {
            stack.push(i); // 左括号入栈
        } else {
            stack.pop(); // 尝试匹配
            if (stack.length === 0) {
                stack.push(i); // 无法匹配,记录断开的位置
            } else {
                maxLength = Math.max(maxLength, i - stack[stack.length - 1]);
            }
        }
    }

    return maxLength;
};


var longestValidParentheses = function(s) {
    const n = s.length;
    if (n === 0) return 0;

    const dp = new Array(n).fill(0); // 初始化 DP 数组
    let maxLength = 0;

    for (let i = 1; i < n; i++) {
        if (s[i] === ')') {
            if (s[i - 1] === '(') {
                dp[i] = (i >= 2 ? dp[i - 2] : 0) + 2;
            } else if (i - dp[i - 1] - 1 >= 0 && s[i - dp[i - 1] - 1] === '(') {
                dp[i] = dp[i - 1] + (i - dp[i - 1] - 2 >= 0 ? dp[i - dp[i - 1] - 2] : 0) + 2;
            }
            maxLength = Math.max(maxLength, dp[i]);
        }
    }

    return maxLength;
};

多维动态规划

62. 不同路径

js
/**
 * @param {number} m
 * @param {number} n
 * @return {number}
 */
var uniquePaths = function(m, n) {
    const dp = Array(m).fill().map(() => Array(n).fill(0))
    for(let i = 0; i < m; i++) dp[i][0] = 1
    for(let j = 0; j < n; j++) dp[0][j] = 1
    for(let i = 1; i < m; i++) {
        for(let j = 1; j < n; j++) {
            dp[i][j] = dp[i][j-1] + dp[i-1][j]
        }
    }
    return dp[m-1][n-1]
};

64. 最小路径和

js
/**
 * @param {number[][]} grid
 * @return {number}
 */
var minPathSum = function(grid) {
    const m = grid.length
    const n = grid[0].length
    const dp = Array(m).fill().map(() => Array(n).fill(0))
    dp[0][0] = grid[0][0]
    for(let i = 1; i < m; i++) dp[i][0] = dp[i-1][0] + grid[i][0] 
    for(let j = 1; j < n; j++) dp[0][j] = dp[0][j-1] + grid[0][j]

    for(let i = 1; i < m; i++) {
        for(let j = 1; j < n; j++) {
            dp[i][j] = Math.min(dp[i-1][j], dp[i][j-1]) + grid[i][j]
        }
    }
    return dp[m-1][n-1]
};

5. 最长回文子串

js
/**
 * @param {string} s
 * @return {string}
 */
//  暴力
var longestPalindrome = function(s) {
    if(s.length <= 1) return s
    let left = 0, right = 1
    let ans = s[0]
    for(; left < s.length; left++) {
        for(right = left + 1; right < s.length; right++) {
            if(isPalindrome(s, left, right) && right - left + 1 > ans.length) ans = s.slice(left, right+1)
        }
    }
    return ans
};

// 1. 暴力,剪枝
var longestPalindrome = function(s) {
    if(s.length <= 1) return s
    let left = 0, right = 1
    let ans = s[0]
    for(; left < s.length; left++) {
        for(right = left + 1; right < s.length; right++) {
            // 1. 剪枝条件1:当前判断的子串已经比答案短
            if(right - left + 1 <= ans.length) continue
            if(isPalindrome(s, left, right)) ans = s.slice(left, right+1)
        }
    }
    return ans
};

// 2. 中心拓展法
var longestPalindrome = function(s) {
    if (s.length <= 1) return s;

    let start = 0; // 最长回文子串的起始位置
    let maxLen = 0; // 最长回文子串的长度

    // 遍历每个字符,尝试扩展回文
    for (let i = 0; i < s.length; i++) {
        // 奇数长度回文
        expandAroundCenter(s, i, i);
        // 偶数长度回文
        expandAroundCenter(s, i, i + 1);
    }

    // 截取最长回文子串并返回
    return s.substring(start, start + maxLen);

    // 扩展中心函数
    function expandAroundCenter(s, left, right) {
        while (left >= 0 && right < s.length && s[left] === s[right]) {
            left--;
            right++;
        }
        // 当前回文长度
        const len = right - left - 1;
        if (len > maxLen) {
            maxLen = len; // 更新最大长度
            start = left + 1; // 更新起始位置
        }
    }
};

// 盲目动态规划
var longestPalindrome = function(s) {
    const n = s.length;
    if (n <= 1) return s;

    // 初始化 DP 数组
    const dp = Array.from({ length: n }, () => Array(n).fill(false));

    let start = 0; // 记录最长回文子串起始位置
    let maxLen = 1; // 记录最长回文子串长度

    // 单个字符一定是回文串
    for (let i = 0; i < n; i++) {
        dp[i][i] = true;
    }

    // 遍历子串的长度
    for (let len = 2; len <= n; len++) {
        for (let i = 0; i <= n - len; i++) {
            const j = i + len - 1; // 子串右端点
            if (s[i] === s[j]) {
                if (len === 2 || dp[i + 1][j - 1]) {
                    dp[i][j] = true;
                    if (len > maxLen) {
                        maxLen = len;
                        start = i;
                    }
                }
            }
        }
    }

    return s.substring(start, start + maxLen);
};



// 中心扩散+动态规划
var longestPalindrome = function(s) {
    if(s.length <= 1) return s
    const len = s.length
    let start = 0
    let end = 0
    let maxLen = 1
    const dp = Array(len).fill().map(() => Array(len).fill(false))

    for(let r = 1; r < len; r++) {
        for(let l = 0; l < r; l++) {
            if(s[l] === s[r] && (r - l <= 2 || dp[l + 1][r - 1])) {
                dp[l][r] = true
                if(r - l + 1 > maxLen) {
                    maxLen = r - l + 1
                    start = l
                    end = r
                }
            }
        }
    }
    return s.slice(start, end + 1)
};


// manacher算法
var longestPalindrome = function(s) {
    if (s.length <= 1) return s;

    // 预处理字符串
    const preprocess = (str) => `^#${str.split('').join('#')}#$`;
    const t = preprocess(s);
    const n = t.length;
    const p = new Array(n).fill(0); // 存储每个位置的回文半径
    let C = 0, R = 0; // 当前最右回文的中心和右边界
    let maxLen = 0, centerIndex = 0;

    for (let i = 1; i < n - 1; i++) {
        // 如果 i 在 R 的范围内,先计算对称点的回文半径
        const mirror = 2 * C - i;
        if (i < R) {
            p[i] = Math.min(R - i, p[mirror]);
        }

        // 尝试扩展以 i 为中心的回文
        while (t[i + p[i] + 1] === t[i - p[i] - 1]) {
            p[i]++;
        }

        // 更新 C 和 R
        if (i + p[i] > R) {
            C = i;
            R = i + p[i];
        }

        // 更新最长回文信息
        if (p[i] > maxLen) {
            maxLen = p[i];
            centerIndex = i;
        }
    }

    // 提取最长回文子串
    const start = (centerIndex - maxLen) / 2; // 转化为原字符串起点
    return s.substring(start, start + maxLen);
};


var isPalindrome = function(s, left, right) {
    while(left < right) {
        if(s[left] !== s[right]) return false
        left++
        right--
    }
    return true
}

1143. 最长公共子序列

js
/**
 * @param {string} text1
 * @param {string} text2
 * @return {number}
 */
//  动态规划
// dp[i][j]表示text1只考虑前i个,text2只考虑前j个,text2与text1的公共子序列的最长长度
var longestCommonSubsequence = function(text1, text2) {
    // 重新排一下长度
    const big = text1.length > text2.length ? text1 : text2
    const small = text1.length > text2.length ? text2 : text1
    const dp = Array(big.length+1).fill().map(() => Array(small.length+1).fill(0))

    for(let i = 1; i <= big.length; i++) {
        for(let j = 1; j <= small.length; j++) {
            if(big[i-1] === small[j-1]) {
                // 一个错误的状态转移
                // dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]) + 1
                // 因为有可能i出现第一个能用的,j出现若干的能用的,只能匹配一次,所以上一个状态是上一个不用j的状态j-1,且i不能被用(上一个i不在的状态i-1)
                dp[i][j] = dp[i-1][j-1] + 1
            } else {
                dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1])
            }
        }
    }
    // console.log(dp)
    return dp[big.length][small.length]
};

72. 编辑距离

js
/**
 * @param {string} word1
 * @param {string} word2
 * @return {number}
 */
//  动态规划:dp[i][j]为将word1[0...i]转换为word2[0...j]所需要的最少操作数
// 状态转移:上一个状态进行操作后变成下一个状态
// 换操作 === 删操作 再 加操作
var minDistance = function(word1, word2) {
    const dp = Array(word1.length+1).fill().map(() => Array(word2.length+1).fill(0))
    
    for(let i = 0; i <= word1.length; i++) dp[i][0] = i
    for(let j = 0; j <= word2.length; j++) dp[0][j] = j

    for(let i = 1; i <= word1.length; i++) {
        for(let j = 1; j <= word2.length; j++) {
            if(word1[i-1] !== word2[j-1]) {
                // 换
                // dp[i][j] = dp[i-1][j-1] + 1
                // 增
                // dp[i][j] = dp[i][j-1] + 1 || dp[i-1][j] + 1
                // 删
                // dp[i][j] = dp[i][j-1] + 1 || dp[i-1][j] + 1
                dp[i][j] = Math.min(dp[i-1][j-1], dp[i][j-1], dp[i-1][j]) + 1
            } else {
                dp[i][j] = dp[i-1][j-1]
            }
        }
    }
    // console.log(dp)
    return dp[word1.length][word2.length]
};

技巧

136. 只出现一次的数字

js
/**
 * @param {number[]} nums
 * @return {number}
 */
//  位运算(异或运算),规定了某些数字出现了两次,那么两次的可以通过位运算消除
var singleNumber = function(nums) {
    let ans = 0
    for(const num of nums) ans ^= num
    return ans
};

169. 多数元素

js
/**
 * @param {number[]} nums
 * @return {number}
 */
var majorityElement = function(nums) {
    let votes = 0
    let ans = 0
    for(const num of nums) {
        if(votes === 0) ans = num
        if(ans === num) votes++
        else votes--
    }
    return ans
};

75. 颜色分类

js
/**
 * @param {number[]} nums
 * @return {void} Do not return anything, modify nums in-place instead.
 */
//  哈希
var sortColors = function(nums) {
    const map = [0,0,0]
    let index = 0
    for(let num of nums) map[num]++
    for(let i = 0; i < map[0]; i++) nums[index++] = 0
    for(let i = 0; i < map[1]; i++) nums[index++] = 1
    for(let i = 0; i < map[2]; i++) nums[index++] = 2
};


var sortColors = function(nums) {
    let zero = 0, two = nums.length - 1;
    let i = 0;

    while (i <= two) {
        if (nums[i] === 0) {
            // 交换当前元素与 zero 指针的元素
            [nums[i], nums[zero]] = [nums[zero], nums[i]];
            zero++;
            i++;
        } else if (nums[i] === 2) {
            // 交换当前元素与 two 指针的元素
            [nums[i], nums[two]] = [nums[two], nums[i]];
            two--;
        } else {
            // 当前元素为 1,直接跳过
            i++;
        }
    }
};

31. 下一个排列

未完成

287. 寻找重复数

js
/**
 * @param {number[]} nums
 * @return {number}
 */
var findDuplicate = function(nums) {
    // 快慢指针
    let slow = nums[0];
    let fast = nums[0];

    // 第一次相遇:寻找环
    do {
        slow = nums[slow];
        fast = nums[nums[fast]];
    } while (slow !== fast);

    // 第二次相遇:确定环的入口
    slow = nums[0];
    while (slow !== fast) {
        slow = nums[slow];
        fast = nums[fast];
    }

    return slow;
};