leetcode分类刷题:基于数组的双指针(一、基于元素移除的O(1)类型)

文章介绍了如何使用双指针解决数组上的元素移除问题,包括基础的移除元素、移动零以及删除有序数组中的重复项。关键在于初始化双指针和更新条件,以及在保持O(1)空间复杂度下原地修改数组。双指针方法包括快慢指针和左右指针策略,根据题目需求灵活运用。
摘要由CSDN通过智能技术生成

1、对于数组上的双指针问题,主要是指两个变量在数组上移动或遍历的问题,之前整理的“二分查找”系列的算法题目,就是双指针解决数组上的索引相向移动。
2、双指针算法的关键在于:初始化双指针值和双指针值分别更新的条件,保持对以上两点的清晰认知,就能准确掌握这类题目。
3、本次博客总结的“基于元素移除的类型”题目,该类题目必然首先需要一个指针遍历数组元素,然后结合要求O(1)的空间复杂度,必然需要额外的指针在原数组上维护新的数组,因此需要采用双指针的解法。

27. 移除元素

元素移除的基础题,给定序列和目标元素,将目标元素全部移动到数组尾部,新数组的元素顺序可以改变,也不需要考虑数组中超出新长度后面的元素。

from typing import List
'''
27. 移除元素
题目描述:给你一个数组nums和一个值val,你需要 原地 移除所有数值等于val的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
示例 1:
    输入:nums = [3,2,2,3], val = 3
    输出:2, nums = [2,2]
    解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。
    例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。
题眼:原地修改数组+元素的顺序可以改变+不考虑超出新数组长度后的元素。
思路1:双指针之快慢指针,慢指针维护新数组,快指针遍历原数组,不等于val值时进行元素覆盖(赋值操作多),更新慢指针——可以保留原元素在新数组中的相对顺序 
思路2:双指针之左右指针:左指针遍历原数组,等于val值时,由右指针值赋值(赋值操作少),更新右指针,直到右指针小于左指针——不以保留原元素在新数组中的相对顺序 
'''


class Solution:
    # 双指针之快慢指针:需要对留下来的元素不断进行赋值操作,可以保留原元素在新数组中的相对顺序
    def removeElements(self, nums: List[int], val: int) -> int:
        slow, fast = 0, 0  # 快慢指针都指向起始位置,慢指针维护新数组,快指针遍历原数组
        while fast < len(nums):
            if nums[fast] != val:
                nums[slow] = nums[fast]  # 这里也可用 元素交换替换元素覆盖,实现val值全部放到 超出新数组长度后的位置
                slow += 1  # 更新慢指针
            fast += 1  # 更新快指针
        return slow

    # 双指针之左右指针:避免了留下来的数组元素的拷贝操作,赋值操作少,不可以保留原元素在新数组中的相对顺序
    def removeElements2(self, nums: List[int], val: int) -> int:
        left, right = 0, len(nums) - 1  # 左指针遍历原数组+维护新数组,右指针 赋值
        while left <= right:  # 注意迭代停止的状态
            if nums[left] == val:
                nums[left] = nums[right]  # 这里也可用 元素交换替换元素覆盖,实现val值全部放到 超出新数组长度后的位置
                right -= 1  # 更新右指针
            left += 1  # 更新左指针
        return left


if __name__ == '__main__':
    obj = Solution()
    while True:
        try:
            in_line = input().strip().split('=')
            nums = [int(n) for n in in_line[1].split(']')[0].split('[')[1].split(',')]
            val = int(in_line[2].strip())
            result = obj.removeElements2(nums, val)
            print(result, ',nums =', nums[:result])
        except EOFError:
            break

283. 移动零

给定目标元素为0,将目标元素全部移动到数组尾部,需要保持新数组元素的相对顺序,需要考虑数组中超出新长度后面的元素:因此只能采用数组元素交换,不可以用数组元素覆盖。

from typing import List
'''
283. 移动零
题目描述:给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
示例 1:
    输入: nums = [0,1,0,3,12]
    输出: [1,3,12,0,0]
题眼:“27. 移除元素"的特例”——元素值val为0移动到末尾+保持非零元素的相对顺序
思路:双指针之快慢指针,慢指针维护新数组,快指针遍历原数组,不等于val值时进行元素交换,更新慢指针——可以保留原元素在新数组中的相对顺序
'''


class Solution:
    def moveZeros(self, nums: List[int]):
        slow, fast = 0, 0  # 慢指针标记处理好的序列末尾,当遇到一次0以后,标记着0序列的开头;快指针遍历原数组
        while fast < len(nums):
            if nums[fast] != 0:
                nums[slow], nums[fast] = nums[fast], nums[slow]  # 元素交换 把 val值全部放到 超出新数组长度后的位置
                slow += 1
            fast += 1


if __name__ == "__main__":
    obj = Solution()
    while True:
        try:
            in_line = input().strip().split('=')
            nums = [int(n) for n in in_line[1].split(']')[0].split('[')[1].split(',')]
            obj.moveZeros(nums)
            print(nums)
        except EOFError:
            break

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

给定目标元素为数组中的重复项了,是一个需要动态更新的变量了,将目标元素全部移动到数组尾部,需要保持新数组元素的相对顺序,不需要考虑数组中超出新长度后面的元素。

from typing import List
'''
26. 删除有序数组中的重复项
题目描述:给你一个 升序排列 的数组nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次,返回删除后数组的新长度。元素的 相对顺序 应该保持一致。
由于在某些语言中不能改变数组的长度,所以必须将结果放在数组nums的第一部分。更规范地说,如果在删除重复项之后有k个元素,那么nums的前k个元素应该保存最终结果。
将最终结果插入nums 的前 k 个位置后返回 k 。
不要使用额外的空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
示例 1:
    输入:nums = [1,1,2]
    输出:2, nums = [1,2,_]
    解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。
题眼:“27. 移除元素"的特例——元素值val为 有重复的元素(是个需要更新的变量) 移动到末尾 + 升序排列 + 保持非零元素的相对顺序
思路:双指针之快慢指针,慢指针维护新数组,快指针遍历原数组,不等于val值时进行元素交换,更新慢指针——可以保留原元素在新数组中的相对顺序
'''


class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        slow, fast = 1, 1  # 慢指针标记处理好的序列末尾;快指针遍历原数组:第一个元素一定会被留下
        val = nums[slow-1]  # val可以直接用nums[slow-1]代替
        while fast < len(nums):
            if nums[fast] != val:  # 改成nums[fast] > val是利用上了 升序排列 的条件
                nums[slow] = nums[fast]  # 元素赋值/交换都可以
                slow += 1
                val = nums[slow-1]
            fast += 1
        return slow


if __name__ == "__main__":
    obj = Solution()
    while True:
        try:
            in_line = input().strip().split('=')
            nums = [int(n) for n in in_line[1].split(']')[0].split('[')[1].split(',')]
            print(obj.removeDuplicates(nums), nums)
        except EOFError:
            break

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

1、给定目标元素为数组中的重复项,是一个需要动态更新的变量了,同时要求“出现次数超过两次的元素只出现两次”,将目标元素全部移动到数组尾部,需要保持新数组元素的相对顺序,不需要考虑数组中超出新长度后面的元素。
2、如果按照本篇总结的做题顺序刷下来,到这一题还是同样的套路,仅仅需要在上题基础上稍微调整算法细节即可(好的刷题顺序不仅有助于掌握算法核心本质思想,更有助于面对难度加大的题目时更顺利的做出来)

from typing import List
'''
80. 删除有序数组中的重复项 II
题目描述:给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使得出现次数超过两次的元素只出现两次 ,返回删除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
示例 1:
    输入:nums = [1,1,1,2,2,3]
    输出:5, nums = [1,1,2,2,3]
    解释:函数应返回新长度 length = 5, 并且原数组的前五个元素被修改为 1, 1, 2, 2, 3 。 不需要考虑数组中超出新长度后面的元素。
题眼:“26. 删除有序数组中的重复项”的延申——元素值val为 有重复的元素(是个需要更新的变量) 移动到末尾 + 升序排列 + 超过两次的元素只出现两次
思路:双指针之快慢指针,慢指针维护新数组,快指针遍历原数组,不等于val值时进行元素交换,更新慢指针——可以保留原元素在新数组中的相对顺序
'''


class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        slow, fast = 2, 2  # 慢指针标记处理好的序列末尾;快指针遍历原数组:前两个元素一定会被留下
        val = nums[slow-2]  # val可以直接用nums[slow-2]代替
        while fast < len(nums):
            if nums[fast] != val:  # 改成nums[fast] > val是利用上了 升序排列 的条件
                nums[slow] = nums[fast]  # 元素赋值/交换都可以
                slow += 1
                val = nums[slow-2]
            fast += 1
        return slow


if __name__ == "__main__":
    obj = Solution()
    while True:
        try:
            in_line = input().strip().split('=')
            nums = [int(n) for n in in_line[1].split(']')[0].split('[')[1].split(',')]
            print(obj.removeDuplicates(nums), nums)
        except EOFError:
            break

个人总结体会

1、一开始做“基于数组的双指针(一、基于元素移除的类型)”这类题目时,通过看题解,代入式地在思维上接受了双指针的算法思路,对于为什么用双指针一直没太多领悟。
2、通过多次回顾与总结,明白了对于一个数组而言,本身需要第一个指针负责遍历每一个元素,再结合题目要求O(1)的空间复杂度,自然而然地需要第二个指针负责维护新数组了。
3、而对于最关键的双指针分别更新的条件,第一个负责遍历的指针自然是随着每次循环访问数组元素更新,第二个负责维护新数组的指针就需要在满足一定条件下去更新了。
4、维护新数组的指针在进行操作时的细节:可以有元素赋值和元素交换两种选择,就根据具体题目灵活选择即可。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

22世纪冲刺

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值