【算法系列】LeetCode Hot 100

88 阅读49分钟

1. 两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出和为目标值 target 的那两个整数,并返回它们的数组下标。

输入: nums = [2,7,11,15], target = 9
输出: [0,1]
const twoSum = function (nums, target) {
    const map = new Map();
    for (let i = 0, len = nums.length; i < len; i++) {
        const item = nums[i];
        const diffVal = target - item;
        if (map.has(diffVal) && map.get(diffVal) !== i) {
            return [map.get(diffVal), i];
        } else {
            map.set(item, i);
        }
    }
    return [];
};

const arr1 = [2,7,11,15]
const target = 9
twoSum(arr1, target) // [0, 1]

2. 字母异位词分组

给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。

字母异位词 是由重新排列源单词的所有字母得到的一个新单词。

输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出: [["bat"], ["nat","tan"], ["ate","eat","tea"]]
var groupAnagrams = function (strs) {
    const map = new Map();
    for (let k of strs) {
        let arr = Array.from(k);
        arr.sort();
        let key = arr.toString();
        let list = map.get(key) ? map.get(key) : [];
        list.push(k);
        map.set(key, list);
    }
    return Array.from(map.values());
};

3. 移动零

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

请注意 ,必须在不复制数组的情况下原地对数组进行操作。

输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]
var moveZeroes = function (nums) {
    let slowIndex = 0;
    for (let fastIndex = 0; fastIndex < nums.length; fastIndex++) {
        if (nums[fastIndex] !== 0) {
            [nums[slowIndex], nums[fastIndex]] = [nums[fastIndex], nums[slowIndex]];
            slowIndex++;
        }
    }
};

4. 盛最多水的容器

给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。

找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

返回容器可以储存的最大水量。

说明: 你不能倾斜容器。

示例1: image.png

输入: [1,8,6,2,5,4,8,3,7]
输出: 49 
解释: 图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49
var maxArea = function (height) {
    let left = 0;
    let right = height.length - 1;
    let max = 0;
    while (left < right) {
        let currentArea = (right - left) * Math.min(height[left], height[right]);
        max = Math.max(currentArea, max);
        height[left] < height[right] ? left++ : right--;
    }
    return max;
};

5. 三数之和

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请

你返回所有和为 0 且不重复的三元组。

注意: 答案中不可以包含重复的三元组。 示例 1:

输入: nums = [-1,0,1,2,-1,-4]
输出: [[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1][-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。
var threeSum = function (nums) {
    let res = [];
    let len = nums.length;
    if (nums === null || len < 3) {
            return res;
    }
    // 排序
    nums.sort((a, b) => a - b);
    for (let i = 0; i < len; i++) {
        // 如果当前数字大于0,则三数之和一定大于0,所以结束循环
        if (nums[i] > 0) {
            break;
        }
        // nums[i] === nums[i - 1]说明该数字重复,会导致结果重复,所以应该跳过
        if (i > 0 && nums[i] === nums[i - 1]) {
            continue;
        }
        let L = i + 1;
        let R = len - 1;
        while (L < R) {
            const sum = nums[i] + nums[L] + nums[R];
            if (sum === 0) {
                res.push([nums[i], nums[L], nums[R]]);
                // nums[L] == nums[L+1] 则会导致结果重复,应该跳过,L++
                while (L < R && nums[L] === nums[L + 1]) {
                    L++;
                }
                // nums[R] == nums[R−1] 则会导致结果重复,应该跳过,R−−
                while (L < R && nums[R] === nums[R - 1]) {
                    R--;
                }
                L++;
                R--;
            } else if (sum < 0) {
                L++;
            } else if (sum > 0) {
                R--;
            }
        }
    }
    return res;
};

6. 和为 K 的子数组

给你一个整数数组 nums 和一个整数 k ,请你统计并返回 *该数组中和为 k ***的子数组的个数

子数组是数组中元素的连续非空序列。

示例一:
输入: nums = [1,2,3], k = 3
输出: 2

示例二:
输入: nums = [1,1,1], k = 2
输出: 2

var subarraySum = function (nums, k) {
    let map = new Map();
    let res = 0;
    let pre = 0;
    map.set(0, 1);
    for (let num of nums) {
        pre = pre + num;
        if (map.has(pre - k)) {
            res = res + map.get(pre - k);
        }
        if (map.has(pre)) {
            map.set(pre, map.get(pre) + 1);
        } else {
            map.set(pre, 1);
        }
    }
    return res;
};

7. 有效的括号

/**
* 给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。
* 有效字符串需满足:
* 左括号必须用相同类型的右括号闭合。
* 左括号必须以正确的顺序闭合。
* 输入: s = "()[]{}"
* 输出: true
*/
const isValid = function (s) {
    if (!s) {
        throw TypeError('请输入字符串');
    }
    const len = s.length;
    if (len % 2 !== 0) {
        return false;
    }
    s = [...s];
    let stack = [];
    let map = new Map([
        [')', '('],
        [']', '['],
        ['}', '{'],
    ]);
    for (let k of s) {
        if (map.get(k)) {
            if (!stack.length || stack[stack.length - 1] !== map.get(k)) {
                return false;
            }
            stack.pop();
        } else {
            stack.push(k);
        }
    }
    return !stack.length;
};

// 测试
const a = "()[]{}"
const b = "()[[{)}"
isValid(a)
isValid(b)

8. 判断一个字符串是否是回文字符串

/**
* input: racecar
* output: true
*/
function isPalindrome(str) {
    let l = 0, r = str.length - 1;
    
    while (l < r) {   //对撞指针不断判断两边的数字是否相等         
        if (str[l] != str[r]) {
            return false;
        }
        l++;
        r--;
    }
    return true;
}

// 测试
isPalindrome('racecar')

9. 验证回文串

如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后,短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。

字母和数字都属于字母数字字符。

给你一个字符串 s,如果它是 回文串 ,返回 true;否则,返回 false

示例一:
输入: s = "A man, a plan, a canal: Panama"
输出: true
解释: "amanaplanacanalpanama" 是回文串。

示例二:
输入: s = "race a car"
输出: false
解释: "raceacar" 不是回文串。
var isPalindrome = function (s) {
    s = s.replace(/[^0-9a-zA-Z]/g, '').toLowerCase();
    let l = 0;
    let r = s.length - 1;
    while (l < r) {
        if (s[l] !== s[r]) {
                return false;
        }
        l++;
        r--;
    }
    return true;
};

10. 最长回文子串

给你一个字符串 s,找到 s 中最长的回文子串。

如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。

示例一:
输入: s = "babad"
输出: "bab"
解释: "aba" 同样是符合题意的答案。

示例二:
输入: s = "cbbd"
输出: "bb"
var longestPalindrome = function (s) {
    function helper(m, n) {
        while (m >= 0 && n < s.length && s[m] == s[n]) {
            m--;
            n++;
        }
        if (n - m - 1 > res.length) {
            res = s.slice(m + 1, n);
        }
    }
    if (s.length < 2) {
        return s;
    }
    let res = '';
    for (let i = 0; i < s.length; i++) {
        helper(i, i);
        helper(i, i + 1);
    }
    return res;
};

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

给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。

算法的时间复杂度应该为 O(log (m+n)) 。

示例一:
输入: nums1 = [1,3], nums2 = [2]
输出: 2.00000

示例二:
输入: nums1 = [1,2], nums2 = [3,4]
输出: 2.50000
var findMedianSortedArrays = function (nums1, nums2) {
    if (nums1.length > nums2.length) {
        [nums1, nums2] = [nums2, nums1];
    }
    let len1 = nums1.length;
    let len2 = nums2.length;
    let left = 0;
    let right = len1;
    while (left <= right) {
        let i = left + Math.floor((right - left) / 2);
        let j = Math.floor((len1 + len2 + 1) / 2) - i;
        let leftMax1 = i === 0 ? -Infinity : nums1[i - 1];
        let rightMin1 = i === len1 ? Infinity : nums1[i];
        let leftMax2 = j === 0 ? -Infinity : nums2[j - 1];
        let rightMin2 = j === len2 ? Infinity : nums2[j];
        if (leftMax1 <= rightMin2 && rightMin1 >= leftMax2) {
            return (len1 + len2) % 2 === 1
                ? Math.max(leftMax1, leftMax2)
                : (Math.max(leftMax1, leftMax2) + Math.min(rightMin1, rightMin2)) / 2;
        } else if (leftMax1 > rightMin2) {
            right = i - 1;
        } else {
            left = left + 1;
        }
    }
};

12. 最小K个数

设计一个算法,找出数组中最小的k个数。以任意顺序返回这k个数均可。

输入: arr = [1,3,5,7,2,4,6,8], k = 4
输出: [1,2,3,4]
var smallestK = function (arr, k) {
    const randomizeSelect = function (arr, l, r, k) {
        if (l >= r) {
            return;
        }
        const pos = randomPartition(arr, l, r);
        const num = pos - l + 1;
        if (k === num) {
            return;
        } else if (k < num) {
            randomizeSelect(arr, l, pos - 1, k);
        } else {
            randomizeSelect(arr, pos + 1, r, k - num);
        }
    };
    const randomPartition = function (arr, l, r) {
        const i = parseInt(Math.random() * (r - l + 1)) + l;
        swap(arr, r, i);
        return partition(arr, l, r);
    };
    const partition = function (arr, l, r) {
        let i = l - 1;
        const pivot = arr[r];
        for (let j = l; j < r; j++) {
            if (arr[j] <= pivot) {
                i++;
                swap(arr, i, j);
            }
        }
        swap(arr, i + 1, r);
        return i + 1;
    };
    const swap = function (arr, i, j) {
        [arr[i], arr[j]] = [arr[j], arr[i]];
    };
    randomizeSelect(arr, 0, arr.length - 1, k);
    return arr.slice(0, k);
};

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

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

示例一:
输入: [3,2,1,5,6,4]k = 2
输出: 5

示例二:
输入: [3,2,3,1,2,4,5,5,6]k = 4
输出: 4
var findKthLargest = function (nums, k) {
    function partSort(arr, startIndex, endIndex) {
        let pivot = arr[startIndex];
        let resIndex = startIndex;
        for (let i = startIndex; i <= endIndex; i++) {
            if (arr[i] < pivot) {
                resIndex++;
                [arr[resIndex], arr[i]] = [arr[i], arr[resIndex]];
            }
        }
        arr[startIndex] = arr[resIndex];
        arr[resIndex] = pivot;
        return resIndex;
    }
    let len = nums.length;
    let targetIndex = len - k;
    let startIndex = 0;
    let endIndex = len - 1;
    let resIndex = partSort(nums, startIndex, endIndex);
    while (resIndex !== targetIndex) {
        if (resIndex < targetIndex) {
            startIndex = resIndex + 1;
        } else {
            endIndex = resIndex - 1;
        }
        resIndex = partSort(nums, startIndex, endIndex);
    }
    return nums[resIndex];
};

14. 最大子数组和

给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

子数组 是数组中的一个连续部分。

输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6 。
var maxSubArray = function (nums) {
    let pre = 0;
    let res = nums[0];
    for (let k of nums) {
        pre = Math.max(k, pre + k);
        res = Math.max(res, pre);
    }
    return res;
};

// 测试
const nums = [-2,1,-3,4,-1,2,1,-5,4]
maxSubArray(nums) // 6

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

给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。

题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在  32 位 整数范围内。

请 不要使用除法, 且在 O(n) 时间复杂度内完成此题。

输入: nums = [1,2,3,4]
输出: [24,12,8,6]

输入: nums = [-1,1,0,-3,3]
输出: [0,0,9,0,0]
var productExceptSelf = function (nums) {
    const len = nums.length;
    const L = [];
    const R = [];
    const res = [];
    L[0] = 1;
    for (let i = 1; i < len; i++) {
        L[i] = nums[i - 1] * L[i - 1];
    }
    R[len - 1] = 1;
    for (let i = len - 2; i >= 0; i--) {
        R[i] = nums[i + 1] * R[i + 1];
    }
    for (let i = 0; i < len; i++) {
        res[i] = L[i] * R[i];
    }
    return res;
};

16. 乘积最大子数组

给你一个整数数组 nums ,请你找出数组中乘积最大的非空连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。

测试用例的答案是一个 32-位 整数。

子数组 是数组的连续子序列。

示例一:
输入: nums = [2,3,-2,4]
输出: 6

示例二:
输入: nums = [-2,0,-1]
输出: 0
var maxProduct = function (nums) {
    let res = nums[0];
    let prevMin = nums[0];
    let prevMax = nums[0];
    for (let i = 1; i < nums.length; i++) {
        let temp1 = prevMin * nums[i];
        let temp2 = prevMax * nums[i];
        prevMax = Math.max(temp1, temp2, nums[i]);
        prevMin = Math.min(temp1, temp2, nums[i]);
        res = Math.max(res, prevMax);
    }
    return res;
};

17. 合并区间

以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。

输入: intervals = [[1,3],[2,6],[8,10],[15,18]]
输出: [[1,6],[8,10],[15,18]]
解释: 区间 [1,3][2,6] 重叠, 将它们合并为 [1,6].
var merge = function (intervals) {
    let res = [];
    intervals.sort((a, b) => a[0] - b[0]);
    let prev = intervals[0];
    for (let i = 1; i < intervals.length; i++) {
        const cur = intervals[i];
        if (cur[0] <= prev[1]) {
            prev[1] = Math.max(cur[1], prev[1]);
        } else {
            res.push(prev);
            prev = cur;
        }
    }
    res.push(prev);
    return res;
};

18. 合并两个有序数组

/**
 * 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。
 * 输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
 * 输出:[1,2,2,3,5,6]
 */
function merge(nums1, m, nums2, n) {
    let p1 = m - 1;
    let p2 = n - 1;
    let tail = m + n - 1;
    let cur;
    while (p1 >= 0 || p2 >= 0) {
        if (p1 === -1) {
            cur = nums2[p2--];
        } else if (p2 === -1) {
            cur = nums1[p1--];
        } else if (nums1[p1] > nums2[p2]) {
            cur = nums1[p1--];
        } else {
            cur = nums2[p2--];
        }
        nums1[tail--] = cur;
    }
}

let nums1 = [1, 2, 3, 0, 0, 0];
let m = 3;
let nums2 = [2, 5, 6];
let n = 3;
merge(nums1, m, nums2, n);
console.log(nums1);

19. 合并排序的数组

给定两个排序后的数组 A 和 B,其中 A 的末端有足够的缓冲空间容纳 B。 编写一个方法,将 B 合并入 A 并排序。

初始化 A 和 B 的元素数量分别为 m 和 n

A = [1,2,3,0,0,0], m = 3
B = [2,5,6],       n = 3

输出: [1,2,2,3,5,6]
var merge = function (A, m, B, n) {
    let pa = m - 1;
    let pb = n - 1;
    let tail = m + n - 1;
    let cur;
    while (pa >= 0 || pb >= 0) {
        if (pa === -1) {
            cur = B[pb--];
        } else if (pb === -1) {
            cur = A[pa--];
        } else if (A[pa] > B[pb]) {
            cur = A[pa--];
        } else {
            cur = B[pb--];
        }
        A[tail--] = cur;
    }
};

20. 两个数组的交集

给定两个数组 nums1 和 nums2 ,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。

示例一:
输入: nums1 = [1,2,2,1], nums2 = [2,2]
输出: [2]

示例二:
输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出: [9,4]
解释: [4,9] 也是可通过的
var intersection = function (nums1, nums2) {
    const setIntersection = function (set1, set2) {
        if (set1.size > set2.size) {
            return setIntersection(set2, set1);
        }
        let set = new Set();
        for (let k of set1) {
            if (set2.has(k)) {
                set.add(k);
            }
        }
        return [...set];
    };
    const set1 = new Set(nums1);
    const set2 = new Set(nums2);
    return setIntersection(set1, set2);
};

21. 两个数组的交集 II

给你两个整数数组 nums1 和 nums2 ,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可以不考虑输出结果的顺序。

示例一:
输入: nums1 = [1,2,2,1], nums2 = [2,2]
输出: [2,2]

示例二:
输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出: [4,9]
// 方法一
var intersect = function (nums1, nums2) {
    const res = [];
    const map = {};
    for (let k of nums1) {
        if (map[k]) {
            map[k]++;
        } else {
            map[k] = 1;
        }
    }
    for (let k of nums2) {
        if (map[k] > 0) {
            res.push(k);
            map[k]--;
        }
    }
    return res;
};

// 方法二
var intersect = function (nums1, nums2) {
    nums1.sort((a, b) => a - b);
    nums2.sort((a, b) => a - b);
    const res = [];
    let p1 = 0;
    let p2 = 0;
    while (p1 < nums1.length && p2 < nums2.length) {
        if (nums1[p1] > nums2[p2]) {
            p2++;
        } else if (nums1[p1] < nums2[p2]) {
            p1++;
        } else {
            res.push(nums1[p1]);
            p1++;
            p2++;
        }
    }
    return res;
};

22. 求两个数组的差集

// 方法一:
var differentSet = function (nums1, nums2) {
    return nums1.filter(item => !nums2.includes(item));
};

// 方法二:
var differentSet = function (nums1, nums2) {
    let set1 = new Set(nums1);
    return nums1.filter(item => !set2.has(item));
};

// 方法三:
var differentSet = function (nums1, nums2) {
    let set1 = new Set(nums1);
    let set2 = new Set(nums2);
    const res = [];
    for (let k of set1) {
        if (!set2.has(k)) {
                res.push(k);
        }
    }
    return res;
};

23. 求两个数组的补集

var completeSet = function (nums1, nums2) {
    const set1 = new Set(nums1);
    const set2 = new Set(nums2);
    return [...nums1.filter(item => !set2.has(item)), ...nums2.filter(item => !set1.has(item))];
};

24. 快速排序

var quickSort = function (arr) {
    if (arr.length <= 1) {
        return arr;
    }
    let left = [];
    let right = [];
    let midIndex = Math.floor(arr.length / 2);
    let mid = arr.splice(midIndex, 1)[0];
    for (let i = 0; i < arr.length; i++) {
        const cur = arr[i];
        if (arr[i] < mid) {
            left.push(cur);
        } else {
            right.push(cur);
        }
    }
    return [...quickSort(left), mid, ...quickSort(right)];
};

25. 冒泡排序

var bubbleSort = function (arr = []) {
    const len = arr.length;
    if (!Array.isArray(arr) || len < 2) {
            return arr;
    }
    for (let i = 0; i < len - 1; i++) {
        for (let j = 0; j < len - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                var temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
    return arr;
};

26. 插入排序

function insertSort(arr) {
    const len = arr.length;
    for (let i = 1; i < len; i++) {
        let prevIndex = i - 1;
        let cur = arr[i];
        while (prevIndex >= 0 && arr[prevIndex] > cur) {
            arr[prevIndex + 1] = arr[prevIndex];
            prevIndex--;
        }
        arr[prevIndex + 1] = cur;
    }
    return arr;
}

27. 选择排序

function selectionSort(arr) {
    const len = arr.length;
    for (let i = 0; i < len - 1; i++) {
        for (let j = i + 1; j < len; j++) {
            if (arr[i] > arr[j]) {
                const temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
        }
    }
    return arr;
}

28. 删除有序数组中的重复项 II

给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使得出现次数超过两次的元素只出现两次 ,返回删除后数组的新长度。

不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

说明:

为什么返回数值是整数,但输出的答案是数组呢?

请注意,输入数组是以**「引用」**方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。

你可以想象内部操作如下:

// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝
int len = removeDuplicates(nums);

// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。
for (int i = 0; i < len; i++) {
    print(nums[i]);
}
示例一:
输入: nums = [1,1,1,2,2,3]
输出: 5, nums = [1,1,2,2,3]
解释: 函数应返回新长度 length = 5, 并且原数组的前五个元素被修改为 1, 1, 2, 2, 3。 不需要考虑数组中超出新长度后面的元素。

示例二:
输入: nums = [0,0,1,1,1,1,2,3,3]
输出: 7, nums = [0,0,1,1,2,3,3]
解释: 函数应返回新长度 length = 7, 并且原数组的前五个元素被修改为 0, 0, 1, 1, 2, 3, 3。不需要考虑数组中超出新长度后面的元素。
var removeDuplicates = function (nums) {
    const len = nums.length;
    if (len <= 2) {
        return len;
    }
    let slow = 2;
    let fast = 2;
    while (fast < len) {
        if (nums[slow - 2] !== nums[fast]) {
            nums[slow] = nums[fast];
            slow++;
        }
        fast++;
    }
    return slow;
};

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

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
    // 定义无重复长度
    let res = 0;
    let map = new Map();
    // 定义左指针
    let left = 0;
    let len = s.length;
    // i为右侧滑动指针
    for (let i = 0; i < len; i++) {
        // 如果字符中有重复的,并且右侧指针的索引>左侧指针索引
        if (map.has(s[i]) && map.get(s[i]) >= left) {
            // 那么左侧指针索引进一位
            left = map.get(s[i]) + 1;
        }
        res = Math.max(res, i - left + 1);
        map.set(s[i], res);
    }
    return res;

30. 最小覆盖子串

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。 注意:

  • 对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
  • 如果 s 中存在这样的子串,我们保证它是唯一的答案。
示例一:
输入: s = "ADOBECODEBANC", t = "ABC"
输出: "BANC"
解释: 最小覆盖子串 "BANC" 包含来自字符串 t 的 'A''B''C'。

示例二:
输入: s = "a", t = "a"
输出: "a"
解释: 整个字符串 s 是最小覆盖子串。
var minWindow = function (s, t) {
    let minLen = s.length + 1;
    let start = s.length;
    let map = {};
    let missingType = 0;
    for (let k of t) {
        if (!map[k]) {
            missingType++;
            map[k] = 1;
        } else {
            map[k]++;
        }
    }
    let l = 0;
    for (let r = 0; r < s.length; r++) {
        const rightChar = s[r];
        if (map[rightChar] !== undefined) {
            map[rightChar]--;
        }
        if (map[rightChar] === 0) {
            missingType--;
        }
        while (missingType === 0) {
            if (r - l + 1 < minLen) {
                minLen = r - l + 1;
                start = l;
            }
            let leftChar = s[l];
            if (map[leftChar] !== undefined) {
                map[leftChar]++;
            }
            if (map[leftChar] > 0) {
                missingType++;
            }
            l++;
        }
    }
    if (start === s.length) {
        return '';
    }
    return s.substring(start, start + minLen);
};

31. 最长连续序列

给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。

请你设计并实现时间复杂度为 O(n) 的算法解决此问题。

输入: nums = [100,4,200,1,3,2]
输出: 4
解释: 最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。
var longestConsecutive = function (nums) {
    let num_set = new Set();
    for (let num of nums) {
        num_set.add(num);
    }
    let res = 0;
    for (const num of num_set) {
        if (!num_set.has(num - 1)) {
            let currentNum = num;
            let currentSteak = 1;
            while (num_set.has(currentNum + 1)) {
                currentNum = currentNum + 1;
                currentSteak = currentSteak + 1;
            }
            res = Math.max(currentSteak, res);
        }
    }
    return res;
};

32. 最长递增子序列

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

示例一:
输入: nums = [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长递增子序列是 [2,3,7,101],因此长度为 4 。

示例二:
输入: nums = [0,1,0,3,2,3]
输出: 4

示例三:
输入: nums = [7,7,7,7,7,7,7]
输出: 1
var lengthOfLIS = function (nums) {
    let dp = new Array(nums.length).fill(1);
    for (let i = 0; i <= nums.length; i++) {
        for (let j = 0; j < i; j++) {
            if (nums[i] > nums[j]) {
                dp[i] = Math.max(dp[i], dp[j] + 1);
            }
        }
    }
    return Math.max(...dp);
};

33. 最长公共子序列

给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。

一个字符串的 子序列 **是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。

  • 例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。

两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。

示例一:
输入: text1 = "abcde", text2 = "ace" 
输出: 3  
解释: 最长公共子序列是 "ace" ,它的长度为 3 。

示例二:
输入: text1 = "abc", text2 = "abc"
输出: 3
解释: 最长公共子序列是 "abc" ,它的长度为 3 。

示例三:
输入: text1 = "abc", text2 = "def"
输出: 0
解释: 两个字符串没有公共子序列,返回 0 。
var longestCommonSubsequence = function (text1, text2) {
    const len1 = text1.length;
    const len2 = text2.length;
    const dp = new Array(len1 + 1).fill(0).map(() => new Array(len2 + 1).fill(0));
    for (let i = 1; i <= len1; i++) {
        const cur1 = text1[i - 1];
        for (let j = 1; j <= len2; j++) {
            const cur2 = text2[j - 1];
            if (cur1 === cur2) {
                dp[i][j] = dp[i - 1][j - 1] + 1;
            } else {
                dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
            }
        }
    }
    return dp[len1][len2];
};

34. 轮转数组

给定一个整数数组 nums,将数组中的元素向右轮转 k **个位置,其中 k **是非负数。

示例一:
输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右轮转 1 步: [7,1,2,3,4,5,6]
向右轮转 2 步: [6,7,1,2,3,4,5]
向右轮转 3 步: [5,6,7,1,2,3,4]

示例二:
输入: nums = [-1,-100,3,99], k = 2
输出: [3,99,-1,-100]
解释: 
向右轮转 1 步: [99,-1,-100,3]
向右轮转 2 步: [3,99,-1,-100]
const reverse = function (nums, start, end) {
    while (start < end) {
        let temp = nums[start];
        nums[start] = nums[end];
        nums[end] = temp;
        start += 1;
        end -= 1;
    }
};
var rotate = function (nums, k) {
    const len = nums.length;
    k = k % len;
    reverse(nums, 0, len - 1);
    reverse(nums, 0, k - 1);
    reverse(nums, k, len - 1);
};

35. 搜索插入位置

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

请必须使用时间复杂度为 O(log n) 的算法。

示例一:
输入: nums = [1,3,5,6], target = 5
输出: 2

示例二:
输入: nums = [1,3,5,6], target = 2
输出: 1

示例三:
输入: nums = [1,3,5,6], target = 7
输出: 4
var searchInsert = function (nums, target) {
    let n = nums.length;
    let left = 0;
    let right = n - 1;
    let res = n;
    while (left <= right) {
        const mid = left + Math.floor((right - left) / 2);
        if (nums[mid] < target) {
            left = mid + 1;
        } else {
            res = mid;
            right = mid - 1;
        }
    }
    return res;
};

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

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]

你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

示例一:
输入: nums = [5,7,7,8,8,10], target = 8
输出: [3,4]

示例二:
输入: nums = [5,7,7,8,8,10], target = 6
输出: [-1,-1]

示例三:
输入: nums = [], target = 0
输出: [-1,-1]
var searchRange = function (nums, target) {
    const binarySearch = function (nums, target, lower) {
        let len = nums.length;
        let res = len;
        let left = 0;
        let right = len - 1;
        while (left <= right) {
            let mid = Math.floor((left + right) / 2);
            if (nums[mid] > target || (nums[mid] >= target && lower)) {
                right = mid - 1;
                res = mid;
            } else {
                left = mid + 1;
            }
        }
        return res;
    };
    let res = [-1, -1];
    let leftIndex = binarySearch(nums, target, true);
    let rightIndex = binarySearch(nums, target, false) - 1;
    if (
        leftIndex <= rightIndex &&
        rightIndex < nums.length &&
        nums[leftIndex] == target &&
        nums[rightIndex] == target
    ) {
        res = [leftIndex, rightIndex];
    }
    return res;
};

37. 搜索旋转排序数组

整数数组 nums 按升序排列,数组中的值 互不相同 。

在传递给函数之前,nums 在预先未知的某个下标 k0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。

给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。

你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。

// 示例一
输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4

// 示例二
输入: nums = [4,5,6,7,0,1,2], target = 3
输出: -1
var search = function (nums, target) {
    if (nums.length === 0) {
        return -1;
    }
    let left = 0;
    let right = nums.length - 1;
    while (left <= right) {
        let mid = left + Math.floor((right - left) / 2);
        let midVal = nums[mid];
        if (midVal === target) {
            return mid;
        }
        let leftVal = nums[left];
        let rightVal = nums[right];
        if (midVal >= leftVal) {
            if (target >= leftVal && target < midVal) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
    } else {
            if (target <= rightVal && target > midVal) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
    }
    return -1;
};

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

已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到:

  • 若旋转 4 次,则可以得到 [4,5,6,7,0,1,2]
  • 若旋转 7 次,则可以得到 [0,1,2,4,5,6,7]

注意,数组 [a[0], a[1], a[2], ..., a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]] 。

给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。

你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。

示例一:
输入: nums = [3,4,5,1,2]
输出: 1

示例二:
输入: nums = [4,5,6,7,0,1,2]
输出: 0
var findMin = function (nums) {
    let left = 0;
    let right = nums.length - 1;
    while (left < right) {
        let mid = left + Math.floor((right - left) / 2);
        if (nums[mid] < nums[right]) {
            right = mid;
        } else {
            left = mid + 1;
        }
    }
    return nums[left];
};

39. 整数反转

给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。

如果反转后整数超过 32 位的有符号整数的范围 [−231,  231 − 1] ,就返回 0。

假设环境不允许存储 64 位整数(有符号或无符号)。

示例一:
输入: x = 123
输出: 321

示例二:
输入: x = -123
输出: -321

示例三:
输入: x = 120
输出: 21
var reverse = function (x) {
    let res = 0;
    while (x) {
        res = res * 10 + (x % 10);
        if (res > Math.pow(2, 31) - 1 || res < Math.pow(-2, 31)) {
            return 0;
        }
        x = parseInt(x / 10);
    }
    return res;
};

40. 给数字添加千分位分隔符

123456.89,三位一区分,就是写成123456.78

41. 千位分隔数

给你一个整数 n,请你每隔三位添加点(即 "." 符号)作为千位分隔符,并将结果以字符串格式返回。

示例一:
输入: n = 987
输出: "987"

示例二:
输入: n = 123456789
输出: "123.456.789"

示例三:
输入: n = 1234
输出: "1.234"
var thousandSeparator = function (n) {
    if (n === 0) {
        return '0';
    }
    let res = '';
    let count = 0;
    while (n) {
        let cur = n % 10;
        n = parseInt(n / 10);
        res = res + String(cur);
        count++;
        if (count % 3 === 0 && n > 0) {
            res = res + '.';
        }
    }
    return res.split('').reverse().join('');
};

42. 相交链表

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。

图示两个链表在节点 c1 开始相交

题目数据 保证 整个链式结构中不存在环。

注意,函数返回结果后,链表必须 保持其原始结构 。

var getIntersectionNode = function (headA, headB) {
    if (headA == null || headB == null) {
        return null;
    }
    let pA = headA;
    let pB = headB;
    while (pA !== pB) {
        pA = pA === null ? headB : pA.next;
        pB = pB === null ? headA : pB.next;
    }
    return pA;
};
// 方法二
var getIntersectionNode = function (headA, headB) {
    let set = new Set();
    let temp = headA;
    while (temp !== null) {
        set.add(temp);
        temp = temp.next;
    }
    temp = headB;
    while (temp !== null) {
        if (set.has(temp)) {
            return temp;
        }
        temp = temp.next;
    }
    return null;
};

43. 合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

输入: l1 = [1,2,4], l2 = [1,3,4]
输出: [1,1,2,3,4,4]
var mergeTwoLists = function (list1, list2) {
    if (list1 == null) {
        return list2;
    } else if (list2 == null) {
        return list1;
    } else if (list1.val < list2.val) {
        list1.next = mergeTwoLists(list1.next, list2);
        return list1;
    } else {
        list2.next = mergeTwoLists(list1, list2.next);
        return list2;
    }
};

// 测试
const list1 = [1,2,4]
const list2 = [1,3,4]
mergeTwoLists(list1, list2)

44. 反转链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

输入: head = [1,2,3,4,5]
输出: [5,4,3,2,1]
var reverseList = function (head) {
    let prev = null;
    let cur = head;
    while (cur) {
        let next = cur.next;
        cur.next = prev;
        prev = cur;
        cur = next;
    }
    return prev;
};

45. 反转链表 II

给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。

输入: head = [1,2,3,4,5], left = 2, right = 4
输出: [1,4,3,2,5]
var reverseBetween = function (head, left, right) {
    let dummyNode = new ListNode(-1);
    dummyNode.next = head;
    let pre = dummyNode;
    for (let i = 0; i < left - 1; i++) {
        pre = pre.next;
    }
    let cur = pre.next;
    for (let i = 0; i < right - left; i++) {
        let next = cur.next;
        cur.next = next.next;
        next.next = pre.next;
        pre.next = next;
    }
    return dummyNode.next;
};

46. 反转链表,每 k 个值反转一次

47.  LRU 缓存

请你设计并实现一个满足  LRU (最近最少使用) 缓存 约束的数据结构。

实现 LRUCache 类:

  • LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存
  • int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
  • void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。

函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。

输入
["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
输出
[null, null, null, 1, null, -1, null, -1, 3, 4]

解释
LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // 缓存是 {1=1}
lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
lRUCache.get(1);    // 返回 1
lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
lRUCache.get(2);    // 返回 -1 (未找到)
lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
lRUCache.get(1);    // 返回 -1 (未找到)
lRUCache.get(3);    // 返回 3
lRUCache.get(4);    // 返回 4
var LRUCache = function (capacity) {
    this.map = new Map();
    this.capacity = capacity;
};
LRUCache.prototype.get = function (key) {
    if (this.map.has(key)) {
        let temp = this.map.get(key);
        this.map.delete(key);
        this.map.set(key, temp);
        return temp;
    }
    return -1;
};
LRUCache.prototype.put = function (key, value) {
    if (this.map.has(key)) {
        this.map.delete(key);
    }
    this.map.set(key, value);
    if (this.map.size > this.capacity) {
        this.map.delete(this.map.keys().next().value);
    }
};

48. 环形链表

给你一个链表的头节点 head ,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。

如果链表中存在环 ,则返回 true 。 否则,返回 false 。

输入: head = [3,2,0,-4], pos = 1
输出: true
解释: 链表中有一个环,其尾部连接到第二个节点。
var hasCycle = function (head) {
    if(!head || head == null){
        return false
    }
    let slow = head;
    let fast = head.next;
    while (fast && fast.next) {
        if (slow === fast) {
            return true;
        }
        slow = slow.next;
        fast = fast.next.next;
    }
    return false;
};

49. 环形链表 II

给定一个链表的头节点  head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

不允许修改 链表。

输入: head = [3,2,0,-4], pos = 1
输出: 返回索引为 1 的链表节点
解释: 链表中有一个环,其尾部连接到第二个节点。
var detectCycle = function (head) {
    let set = new Set();
    while (head !== null) {
        if (set.has(head)) {
            return head;
        }
        set.add(head);
        head = head.next;
    }
    return null;
};

50. 回文链表

给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。

示例一
输入: head = [1,2,2,1]
输出: true

示例二
输入: head = [1,2]
输出: false
var isPalindrome = function (head) {
    let vals = [];
    while (head !== null) {
        vals.push(head.val);
        head = head.next;
    }
    for (let i = 0, j = vals.length - 1; i < j; i++, j--) {
        if (vals[i] !== vals[j]) {
            return false;
        }
    }
    return true;
};

51. 两数相加

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

输入: l1 = [2,4,3], l2 = [5,6,4]
输出: [7,0,8]
解释: 342 + 465 = 807.

输入: l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
输出: [8,9,9,9,0,0,0,1]
var addTwoNumbers = function (l1, l2) {
    let head = null;
    let tail = null;
    let carray = 0;
    while (l1 || l2) {
        let n1 = l1 ? l1.val : 0;
        let n2 = l2 ? l2.val : 0;
        const sum = n1 + n2 + carray;
        if (!head) {
            head = tail = new ListNode(sum % 10);
        } else {
            tail.next = new ListNode(sum % 10);
            tail = tail.next;
        }
        carray = parseInt(sum / 10);
        l1 = l1 ? l1.next : null;
        l2 = l2 ? l2.next : null;
        if (carray > 0) {
            tail.next = new ListNode(carray);
        }
    }
    return head;
};

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

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

输入: head = [1,2,3,4,5], n = 2
输出: [1,2,3,5]
var removeNthFromEnd = function (head, n) {
    let preHead = new ListNode(0);
    preHead.next = head;
    let fast = preHead;
    let slow = preHead;
    while (n--) {
        fast = fast.next;
    }
    while (fast && fast.next) {
        fast = fast.next;
        slow = slow.next;
    }
    slow.next = slow.next.next;
    return preHead.next;
};

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

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

输入: head = [1,2,3,4]
输出: [2,1,4,3]
// 方式一 - 递归
var swapPairs = function (head) {
    if (head == null || head.next == null) {
        return head;
    }
    const newHead = head.next;
    head.next = swapPairs(newHead.next);
    newHead.next = head;
    return newHead;
};

// 方式二 - 迭代
var swapPairs = function (head) {
    const preHead = new ListNode(0);
    preHead.next = head;
    let temp = preHead;
    while (temp.next !== null && temp.next.next !== null) {
        let node1 = temp.next;
        let node2 = temp.next.next;
        temp.next = node2;
        node1.next = node2.next;
        node2.next = node1;
        temp = node1;
    }
    return preHead.next;
};

54. 复制带随机指针的链表

给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。

构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点

例如,如果原链表中有 X 和 Y 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 x 和 y ,同样有 x.random --> y 。

返回复制链表的头节点。

用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:

  • val:一个表示 Node.val 的整数。
  • random_index:随机指针指向的节点索引(范围从 0 到 n-1);如果不指向任何节点,则为  null 。

你的代码  接受原链表的头节点 head 作为传入参数。

输入: head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出: [[7,null],[13,0],[11,4],[10,2],[1,0]]
var copyRandomList = function (head, map = new Map()) {
    if (head == null) {
        return head;
    }
    if (!map.has(head)) {
        map.set(head, { val: head.val });
        Object.assign(
            map.get(head),
            { next: copyRandomList(head.next, map) },
            { random: copyRandomList(head.random, map) }
        );
    }
    return map.get(head);
};

55. 删除排序链表中的重复元素

给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。

示例一:
输入: head = [1,1,2]
输出: [1,2]

示例二:
输入: head = [1,1,2,3,3]
输出: [1,2,3]
var deleteDuplicates = function (head) {
    if (head == null) {
        return head;
    }
    let cur = head;
    while (cur.next) {
        if (cur.next.val === cur.val) {
            cur.next = cur.next.next;
        } else {
            cur = cur.next;
        }
    }
    return head;
};

56. 删除排序链表中的重复元素 II

示例一:
输入: head = [1,1,1,2,3]
输出: [2,3]

示例二:
输入: head = [1,2,3,3,4,4,5]
输出: [1,2,5]
var deleteDuplicates = function (head) {
    if (head === null) {
        return head;
    }
    const dummyHead = new ListNode();
    dummyHead.next = head;
    let cur = dummyHead;
    while (cur.next && cur.next.next) {
        if (cur.next.val === cur.next.next.val) {
            let x = cur.next.val;
            while (cur.next && cur.next.val === x) {
                cur.next = cur.next.next;
            }
        } else {
            cur = cur.next;
        }
    }
    return dummyHead.next;
};

57. 手写二叉树查找

58. 二叉树前序遍历

// 递归
var epilogueTraversal = function(root) {
    const res = [];
    function inner(root) {
        if (!root) {
            return;
        }
        res.push(root.val);
        inner(root.left);
        inner(root.right);
    }
    inner(root);
    return res;
};

// 迭代
var preorderTraversal = function (root) {
    let res = [];
    let stack = [];
    if (root) {
        stack.push(root);
    }
    while (stack.length) {
        const cur = stack.pop();
        res.push(cur.val);
        if (cur.right !== null) {
            stack.push(cur.right);
        }
        if (cur.left !== null) {
            stack.push(cur.left);
        }
    }
    return res;
};

59. 二叉树中序遍历

/**
* 给定一个二叉树的根节点 `root` ,返回它的中序遍历 。
* 二叉树的中序遍历:按照访问左子树——根节点——右子树的方式遍历这棵树,而在访问左子树或者右子树的时候我们按照同样的方式遍历,直到遍历完整棵树。
* 输入: root = [1,null,2,3]
* 输出: [1,3,2]
*/
// 方法一:递归
var inorderTraversal = function(root) {
    const res = [];
    function inner(root) {
        if (!root) {
            return;
        }
        inner(root.left);
        res.push(root.val);
        inner(root.right);
    }
    inner(root);
    return res;
};

// 方法二: 迭代
var inorderTraversal = function(root) {
    const res = [];
    const stack = [];
    while (root || stack.length) {
        while (root) {
            stack.push(root);
            root = root.left;
        }
        root = stack.pop();
        res.push(root.val);
        root = root.right;
    }
    return res;
};

60. 二叉树后序遍历

var epilogueTraversal = function(root) {
    const res = [];
    function inner(root) {
        if (!root) {
            return;
        }
        inner(root.left);
        inner(root.right);
        res.push(root.val);
    }
    inner(root);
    return res;
};

61. 二叉树的层序遍历

给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。

输入: root = [3,9,20,null,null,15,7]
输出: [[3],[9,20],[15,7]]
var levelOrder = function (root) {
    let res = [];
    if (root == null) {
        return res;
    }
    let queue = [root];
    while (queue.length > 0) {
        let currentLevelSize = queue.length;
        res.push([]);
        for (let i = 0; i < currentLevelSize; i++) {
            let node = queue.shift();
            const { val, left, right } = node;
            res[res.length - 1].push(val);
            if (left) {
                queue.push(left);
            }
            if (right) {
                queue.push(right);
            }
        }
    }
    return res;
};

62. 对称二叉树

给你一个二叉树的根节点 root , 检查它是否轴对称。

输入: root = [1,2,2,3,4,4,3]
输出: true

输入: root = [1,2,2,null,3,null,3]
输出: false
// 方法一 - 递归
var isSymmetric = function(root) {
    //使用递归遍历左右子树 递归三部曲 
    // 1. 确定递归的参数 root.left root.right和返回值true false
    const compareFun = function (left, right) {
        // 2. 确定终止条件 空的情况
        if ((left === null && right !== null) || (left !== null && right === null)) {
            return false;
        } else if (left === null && right === null) {
            return true;
        } else if (left.val !== right.val) {
            return false;
        }
        // 3. 确定单层递归逻辑
        let outSide = compareFun(left.left, right.right);
        let inSide = compareFun(left.right, right.left);
        return outSide && inSide;
    };
    if (root === null) {
        return true;
    }
    return compareFun(root.left, root.right);
};

// 方法二-迭代(用队列实现)
var isSymmetric = function (root) {
    if (root == null) {
        return true;
    }
    let queue = [];
    queue.push(root.left);
    queue.push(root.right);
    while (queue.length) {
        const left = queue.shift();
        const right = queue.shift();
        if (left === null && right === null) {
            continue;
        }
        if ((left === null && right !== null) || (left !== null && right === null) || left.val !== right.val) {
            return false;
        }
        queue.push(left.left);
        queue.push(right.right);
        queue.push(left.right);
        queue.push(right.left);
    }
    return true;
};

// 方法三-迭代(用栈实现)
var isSymmetric = function (root) {
    if (root == null) {
        return true;
    }
    let stack = [];
    stack.push(root.left);
    stack.push(root.right);
    while (stack.length) {
        const left = stack.pop();
        const right = stack.pop();
        if (left === null && right === null) {
            continue;
        }
        if ( ( left === null && right !== null ) || ( left !== null && right === null ) || left.val !== right.val ) {
            return false
        }
        stack.push( left.left )
        stack.push( right.right )
        stack.push( left.right )
        stack.push( right.left )
    }
    return true
};

63. 二叉树的最大深度

给定一个二叉树 root ,返回其最大深度。

二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。

输入: root = [3,9,20,null,null,15,7]
输出: 3
// 方法一
var maxDepth = function(root) {
    if (root == null) {
        return 0;
    }
    const leftDepth = maxDepth(root.left);
    const rightDepth = maxDepth(root.right);
    return Math.max(leftDepth, rightDepth) + 1;
};

// 方法二
var maxDepth = function(root) {
    if (root === null) {
        return 0;
    }
    let depth = 0;
    const queue = [root];
    while (queue.length) {
        let size = queue.length;
        depth++;
        while (size--) {
            const cur = queue.shift();
            if (cur.left) {
                queue.push(cur.left);
            }
            if (cur.right) {
                queue.push(cur.right);
            }
        }
    }
    return depth;
};

64. 翻转二叉树

给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。

输入: root = [4,2,7,1,3,6,9]
输出: [4,7,2,9,6,3,1]
// 方法一
var invertTree = function (root) {
    if (root == null) {
        return null;
    }
    let left = invertTree(root.left);
    let right = invertTree(root.right);
    root.left = right;
    root.right = left;
    return root;
};

// 方法二
var invertTree = function (root) {
    if (root == null) {
        return null;
    }
    let queue = [root];
    while (queue.length) {
        let cur = queue.shift();
        [cur.left, cur.right] = [cur.right, cur.left];
        if (cur.left) {
            queue.push(cur.left);
        }
        if (cur.right) {
            queue.push(cur.right);
        }
    }
    return root;
};

65. 二叉树的直径

给你一棵二叉树的根节点,返回该树的 直径 。

二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 root 。

两节点之间路径的 长度 由它们之间边数表示。

输入: root = [1,2,3,4,5]
输出: 3
解释: 3 ,取路径 [4,2,1,3][5,2,1,3] 的长度。
var diameterOfBinaryTree = function (root) {
    let res = 0;
    const depth = function (rootNode) {
        if (rootNode == null) {
            return 0;
        }
        const { left, right } = rootNode;
        // 递归,获取左子树的深度
        const L = depth(left);
        // 递归,获取右子树的深度
        const R = depth(right);
        res = Math.max(res, L + R);
        return Math.max(L, R) + 1;
    };
    depth(root);
    return res;
};

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

给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。

高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。

输入: nums = [-10,-3,0,5,9]
输出: [0,-3,9,-10,null,5]
// 方法一 - 递归
var sortedArrayToBST = function (nums) {
    const buildTree = function (arr, left, right) {
        while (left > right) {
            return null;
        }
        const mid = Math.floor(left + (right - left) / 2);
        const root = new TreeNode(arr[mid]);
        root.left = buildTree(arr, left, mid - 1);
        root.right = buildTree(arr, mid + 1, right);
        return root;
    };
    return buildTree(nums, 0, nums.length - 1);
};


// 方法二 - 迭代
var sortedArrayToBST = function (nums) {
    if (nums.length === 0) {
        return null;
    }
    let root = new TreeNode(0);
    let nodeQueue = [root];
    let leftQueue = [0];
    let rightQueue = [nums.length - 1];
    while (nodeQueue.length) {
        let curNode = nodeQueue.pop();
        let left = leftQueue.pop();
        let right = rightQueue.pop();
        let mid = left + Math.floor((right - left) / 2);
        curNode.val = nums[mid];
        if (left <= mid - 1) {
            curNode.left = new TreeNode(0);
            nodeQueue.push(curNode.left);
            leftQueue.push(left);
            rightQueue.push(mid - 1);
        }
        if (right >= mid + 1) {
            curNode.right = new TreeNode(0);
            nodeQueue.push(curNode.right);
            leftQueue.push(mid + 1);
            rightQueue.push(right);
        }
    }
    return root;
};

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

二叉树中的 路径 被定义为一条节点序列,序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。

路径和 是路径中各节点值的总和。

给你一个二叉树的根节点 root ,返回其 最大路径和 。

输入: root = [1,2,3]
输出: 6
解释: 最优路径是 2 -> 1 -> 3 ,路径和为 2 + 1 + 3 = 6
/**
 * 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)
 * }
 */

var maxPathSum = function (root) {
    let maxSum = Number.MIN_SAFE_INTEGER;
    const maxGain = root => {
        if (root == null) {
            return 0;
        }
        const { left, right, val } = root;
        const leftSum = maxGain(left);
        const rightSum = maxGain(right);
        const innerSum = leftSum + rightSum + val;
        maxSum = Math.max(maxSum, innerSum);
        const outSum = Math.max(0, leftSum, rightSum) + val;
        return outSum < 0 ? 0 : outSum;
    };
    maxGain(root);
    return maxSum;
};

68. 验证二叉搜索树

给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。

有效 二叉搜索树定义如下:

  • 节点的左子树只包含 小于 当前节点的数。
  • 节点的右子树只包含 大于 当前节点的数。
  • 所有左子树和右子树自身必须也是二叉搜索树。
// 示例一
输入: root = [2,1,3]
输出: true

// 示例二
输入: root = [5,1,4,null,null,3,6]
输出: false
var isValidBST = function ( root ) {
    const checkBST = function ( root, lower, upper ) {
        if ( root == null ) {
                return true;
        }
        const {val, left, right} = root
        if ( val <= lower || val >= upper ) {
            return false
        }
        return checkBST(left, lower, val) && checkBST(right, val, upper)
    }
    return checkBST(root, -Infinity, Infinity)
}

69. 最小栈

输入:
["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],[-2],[0],[-3],[],[],[],[]]

输出:
[null,null,null,null,-3,null,0,-2]

解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin();   --> 返回 -3.
minStack.pop();
minStack.top();      --> 返回 0.
minStack.getMin();   --> 返回 -2.
var MinStack = function () {
    this._stack = [];
    this._minStack = [Infinity];
};
MinStack.prototype.push = function (val) {
    this._stack.push(val);
    this._minStack.push(Math.min(this._minStack[this._minStack.length - 1], val));
};
MinStack.prototype.pop = function () {
    this._stack.pop();
    this._minStack.pop();
};
MinStack.prototype.top = function () {
    return this._stack[this._stack.length - 1];
};
MinStack.prototype.getMin = function () {
    return this._minStack[this._minStack.length - 1];
};

70. 字符串解码

给定一个经过编码的字符串,返回它解码后的字符串。

编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。

你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。

此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。

示例一:
输入: s = "3[a]2[bc]"
输出: "aaabcbc"

示例二:
输入: s = "2[abc]3[cd]ef"
输出: "abcabccdcdcdef"

示例三:
输入: s = "3[a2[c]]"
输出: "accaccacc"
var decodeString = function (s) {
    let numStack = [];
    let strStack = [];
    let num = 0;
    let res = '';
    for (const k of s) {
        if (!isNaN(k)) {
            num = num * 10 + Number(k);
        } else if (k === '[') {
            strStack.push(res);
            res = '';
            numStack.push(num);
            num = 0;
        } else if (k === ']') {
            const repeatTime = numStack.pop();
            res = strStack.pop() + res.repeat(repeatTime);
        } else {
            res = res + k;
        }
    }
    return res;
};

71. 每日温度

给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0 来代替。

示例一:
输入: temperatures = [73,74,75,71,69,72,76,73]
输出: [1,1,4,2,1,1,0,0]

示例二:
输入: temperatures = [30,40,50,60]
输出: [1,1,1,0]
var dailyTemperatures = function (arr) {
    const res = new Array(arr.length).fill(0);
    const stack = [];
    for (let i = arr.length - 1; i >= 0; i--) {
        while (stack.length > 0 && arr[i] >= arr[stack[stack.length - 1]]) {
            stack.pop();
        }
        if (stack.length) {
            res[i] = stack[stack.length - 1] - i;
        }
        stack.push(i);
    }
    return res;
};

72. 柱状图中最大的矩形

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。

求在该柱状图中,能够勾勒出来的矩形的最大面积。

image.png

输入: heights = [2,1,5,6,2,3]
输出: 10
var largestRectangleArea = function (heights) {
    let maxArea = 0;
    let stack = [];
    heights = [0, ...heights, 0];
    for (let i = 0; i < heights.length; i++) {
        while (heights[i] < heights[stack[stack.length - 1]]) {
            const stackIndexTop = stack.pop();
            maxArea = Math.max(maxArea, heights[stackIndexTop] * (i - stack[stack.length - 1] - 1));
        }
        stack.push(i);
    }
    return maxArea;
};

73. 跳跃游戏

给你一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。

判断你是否能够到达最后一个下标,如果可以,返回 true ;否则,返回 false 。

示例一:
输入: nums = [2,3,1,1,4]
输出: true
解释: 可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。

示例二:
输入: nums = [3,2,1,0,4]
输出: false
解释: 无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。
var canJump = function (nums) {
    let len = nums.length;
    let end = len - 1;
    for (let i = len - 2; i >= 0; i--) {
        if (end - i <= nums[i]) {
                end = i;
        }
    }
    return end === 0;
};

74. 跳跃游戏 II

给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]

每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说,如果你在 nums[i] 处,你可以跳转到任意 nums[i + j] 处:

  • 0 <= j <= nums[i] 
  • i + j < n

返回到达 nums[n - 1] 的最小跳跃次数。生成的测试用例可以到达 nums[n - 1]

输入: nums = [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。
     从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。
var jump = function (nums) {
    let curIndex = 0;
    let nextIndex = 0;
    let steps = 0;
    for (let i = 0; i < nums.length - 1; i++) {
        nextIndex = Math.max(nextIndex, nums[i] + i);
        if (i === curIndex) {
            curIndex = nextIndex;
            steps++;
        }
    }
    return steps;
};

75. 买卖股票的最佳时机

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。

示例一
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
     注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。

示例二
输入: prices = [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
var maxProfit = function (prices) {
    let low = Infinity;
    let result = 0;
    for (let i = 0; i < prices.length; i++) {
        low = Math.min(low, prices[i]);
        result = Math.max(result, prices[i] - low);
    }
    return result;
};

76. 判断输入字符串是否为合法的IP地址

77. 有一个url数组和一个fetch请求方法,写一个方法,控制最大请求数为3个

array = [url1, url2..]
fetch(url).then();

execute( array, limit=3) {
}

78. 实现一个数组包含三个promise,使其按顺序执行

79. 爬楼梯/青蛙跳台阶

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

输入: n = 2
输出: 2
解释: 有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶
// 方法一
var climbStairs = function(n) {
    if ( n === 1 ) {
        return 1
    }
    if ( n === 2 ) {
        return 2
    }
    return climbStairs(n - 1) + climbStairs(n - 2)
};

// 方法二
var climbStairs = function(n) {
    let p = 0;
    let q = 0;
    let res = 1;
    for ( let i = 1; i <= n; i++ ) {
        p = q
        q = res
        res = p + q
    }
    return res
};

// 方法三
var climbStairs = function (n) {
    let dp = [];
    dp[0] = 1;
    dp[1] = 1;
    for (let i = 2; i <= n; i++) {
        dp[i] = dp[i - 1] + dp[i - 2];
    }
    return dp[n];
};

// 测试
climbStairs(3)

80. 打家劫舍

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

示例一:
输入: [1,2,3,1]
输出: 4
解释: 偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
     偷窃到的最高金额 = 1 + 3 = 4 。

示例二:
输入: [2,7,9,3,1]
输出: 12
解释: 偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
     偷窃到的最高金额 = 2 + 9 + 1 = 12
var rob = function (nums) {
    let len = nums.length;
    if (len === 0) {
            return 0;
    }
    let dp = new Array(len + 1);
    dp[0] = 0;
    dp[1] = nums[0];
    for (let i = 2; i <= len; i++) {
        dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i - 1]);
    }
    return dp[len];
};

81. 打家劫舍 II

你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。

给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。

示例一:
输入: nums = [2,3,2]
输出: 3
解释: 你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。

示例二:
输入: nums = [1,2,3,1]
输出: 4
解释: 你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。
     偷窃到的最高金额 = 1 + 3 = 4
var rob = function (nums) {
    const rob1 = function (nums) {
        const len = nums.length;
        if (len === 0) {
            return 0;
        }
        let dp = new Array(len + 1);
        dp[0] = 0;
        dp[1] = nums[0];
        for (let i = 2; i <= len; i++) {
            dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i - 1]);
        }
        return dp[len];
    };
    const len = nums.length;
    return Math.max(nums[0] + rob1(nums.slice(2, len - 1)), rob1(nums.slice(1)));
};

82. 零钱兑换

给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。

计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。

你可以认为每种硬币的数量是无限的。

示例一:
输入: coins = [1, 2, 5], amount = 11
输出: 3 
解释: 11 = 5 + 5 + 1

示例二:
输入: coins = [2], amount = 3
输出: -1

示例三:
输入: coins = [1], amount = 0
输出: 0
var coinChange = function (coins, amount) {
    let dp = new Array(amount + 1).fill(Infinity);
    dp[0] = 0;
    for (let k of coins) {
        for (let i = k; i <= amount; i++) {
            dp[i] = Math.min(dp[i], dp[i - k] + 1);
        }
    }
    return dp[amount] === Infinity ? -1 : dp[amount];
};

83. 单词拆分

给你一个字符串 s 和一个字符串列表 wordDict 作为字典。请你判断是否可以利用字典中出现的单词拼接出 s 。

注意: 不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。

示例一:
输入: s = "leetcode", wordDict = ["leet", "code"]
输出: true
解释: 返回 true 因为 "leetcode" 可以由 "leet""code" 拼接成。

示例二:
输入: s = "applepenapple", wordDict = ["apple", "pen"]
输出: true
解释: 返回 true 因为 "applepenapple" 可以由 "apple" "pen" "apple" 拼接成。注意,你可以重复使用字典中的单词。

示例三:
输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
输出: false
var wordBreak = function (s, wordDict) {
    let dp = new 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];
};

84. 滑动窗口最大值

给你一个整数数组 nums,有一个大小为 k **的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回 滑动窗口中的最大值

输入: nums = [1,3,-1,-3,5,3,6,7], k = 3
输出: [3,3,5,5,6,7]
解释:
滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7      5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7
var maxSlidingWindow = function (nums, k) {
    let queue = [];
    let res = [];
    for (let i = 0; i < nums.length; i++) {
        while (queue.length > 0 && nums[i] > nums[queue[queue.length - 1]]) {
            queue.pop();
        }
        queue.push(i);
        let j = i - k + 1;
        if (j >= 0) {
            if (queue[0] < j) {
                queue.shift();
            }
            res.push(nums[queue[0]]);
        }
    }
    return res;
};

85. 编辑距离

给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数  。

你可以对一个单词进行如下三种操作:

  • 插入一个字符
  • 删除一个字符
  • 替换一个字符
示例一:
输入: word1 = "horse", word2 = "ros"
输出: 3
解释:
horse -> rorse (将 'h' 替换为 'r')
rorse -> rose (删除 'r')
rose -> ros (删除 'e')


示例二:
输入: word1 = "intention", word2 = "execution"
输出: 5
解释:
intention -> inention (删除 't')
inention -> enention (将 'i' 替换为 'e')
enention -> exention (将 'n' 替换为 'x')
exention -> exection (将 'n' 替换为 'c')
exection -> execution (插入 'u')
var minDistance = function (word1, word2) {
    const len1 = word1.length;
    const len2 = word2.length;
    const dp = new Array(len1 + 1).fill(0).map(() => new Array(len2 + 1).fill(0));
    for (let i = 1; i <= len1; i++) {
        dp[i][0] = i;
    }
    for (let j = 1; j <= len2; j++) {
        dp[0][j] = j;
    }
    for (let i = 1; i <= len1; i++) {
        const cur1 = word1[i - 1];
        for (let j = 1; j <= len2; j++) {
            const cur2 = word2[j - 1];
            if (cur1 === cur2) {
                dp[i][j] = dp[i - 1][j - 1];
            } else {
                dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]) + 1;
            }
        }
    }
    return dp[len1][len2];
};

86. 最小路径和

给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

说明: 每次只能向下或者向右移动一步。

image.png

示例一:
输入: grid = [[1,3,1],[1,5,1],[4,2,1]]
输出: 7
解释: 因为路径 13111 的总和最小。

示例二:
输入: grid = [[1,2,3],[4,5,6]]
输出: 12
var minPathSum = function (grid) {
    let m = grid.length;
    let n = grid[0].length;
    const dp = new Array(m + 1).fill(0).map(() => new Array(n + 1).fill(0));
    dp[0][0] = grid[0][0];
    for (let i = 0; i < m; i++) {
        for (let j = 0; j < n; j++) {
            if (i !== 0 && j == 0) {
                dp[i][j] = grid[i][j] + dp[i - 1][j];
            } else if (i == 0 && j !== 0) {
                dp[i][j] = grid[i][j] + dp[i][j - 1];
            } else if (i !== 0 && j !== 0) {
                dp[i][j] = grid[i][j] + Math.min(dp[i - 1][j], dp[i][j - 1]);
            }
        }
    }
    return dp[m - 1][n - 1];
};

87. 最长公共前缀

编写一个函数来查找字符串数组中的最长公共前缀。

如果不存在公共前缀,返回空字符串 ""

var longestCommonPrefix = function (strs) {
    if (strs.length === 0) {
            return '';
    }
    let res = strs[0];
    for (let k of strs) {
        for (let i = 0; i < res.length; i++) {
            if (k[i] !== res[i]) {
                res = res.slice(0, i);
                break;
            }
        }
    }
    return res;
};

88. O(1) 时间插入、删除和获取随机元素

实现RandomizedSet 类:

  • RandomizedSet() 初始化 RandomizedSet 对象
  • bool insert(int val) 当元素 val 不存在时,向集合中插入该项,并返回 true ;否则,返回 false 。
  • bool remove(int val) 当元素 val 存在时,从集合中移除该项,并返回 true ;否则,返回 false 。
  • int getRandom() 随机返回现有集合中的一项(测试用例保证调用此方法时集合中至少存在一个元素)。每个元素应该有 相同的概率 被返回。

你必须实现类的所有函数,并满足每个函数的 平均 时间复杂度为 O(1) 。

输入
["RandomizedSet", "insert", "remove", "insert", "getRandom", "remove", "insert", "getRandom"]
[[], [1], [2], [2], [], [1], [2], []]
输出
[null, true, false, true, 2, true, false, 2]

解释
RandomizedSet randomizedSet = new RandomizedSet();
randomizedSet.insert(1); // 向集合中插入 1 。返回 true 表示 1 被成功地插入。
randomizedSet.remove(2); // 返回 false ,表示集合中不存在 2 。
randomizedSet.insert(2); // 向集合中插入 2 。返回 true 。集合现在包含 [1,2] 。
randomizedSet.getRandom(); // getRandom 应随机返回 1 或 2 。
randomizedSet.remove(1); // 从集合中移除 1 ,返回 true 。集合现在包含 [2] 。
randomizedSet.insert(2); // 2 已在集合中,所以返回 false 。
randomizedSet.getRandom(); // 由于 2 是集合中唯一的数字,getRandom 总是返回 2 。

var RandomizedSet = function () {
    this.nums = [];
    this.map = new Map();
};

/**
 * @param {number} val
 * @return {boolean}
 */
RandomizedSet.prototype.insert = function (val) {
    if (this.map.has(val)) {
            return false;
    }
    const index = this.nums.length;
    this.nums.push(val);
    this.map.set(val, index);
    return true;
};

/**
 * @param {number} val
 * @return {boolean}
 */
RandomizedSet.prototype.remove = function (val) {
    if (!this.map.has(val)) {
        return false;
    }
    const index = this.map.get(val);
    this.nums[index] = this.nums[this.nums.length - 1];
    this.map.set(this.nums[index], index);
    this.nums.pop();
    this.map.delete(val);
    return true;
};

/**
 * @return {number}
 */
RandomizedSet.prototype.getRandom = function () {
    const index = Math.floor(Math.random() * this.nums.length);
    return this.nums[index];
};

/**
 * Your RandomizedSet object will be instantiated and called as such:
 * var obj = new RandomizedSet()
 * var param_1 = obj.insert(val)
 * var param_2 = obj.remove(val)
 * var param_3 = obj.getRandom()
 */