LeetCode Hot 100 (双指针)

133 阅读5分钟

双指针

移动零

思路

利用快慢指针法

  • 快指针用来寻找数组中的非零元素
  • 慢指针用来指向非零元素需要更新到的位置的下标

代码

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

盛最多水的容器

思路

盛雨水.jpg

  1. 初始化: 双指针 l, r 分别指向水槽的左右两端;
  2. 循环收窄: 直至双指针相遇时跳出;
    1. 更新面积最大值 max ;
    2. 选定两板高度中的短板,向中间收窄一格 (或收窄至板高不小于当前短板);
  3. 返回值: 返回面积最大值 max 即可;

代码

  1. 完整版

    /**
     * @param {number[]} height
     * @return {number}
     */
    var maxArea = function (height) {
        let max = 0, l = 0, r = height.length - 1
        while (l < r) {
            let hl = height[l]
            let hr = height[r]
            let h = hl > hr ? hr : hl
            let m = h * (r-l)
            max = max > m ? max : m
            if (hl > hr) {
                while (height[--r] < hr) { }
            } else {
                while (height[++l] < hl) { }
            }
        }
        return max
    };
    
  2. 缩减版

    /**
     * @param {number[]} height
     * @return {number}
     */
    var maxArea = function (height) {
        let max = 0, l = 0, r = height.length - 1
        while (l < r) {
            max = height[l] < height[r] ?
                Math.max(max, (r - l) * height[l++]) :
                Math.max(max, (r - l) * height[r--])
        }
        return max
    };
    

三数之和

思路

  1. 将数组按升序排序
  2. 从左侧开始,选定一个值为定值 ,右侧进行求解,获取与其相加为 0 的两个值
  3. 类似于快排,定义首和尾
  4. 首尾与定值相加
    • 等于 0,记录这三个值
    • 小于 0,首部右移
    • 大于 0,尾部左移
  5. 定值右移,重复该步骤
  6. 剪枝及去重:
    • 剪枝:定值的位置应与数组末尾至少相隔一个数;定值若大于 0 ,则直接返回结果
    • 去重:定值、首尾值都不应重复,若重复则直接跳过

注意

Array.prototype.sort() 方法就地对数组的元素进行排序。

  • 默认排序是将元素转换为字符串,然后按照它们的 UTF-16 码元值升序排序。在数值排序中,9 出现在 80,但因为数字会被转换为字符串,在 Unicode 顺序中 “80” 排在 “9”。所有的 undefined 元素都会被排序到数组的末尾

  • 如果提供了 compareFn,所有 undefined 的数组元素都会按照比较函数的返回值进行排序(所有的 undefined 元素都会被排序到数组的末尾,并且不调用 compareFn

    compareFn(a, b) 返回值排序顺序
    > 0ab 后,如 [b, a]
    < 0ab 前,如 [a, b]
    === 0保持 ab 原来的顺序

代码

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var threeSum = function (nums) {
    let res = []
    nums.sort((a, b) => a - b)
    for (let s = 0; s < nums.length - 2; s++) {
        if (nums[s] > 0) return res
        if (s > 0 && nums[s] === nums[s - 1]) continue
        let l = s + 1, r = nums.length - 1
        while (l < r) {
            let sum = nums[s] + nums[l] + nums[r]
            if (sum > 0)
                r--
            else if (sum < 0)
                l++
            else {
                res.push([nums[s], nums[l], nums[r]])
                while (nums[l] === nums[++l] && l < r) { }
                while (l < r && nums[r] === nums[--r]) { }
            }
        }
    }
    return res
};

接雨水

思路

  1. 按列计算

    接雨水c.png

    分别计算除左右两端之外所有列的接水量并求和:

    • 每列接水量为其左侧最高柱与右侧最高柱中的较低柱与该列高度差。(注意:当差小于 0 时,此列接水量也应为 0,即只有差大于 0 时,才需要进行求和操作)

    • 每列的侧最高柱高度为一列的侧最高柱与一列相比较高者的高度,

      每列的侧最高柱高度为一列的侧最高柱与一列相比较高者的高度。

    故,

    1. 从前往后遍历数组,求取每列柱子的左侧最高柱并记入 lMax 数组
    2. 从后往前遍历数组,求取每列柱子的右侧最高柱并记入 rMax 数组
    3. 遍历柱子数组,进行接水量计算及求和操作
    4. 返回结果
  2. 单调栈(按行计算)

    接雨水r.png

    1. 使用单调递减栈(原因:当当前柱子高于前一个柱子时,则可开始计算接水量)

      栈顶元素即接水柱子 cur ,栈顶第二个元素即左边柱子 l,当前柱子即右边柱子 r

    2. 初始化:将第一个柱子入栈

    3. 遍历剩余柱子:

      • 若栈不空,且当前柱子 i (r)高于栈顶柱子,则:(可开始计算接水量)

        1. 将栈顶柱子记录并出栈

        2. 判断此时栈是否为空,

          若空,则不做处理(即无左柱子无法接水);

          若不空,则计算接水量 sum:

          width = r - l -1

          heigth = Math.min(height[l], height[r]) - height[cur]

          sum = width * height

        3. 若仍满足条件,则一直重复以上两步

      • 将当前柱子 i 进栈

    4. 返回结果

代码

  1. 双指针

    /**
     * @param {number[]} height
     * @return {number}
     */
    var trap = function (height) {
        if (height.length < 3) return 0
        let res = 0, lMax = [], rMax = []
        lMax[0] = 0, rMax[height.length - 1] = 0
        // 记录每个柱子的左边柱子最大高度
        for (let i = 1; i < height.length - 1; i++)
            lMax[i] = Math.max(lMax[i - 1], height[i - 1])
        // 记录每个柱子的右边柱子最大高度
        for (let i = height.length - 2; i > 0; i--)
            rMax[i] = Math.max(rMax[i + 1], height[i + 1])
        // 求和
        for (let i = 1; i < height.length - 1; i++) {
            let h = Math.min(lMax[i], rMax[i]) - height[i]
            if (h > 0)
                res += h
        }
    
        return res
    };
    
  2. 单调栈

    /**
     * @param {number[]} height
     * @return {number}
     */
    var trap = function (height) {
        let res = 0, s = [0]
        for (let i = 1; i < height.length; i++) {
            while (s.length > 0 && height[i] > height[s[s.length - 1]]) {
                let cur = s[s.length - 1]
                s.pop()
                if (s.length < 1) break
                res += ((Math.min(height[s[s.length - 1]], height[i]) - height[cur]) * (i - s[s.length - 1] - 1))
            }
            s.push(i)
        }
        return res
    };