双指针-刷题记录-ING

本文介绍了在编程中如何使用快慢指针和双指针优化的方法解决移除元素和移动零问题,通过对比两种算法并提供代码示例,展示了在处理数组操作中的高效策略。
摘要由CSDN通过智能技术生成

基础题

移除元素移动零

两道题的基本思路基本一致,但移除元素移动零少了需要按原有顺序排列的限制,因此可以进行优化。

方法一:快慢指针

双指针,快指针用于找到所有非val的值,慢指针用于记录。
快慢指针同时往右走,用快指针指向的值替换慢指针指向的值。如果数组内没有val,意味着快慢指针始终指向同一个值直到遍历结束。一旦出现一个val,快指针会继续遍历,找到第一个不为val的值,但慢指针还是指向为val的值。此时用快指针的值替换慢指针的值,快慢指针继续遍历,直至快指针指向num.length。此时快指针已经处理过所有的值,慢指针指向第一个为val的值,即非val数组的长度是慢指针此时的索引值。

/**
 * @param {number[]} nums
 * @param {number} val
 * @return {number}
 */
var removeElement = function(nums, val) { 
    let slow = 0, fast = 0
    while (fast < nums.length) {
        if(nums[fast] === val) {
            fast++
            continue
        }
        const o_s = nums[slow], o_f = nums[fast]
        nums[slow++] = o_f
        nums[fast++] = o_s
    }
    return slow
};

方法二:双指针优化

即leetcode官方优化版,思路一致,写法不太一样。核心都是把右边的非val值和左边的val值替换,当两个指针相遇时右指针右侧都是val值,左指针左侧都是非val值。区别和二分法中左/右边界的法一法二一致。right取nums.length - 1和nums.length的差别导致跳出时的条件和返回的值不一样。

由于移动元素不需要保证移动后数组元素的相对顺序,可以考虑分别从两边遍历数组,左边的指针如果指向非val值直接继续遍历,如果指向val值则和右边第一个非val值调换后继续遍历。

需要注意的点是最后的返回值取什么。虽然最后一次循环无法判断是走入了哪一个分支,可以假设当right===left时指向的是val值,right--后跳出,指向最后一个非val值,返回right+1(或left)。反之如果指向的是非val值,仍返回right+1(或left)。

分析:最后返回的值对应的是数组调整后第一个指向val值的索引,最后跳出时一定有left > right,而right右边的值都是val,left左边的值都是非val,因而left跳出时指向的一定是第一个非val值。

var removeElement = function(nums, val) {
    let left = 0, right = nums.length - 1
    while (left <= right) {
        if(nums[right] === val) {
            right--
            continue
        } 
        if(nums[left] === val) {
            nums[left] = nums[right--]
        }
        left += 1
    }
    return left // 或right + 1
};

代码优化

var removeElement = function(nums, val) {
    let left = 0, right = nums.length - 1
    while (left <= right) {
        if(nums[right] !== val) {
             nums[left++] = nums[right]
        } 
        right--
    }
    return left // 或right + 1
};

其他基础题

题目难度备注
反转字符串简单

应用题

乘最多水的容器

方法一:暴力解法(超时)

function maxArea(height) {
    // 暴力
    let area = 0
    for (let left = 0; left < height.length - 1; left++) {
        const leftHeight = height[left]
        for (let right = left + 1; right < height.length; right++) {
            const rightHeight = height[right]
            area = Math.max(area, (right - left) * Math.min(leftHeight, rightHeight))
        }
    }
    return area
}

方法二:双指针

*参考反转字符串

可以分析暴力解法中面积的计算方式

(right - left) * Math.min(height[left], height[right])

right-left最大为nums.length-1,此时左右指针分别在0, nums.length - 1,如果希望获得更大的面积可以发现只能移动height[left], height[right]中短的那一个。因为根据公式Math.min(height[left], height[right])可以发现面积只与短边有关,因此在不断向中心移动左右指针的过程中长(right-left)越小,每次移动短边获得可能的更大的面积。

// 由于算面积时取左右height值中低的那一个,因此如果向中心移动高值对应的坐标,面积只会更低。如果向中心移动低值对应的坐标,如果新的值大于低值,则面积可能会变大。
    let left = 0, right = height.length - 1, area = 0
    while (left < right) {
        area = Math.max(area, (right - left) * Math.min(height[left], height[right]))
        if (height[left] > height[right]) {
            right -= 1
        } else {
            left += 1
        }

    }
    return area
}

三数和

经典题了,核心思路是将数组排序,把题目转化为 第二个数和第三个数的和是负的第一个数

-nums[first] = nums[second] + nums[third]

**代码中有些细节点要注意(比如不需要专门用一个值存重复的nums[start]和nums[second],既然排序,那重复的值必然相邻,判断nums[start++]和nums[start]是否相等就行)

function threeSum(nums) {
    // 等同于找三个数 -nums[i] = nums[j] + nums[k]
    // 当nums[i]值固定时,如果nums[j]的值也固定,那nums[k]的值也唯一了。
    // 可以把题目简化为找到[i+1, nums.length)中和为-nums[i]的两个数
    // 此时把数组排序可以避免很多的重复处理,1.nums[i]的值重复 2.nums[j]的值重复
    if (nums.length < 3) return []
    nums = nums.sort((a, b) => a - b)
    const res = []
    let sum = Number.MAX_SAFE_INTEGER
    for (let i = 0; i < nums.length - 2; i++) {
        if (sum + nums[i] === 0) {
            continue
        }
        sum = -nums[i]
        let left = i + 1, right = nums.length - 1
        let cur_left = Number.MAX_SAFE_INTEGER

        while (left < right) {
            const cur_sum = nums[left] + nums[right]
            if (cur_sum < sum) {
                left += 1
            } else if (cur_sum === sum) {
                if (nums[left] === nums[cur_left]) {
                    left += 1
                    right -= 1
                } else {
                    res.push([nums[i], nums[left], nums[right--]])
                    cur_left = left++
                }
            } else if (cur_sum > sum) {
                right -= 1
            }
        }
    }
    return res
}

代码优化

function threeSum(nums) {
    if (nums.length < 3) return []
    nums = nums.sort((a, b) => a - b)
    const res = []
    let sum = Number.MAX_SAFE_INTEGER
    for (let i = 0; i < nums.length - 2; i++) {
        // 同一个nums[i]值只用处理一遍,但注意i>0
        if (i > 0 && nums[i] === nums[i - 1]) {
            continue
        }
        sum = -nums[i]
        let left = i + 1, right = nums.length - 1

        while (left < right) {
            const cur_sum = nums[left] + nums[right]
            if (cur_sum < sum) {
                left += 1
            } else if (cur_sum === sum) {
                // 和一样,nums[second]一样时nums[third]一定一样
                if (left > i + 1 && nums[left] === nums[left - 1]) {
                    left += 1
                    right -= 1
                } else {
                    res.push([nums[i], nums[left++], nums[right--]])
                }
            } else if (cur_sum > sum) {
                right -= 1
            }
        }
    }
    return res
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值