Python Leetcode刷题之双指针解题

双指针

双指针法,有时也叫快慢指针,在数组里是用两个整型值代表下标,在链表里是两个指针,一般能实现O(n)的时间解决问题,两个指针的位置一般在第一个元素和第二个元素或者第一个元素和最后一个元素,快指针在前“探路”,当符合某种条件时慢指针向前挪。

双指针还可以用来缩减搜索空间!!!
主要是将一头一尾的索引设为两个指针,当满足条件1时,头指针加一;当满足条件2时,尾指针减一;当满足条件3时,返回结果。多用于有序的数组(矩阵)中,可以有效降低算法的复杂度。

1.盛最多水的容器

在这里插入图片描述

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

参考:双指针可用于缩减搜索空间

2.搜索二维矩阵 II

在这里插入图片描述

class Solution:
    def searchMatrix(self, matrix, target):
        if not matrix:
            return False
        i, j = 0, len(matrix[0]) - 1
        while i < len(matrix) and j > -1:
            if matrix[i][j] < target:
                i += 1
            elif matrix[i][j] > target:
                j -= 1
            else:
                return True
        return False

3.两数之和 II - 输入有序数组

在这里插入图片描述

class Solution:
    def twoSum(self, numbers, target):
        l, r = 0, len(numbers)-1
        while numbers[l] + numbers[r] != target:
            if numbers[l] + numbers[r] < target:
                l += 1
            else:
                r -= 1
        return l+1, r+1

4.删除排序数组中的重复项

在这里插入图片描述

class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        a = 0
        b = 1
        while b < len(nums):
            if nums[a] == nums[b]:
                b += 1
            else:
                a += 1
                nums[a] = nums[b]
        return a+1

优化:
当数组无重复数字(中间多个数字无重复)时,代码else的操作会将数组复制一遍。因此可以优化如下:

#优化
class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        a = 0
        b = 1
        while b < len(nums):
            if nums[a] != nums[b]:
                #将多个连续不重复的数字直接跳过赋值操作
                if (b - a) > 1:
                    nums[a + 1] = nums[b]
                a += 1
            else:
                b += 1
        return a + 1

5.移动零

在这里插入图片描述
本题为简单题,主要是介绍快慢指针的思路。
在原数组上进行操作,因此可以用一个index表示不为0的下标(慢指针),对数组进行遍历(快指针),当遍历的数据不为0时,将该值赋值给index下标的元素。最后将数组其他元素置为0。

class Solution:
    def moveZeroes(self, nums) -> None:
        index = 0
        for i in range(len(nums)):
            if nums[i] != 0:
                nums[index] = nums[i]
                index += 1
        for i in range(index, len(nums)):
            nums[i] = 0
        return nums


#循环一次
class Solution:
    def moveZeroes(self, nums) -> None:
        j = 0
        for i in range(len(nums)):
            if nums[i] != 0:
                nums[j], nums[i] = nums[i], nums[j]
                j += 1
        return nums

6.三数之和

在这里插入图片描述
先对数组进行排序,然后用一个for循环对循环体内部应用双指针进行遍历求和,思路就是先固定前面一个数,然后对后面的数进行类似两数之和的操作,只是多了对重复数字的跳过操作(while循环操作)。

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        n = len(nums)
        if not nums or n < 3:
            return []
        nums.sort()
        res = []
        for i in range(n-2):
            if nums[i] > 0:
                return res
            if i > 0 and nums[i] == nums[i - 1] :
                continue
            l = i + 1
            r = n - 1
            while l < r:
                sum = nums[i] + nums[l] + nums[r]
                if  sum == 0:
                    res.append([nums[i], nums[l], nums[r]])
                    #跳过重复的数字
                    while l < r and nums[l] == nums[l + 1]:
                        l += 1
                    while r > l and nums[r] == nums[r - 1]:
                        r -= 1
                    l += 1
                    r -= 1
                elif sum > 0:
                    r -= 1
                else:
                    l += 1
        return res

7.四数之和

在这里插入图片描述
思路:
三数之和的子步骤是两数之和,同样四数之和的子步骤是三数之和,因此可以先确定两个数,然后再利用两数之和的解题框架进行编写代码。

class Solution:
    def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
        n = len(nums)
        res = []
        nums.sort()
        #if n < 4 or nums[0] > target or nums[-1] < target:
        if n < 4:
            return res
        for i in range(n - 3):
            # if nums[i] > target:
            #     break
            if i > 0 and nums[i] == nums[i - 1]:
                continue
            for j in range(i+1, n - 2):
                # if nums[i] + nums[j] > target :
                #     break
                if j > i + 1 and nums[j] == nums[j - 1]:
                    continue

                l = j + 1
                r = n - 1
                while l < r:
                    sum = nums[i] + nums[j] + nums[l] + nums[r]
                    if sum == target:
                        res.append([nums[i], nums[j], nums[l], nums[r]])
                        while l < r and nums[l] == nums[l + 1]:
                            l += 1
                        while r > l and nums[r] == nums[r - 1]:
                            r -= 1
                        l += 1
                        r -= 1
                    elif sum > target:
                        r -= 1
                    else:
                        l += 1
        return res

易错点!!!
与三数之和(和为0)不同,如果对代码进行剪枝,应该注意此题给的target不一定为0!!!如上代码注释掉的代码为错误代码,当target为负数时,程序运行就不能得到正确答案!如果要对程序进行剪枝,可采取如下剪枝:
在这里插入图片描述

###剪枝后的代码
class Solution:
    def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
        quadruplets = list()
        if not nums or len(nums) < 4:
            return quadruplets
        
        nums.sort()
        length = len(nums)
        for i in range(length - 3):
            if i > 0 and nums[i] == nums[i - 1]:
                continue
            if nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] > target:
                break
            if nums[i] + nums[length - 3] + nums[length - 2] + nums[length - 1] < target:
                continue
            for j in range(i + 1, length - 2):
                if j > i + 1 and nums[j] == nums[j - 1]:
                    continue
                if nums[i] + nums[j] + nums[j + 1] + nums[j + 2] > target:
                    break
                if nums[i] + nums[j] + nums[length - 2] + nums[length - 1] < target:
                    continue
                left, right = j + 1, length - 1
                while left < right:
                    total = nums[i] + nums[j] + nums[left] + nums[right]
                    if total == target:
                        quadruplets.append([nums[i], nums[j], nums[left], nums[right]])
                        while left < right and nums[left] == nums[left + 1]:
                            left += 1
                        left += 1
                        while left < right and nums[right] == nums[right - 1]:
                            right -= 1
                        right -= 1
                    elif total < target:
                        left += 1
                    else:
                        right -= 1
        
        return quadruplets

剪枝后的代码运行时间可以达到80ms,而未剪枝的代码运行时间为990ms左右,可见剪枝可以很好的优化程序的运行时间。

另外,本题还可以采用递归+两数之和的方法。代码 如下:

class Solution:
    def fourSum(self, nums, target):
        def findNsum(nums, target, N, result, results):
            if len(nums) < N or N < 2 or target < nums[0]*N or target > nums[-1]*N:  # early termination
                return
            if N == 2: # two pointers solve sorted 2-sum problem
                l,r = 0,len(nums)-1
                while l < r:
                    s = nums[l] + nums[r]
                    if s == target:
                        results.append(result + [nums[l], nums[r]])
                        l += 1
                        while l < r and nums[l] == nums[l-1]:
                            l += 1
                    elif s < target:
                        l += 1
                    else:
                        r -= 1
            else: # recursively reduce N
                for i in range(len(nums)-N+1):
                    if i == 0 or (i > 0 and nums[i-1] != nums[i]):
                        findNsum(nums[i+1:], target-nums[i], N-1, result+[nums[i]], results)

        results = []
        findNsum(sorted(nums), target, 4, [], results)
        return results

8.合并两个有序数组

在这里插入图片描述
一般而言,对于有序数组可以通过双指针法达到O(n+m)的时间复杂度。
最直接的算法实现就是将一个指针指向nums1的开头,另外一个指针指向nums2的开头,然后判断两个指针指向的数字大小先后加入一个大小为n+m的数组中,并移动相应的指针。代码实现如下:

双指针从前往后:

class Solution:
    def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:   
        p, q = 0, 0
        nums1_copy = nums1[:m]
        nums1[:] = []     #注意与nums1 = []的区别:没有改变旧的nums1的大小
        while p < m and q < n:
            if nums1_copy[p] < nums2[q]:
                nums1.append(nums1_copy[p])
                p += 1
            else:
                nums1.append(nums2[q])
                q += 1
        if p < m :
            nums1 += nums1_copy[p:]
        if q < n:
            nums1 += nums2[q:]
        return nums1

双指针从后往前:

class Solution:
    def merge(self, nums1, m, nums2, n):
        while m > 0 and n > 0:
            if nums1[m-1] >= nums2[n-1]:
                nums1[m+n-1] = nums1[m-1]
                m -= 1
            else:
                nums1[m+n-1] = nums2[n-1]
                n -= 1
        if n > 0:
            nums1[:n] = nums2[:n]

9.接雨水

在这里插入图片描述

class Solution:
    def trap(self, height: List[int]) -> int:
        l, r = 0, len(height) - 1
        res = 0
        left_max, right_max = 0, 0
        while l < r:
            if height[l] < height[r]:
                if height[l] >= left_max:
                    left_max = height[l]
                else:
                    res += left_max - height[l]
                l += 1
            else:
                if height[r] >= right_max:
                    right_max = height[r]
                else:
                    res += right_max - height[r]
                r -= 1
        return res

上述思路可能不太好理解,另有一种思路,参考双指针解法,代码如下:

class Solution:
    def trap(self, height: List[int]) -> int:
        l, r = 0, len(height) - 1
        res = 0
        temp_h = 0
        while l <= r:
            min_h = min(height[l], height[r])
            if min_h > temp_h:
                res += (min_h - temp_h) * (r - l + 1)
                temp_h = min_h
            while l <= r and height[l] <= temp_h:
                l += 1
            while l <= r and height[r] <= temp_h:
                r -= 1
        res -= sum(height)
        return res
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值