3.1.1 python数组双指针算法1——求和问题(LeetCode 2sum & 3sum & 4sum)

LeetCode题目中数组和字符串的占比很大。在Array(数组)和String(字符串)的题目中,很多都是用双指针去解决问题的。在此综合几道Array中双指针的题目将这一思想方法汇总学习。后续还会有双指针在其他方面的应用。

双指针算法介绍

在之前链表的题目中也有双指针这一思想,详情可看 python数据结构之链表——带环链表及交叉链表(双指针法),链表中的双指针与数组中不同。双指针遍历数组时(通常是有序数组),一个在头,一个在尾,同时向中间遍历(在链表中不能实现),直到两个指针相交,时间复杂度为O(n)。算法的应用范围有两数求和、就地交换等。

应用场景之求和问题

1 Two Sum

Given an array of integers, return indices of the two numbers such that they add up to a specific target.

You may assume that each input would have exactly one solution, and you may not use the same element twice.

这是LeetCode的第一题,非常简单,但是也有许多可说的干货,故拿出来溜溜。这一问题基本上三种解法,代表了三种层次的复杂度,很有代表性。

1> 双重循环暴力求解 O(n^2),这样竟然也能过,阿弥陀佛了。许多问题我们第一反应肯定是暴力求解可以解决,但是我们不应该去这样想,一定要也一定会写出一个更优的算法的,更何况有的题目暴力法会超时。这样的方法我们一般都可以优化为O(nlogn)的,请看下面方法2.

class Solution:
    def twoSum(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """       
        l = len(nums)
        for i in range(l-1):
            for j in range(i+1,l):
                if nums[i] + nums[j] == target:
                    return [i,j]

2>排序法O(nlogn),先用排序函数排序,再利用双指针遍历数组。看下面的代码,似乎也没什么可说的。可以看到,找到相应两个数之后,我们还需遍历一遍原数组找到原索引,十分别扭,下面对此进行了改良。

class Solution:
    def twoSum(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        array = nums.copy()
        nums.sort()
        i, j = 0, len(nums)-1
        while i<j:
            s = nums[i] + nums[j]
            if s<target:
                i += 1
            elif s>target:
                j -= 1
            else:
                break

        res = []
        for k in range(len(nums)):
            if array[k] == nums[i] or array[k] == nums[j]:
                res.append(k)
        return res
class Solution:
    def twoSum(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        l = len(nums)
        array = list(zip(nums,range(l)))
        array.sort()
        i, j = 0, l-1
        while i<j:
            s = array[i][0] + array[j][0]
            if s<target:
                i += 1
            elif s>target:
                j -= 1
            else:
                return [array[i][1],array[j][1]]

3>最后一种方法利用了哈希表结构。不熟悉这一东东的参考这篇文章:python数据结构与算法——哈希表,利用这一结构使我们的程序达到了O(n).下面的方法copy的别人的,利用了Python的字典结构。

class Solution:
    def twoSum(self, nums, target):
        map = {}
        for i in range(len(nums)):
            if nums[i] not in map:
                map[target - nums[i]] = i
            else:
                return map[nums[i]], i

15 3Sum

Given an array nums of n integers, are there elements a, b, c in nums such that a + b + c = 0? Find all unique triplets in the array which gives the sum of zero.
Note:
The solution set must not contain duplicate triplets.
Example:
Given array nums = [-1, 0, 1, 2, -1, -4],
A solution set is:
[
  [-1, 0, 1],
  [-1, -1, 2]

]

这一问题在2SUM上升级,我们基本思路是固定一个数a,然后用解决2SUM的方法使找到另外两个数和为0-a,当然数组还是排过序的才可。需要注意的是,left只在小于等于0的里面遍历即可;数组里有重复的数字,我们要做一些处理避免重复的结果~看下面的代码吧。

class Solution:
    def threeSum(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        nums.sort()
        left = 0
        l = len(nums)
        res = []
        while left < l and nums[left] <= 0:                            # 外循环
            mid = left + 1
            right = l - 1            
            if left > 0 and nums[left] == nums[left-1]:                # 跳过重复项
                left += 1
                continue
            while mid < right:                                         # 双指针
                tmp = -nums[left]     
                if nums[mid] + nums[right] == tmp:
                    res.append([nums[left],nums[mid],nums[right]])
                    while mid < right and nums[mid+1] == nums[mid]:    # 跳过重复项
                        mid += 1
                    mid += 1
                    while mid < right and nums[right-1] == nums[right]:   # 跳过重复项
                        right -= 1
                    right -= 1                        
                    
                elif nums[mid] + nums[right] < tmp:
                    mid += 1
                else:
                    right -= 1
            left += 1
        return res

16. 3Sum Closest

Given an array nums of n integers and an integer target, find three integers in nums such that the sum is closest to target. Return the sum of the three integers. You may assume that each input would have exactly one solution.
Example:
Given array nums = [-1, 2, 1, -4], and target = 1.

The sum that is closest to the target is 2. (-1 + 2 + 1 = 2).

现在我们加大一点难度,找最接近结果的,基本思路仍然参考上一题,但是要找遍所有情况,而且记录的是最接近的值,我们也不怕重复的数字。看下面的代码,closet变量记录最接近的差的绝对值以及该数,为了加速,我们还是滤掉了重复的left,最后结果也不错。

class Solution:
    def threeSumClosest(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: int
        """
        nums.sort()
        l = len(nums)
        left = 0
        closet = [abs(sum(nums[0:3])-target), sum(nums[0:3])]
        while left < l-2: 
            mid, right = left + 1, l - 1
            if left>0 and nums[left] == nums[left-1]:
                left += 1
                continue
            while mid<right:
                tmp = nums[left] + nums[mid] + nums[right]
                clo = abs(target - tmp)                
                if clo==0:
                    return target                                    
                elif tmp < target:
                    mid += 1                    
                else:
                    right -= 1
                
                if clo < closet[0]:
                    closet = [clo, tmp]
            left += 1
                
        return closet[1]

 

18. 4Sum

Given an array nums of n integers and an integer target, are there elements a, b, c, and d in nums such that a + b + c + d = target? Find all unique quadruplets in the array which gives the sum of target.
Note:
The solution set must not contain duplicate quadruplets.
Example:
Given array nums = [1, 0, -1, 0, -2, 2], and target = 0.
A solution set is:
[
  [-1,  0, 0, 1],
  [-2, -1, 1, 2],
  [-2,  0, 0, 2]

]

好了,双指针求和问题的终极版来了,四数和问题。顺延前面的思路,确定一个数,然后就转换为3SUM的问题了,想必怎么写盆友们也知道了,三重循环,最内层仍然是双指针,这样复杂度竟然飙到了O(n^3),看着很是不爽。在此基础上,后面讨论内容中有人对此进行了优化,速度达到了最快的,虽然代码一大篇看似冗余。

这一方法的思路就是排除一些不可能的情况,众所周知,最外两层循环各自确定两个数(设为A,B),而且一定是最小的两个数,当A,B过小或过大是进行处理,以A为例,在A遍历整个数组的过程中,A最小,剩余数组部分有个最大值MAX:

1)A太小,A+3*MAX<target ,跳过这个A,A+1

2)A太大,4*A >=target, 那么A的取值到此为止了,break

B也是如此处理,这样遍历过程大大缩短。鄙人试着将该大佬的Java代码改为python。

class Solution:
    def fourSum(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[List[int]]
        """        
        l = len(nums)
        res = []
        if l < 4:
            return res                
        nums.sort()
        max_num = nums[-1]
        
        # 4SUM
        for i in range(l-3):
            a = nums[i]
            if i>0 and nums[i] == nums[i-1]:
                continue
            if a + 3*max_num < target:
                continue
            elif 4*a == target:
                if i+3<l and nums[i+3]==a:
                    res.append([a,a,a,a])
                break
            elif 4*a > target:
                break
            
            # 3SUM
            target_3 = target - a
            j = i+1
            for j in range(i+1,l-2):
                b = nums[j]
                if j>i+1 and nums[j]==nums[j-1]:
                    continue
                if b + 2*max_num < target_3:
                    continue
                elif 3*b == target_3:
                    if j+2<l and nums[j+2]==b:
                        res.append([a,b,b,b])
                    break
                elif 3*b > target_3:
                    break
                
                # 2SUM
                target_2 = target_3 - b
                left,right = j+1, l-1
                while left < right:                        
                    sum_2 = nums[left] + nums[right]
                    if sum_2 == target_2:
                        res.append([a,b,nums[left],nums[right]])
                        while left<right and nums[left+1]==nums[left]:
                            left += 1
                        left += 1
                        while left<right and nums[right-1]==nums[right]:
                            right -= 1
                        right -= 1
                    elif sum_2 < target_2:
                        left += 1
                    else:
                        right -= 1
        
        return res

优化后代码十分复杂,但是速度确实是极快的。代码分为三块,确实打包起来看着舒服,但是写一起,其中的各个变量一致,看起来方便。

当然这一问题还不止这一种思想,还有一种思想是将数组n个数两两加和得到n*n的二维数组(数组行列号代表原索引值),这样将4SUM直接降为2SUM,然后再通过上文2SUM中哈希表相关方法解决问题,需要注意的是找到相应两个数后,要确定他们的行列都不同(去掉数字两用的)。有兴趣的可以试试写一个。

总结一下:一口气学习了四道题,逐渐加大难度;2SUM的三种方法是基础;理解双指针求和问题的基本思路注意多重循环时一些优化处理,包括去重、排除。

下一篇继续介绍双指针算法的其他问题~

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值