[Leetcode] [Tutorial] 双指针


283. 移动零

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。

示例:
输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]

Solution

class Solution:
    def moveZeroes(self, nums: List[int]) -> None:
        slow, fast = 0, 0
        while fast < len(nums):
            if nums[fast] != 0:
                nums[slow], nums[fast] = nums[fast], nums[slow]
                slow += 1
            fast += 1    

15. 三数之和

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

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

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

示例:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
注意,输出的顺序和三元组的顺序并不重要。

Solution

每次将较矮的那根柱子的指针向中心移动,因为移动较高的柱子不会增加水容量。

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        res= []
        nums.sort()
        for i in range(len(nums) - 2):
            if nums[i] > 0:
                break

            left, right = i + 1, len(nums) - 1
            while left < right:
                s = nums[i] + nums[left] + nums[right]
                if s > 0:
                    right -= 1
                elif s < 0:
                    left += 1
                else:
                    if [nums[i], nums[left], nums[right]] not in res:
                        res.append([nums[i], nums[left], nums[right]])
                    left += 1
        return res

如果在每次找到三元组时,都使用if [nums[i], nums[left], nums[right]] not in res:来避免重复,效率是较低的,我们可以:

  • 当找到一个解时,要确保左右指针指向的数字在下一次迭代中不同,以避免重复的三元组。这意味着在找到解后,我们不仅需要移动左或右指针,还需要确保它们不再指向相同的数字。

  • 为固定点i添加一个检查以跳过重复值。这是一个非常重要的步骤,因为如果不这样做,结果中可能会出现重复的三元组,即使您的双指针处理方式是正确的。

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        res = []
        nums.sort()

        for i in range(len(nums) - 2):
            if nums[i] > 0:
                break
            # 跳过重复的数
            if i > 0 and nums[i] == nums[i - 1]:
                continue

            left, right = i + 1, len(nums) - 1
            while left < right:
                s = nums[i] + nums[left] + nums[right]

                if s > 0:
                    right -= 1
                elif s < 0:
                    left += 1
                else:
                    res.append([nums[i], nums[left], nums[right]])
                    # 跳过重复的数
                    while left < right and nums[left] == nums[left + 1]:
                        left += 1
                    while left < right and nums[right] == nums[right - 1]:
                        right -= 1
                    left += 1
                    right -= 1

        return res

11. 盛最多水的容器

给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。
找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
返回容器可以储存的最大水量。

示例:
输入:[1,8,6,2,5,4,8,3,7]
输出:49

Solution

class Solution:
    def maxArea(self, height: List[int]) -> int:
        left, right = 0, len(height) - 1
        max_area = 0
        while left <= right:
            if height[left] < height[right]:
                max_area = max(max_area, (right - left) * height[left])
                left += 1
            else:
                max_area = max(max_area, (right - left) * height[right])
                right -= 1
        return max_area

42. 接雨水

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

示例:
输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6

Solution

朴素的做法是对于数组 height 中的每个位置,分别向左和向右扫描并记录左边和右边的最大高度,然后取两者之间的最小值,与当前高度的差值就是该位置能接的雨水量。

class Solution:
    def trap(self, height: List[int]) -> int:
        if not height:
            return 0
            
        n = len(height)
        rain = 0
        
        for i in range(n):
            max_left, max_right = 0, 0
            left, right = i-1, i+1
            while left >= 0:
                max_left = max(height[left], max_left)
                left -= 1
            while right < n:
                max_right = max(height[right], max_right)
                right += 1
            rain += max(0, min(max_left, max_right) - height[i])

        return rain

需要注意的是,我们需要在减去当前高度之前,确保 min(max_left, max_right) 大于当前高度。

现在我们用两个长度为 n 的数组 max_left 和 max_right,对于 0≤i<n,max_left 表示下标 i 及其左边的位置中 height 的最大高度,max_right 表示下标 i 及其右边的位置中 height 的最大高度,通过动态规划的方法正向遍历数组 height 得到数组 max_left 的每个元素值,反向遍历数组 height 得到数组 max_right 的每个元素值。

class Solution:
    def trap(self, height: List[int]) -> int:
        if not height:
            return 0
        
        n = len(height) 
        max_left, max_right = [0] * n, [0] * n
        max_left[0] = height[0]
        max_right[n - 1] = height[n - 1]

        for i in range(1, n):
            max_left[i] = max(max_left[i - 1], height[i])

        max_right = [0] * (n - 1) + [height[n - 1]]
        for i in range(n - 2, -1, -1):
            max_right[i] = max(max_right[i + 1], height[i])

        rain = sum(min(max_left[i], max_right[i]) - height[i] for i in range(n))
        return rain

在这个解决方法中,我使用了两个不同的循环来得到 max_left 和 max_right 的值,这是因为两个数组开始的位置和遍历的方向都不相同。

如果我们可以做到,在每次算出左边的 max_left 和 右边的 max_right 后,都能算出某一位置的 rain,我们就可以用双指针的方法实现一个时间复杂度为O(n),空间复杂度为O(1)的算法。

class Solution:
    def trap(self, height: List[int]) -> int:
        if not height:
            return 0
        
        n = len(height)
        left, right = 0, n-1
        max_left, max_right = 0, 0
        rain = 0

        while left < right:
            max_left = max(max_left, height[left])
            max_right = max(max_right, height[right])
            if height[left] <= height[right]:
                rain += max_left - height[left]
                left += 1
            else:
                rain += max_right - height[right]
                right -= 1
        return rain

如果 height[left] < height[right],那么对于 left 指向的柱子来说,它的右侧一定有柱子的高度大于等于 height[right](也就是说,right_max 一定大于等于 height[left])。因此,我们就可以计算出left指向的柱子所能接的雨水量,然后 left 向右移动一位。如果height[left] >= height[right],同理我们可以计算出 right 指向的柱子所能接的雨水量,然后 right 向左移动一位。

猜你喜欢

转载自blog.csdn.net/weixin_45427144/article/details/131264117
今日推荐