图解三数之和问题

思考

由于最近身边的人都在各种面试,被面试算法题折腾的够呛,日常LeedCode,个人也刷了一段时间的LeedCode,刚开始想着不看题解自己做,然后只能想到暴力破解法,其他思路几天也憋不出来。再之后要是一道题20分钟没有想到可行的思路,乖乖去看题解吧。

算法思维可以慢慢培养,不过这和数学奥数也差不多,如果不是为了专职算法或者参加竞赛,不用把所有精力放在上面,就和高考或考研的数学题一样,对于面试算法题可以靠题海战术应对,在明确好自己的职业路线,是开发路线还是算法路线,再对算法这块进行侧重。

无论什么算法最终针对的都是数据的操作,数据不到一定的量级各种算法难以看出差距,工作中也不要为了用算法而用算法,就和设计模式一样,业务需求和程序设计之间还有很多文章。

双指针

指的是在遍历对象的过程中,不是普通的使用单个指针进行访问,而是使用两个相同方向(快慢指针)或者相反方向(对撞指针)的指针进行扫描,从而达到相应的目的。一般来说,双指针需要在数组有序情况下才会使用,这种算法的思路较为简单,双指针题型还是较为容易看出来的。

两数之和

原题为:

  • 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那两个整数,并返回他们的数组下标。
  • 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。

示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

原题的解法为暴力破解法和哈希表,由于条件限制,双指针不适合解决这道题,这里吐槽下这些算法题,有时候看半天题目也不理解究竟要干什么,和数学卷子上那些偏题怪题差不多,虽然可以考察算法思维,不过对于开发路线的人来说最好还是结合实际应用场景中的例子,就好比数学题里的假设和实际相差甚远。

现在把这道题改下,假设返回的不是下标而是那两个整数的数组
示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [2, 7]

原题要求返回下标就是提示你用哈希表,算是关键词提示,现在发现试卷做多了,对于这些应试题型的理解还是有点作用的。

暴力破解自然就是双层循环,两两求和比较,时间复杂度就是 O ( n 2 ) O(n^2) O(n2)

现在使用双指针。
给定数组为: [3, 2, -2, 4, 6, 3, 8]
target:8
图解三数之和问题_第1张图片
这是最简单的双指针,简单的说就是两边逼近,求和的结果与target进行比较,由于该数组已经升序排列,和小于target就说明需要更大的元素,left向右移动代表了获得更大的元素,right向左移动代表着获取更小的值。

由于我们题目中假设肯定会有解,就尽量让代码简洁,需要应对其他特殊情况或者进行异常处理的就不做处理,实际上只要找到解题思路,哪些细节就可以不断填充,无论面试还是考试都一样。
代码如下:

def twoSum(nums, target):
    length = len(nums)
    # 数组排序
    nums = sorted(nums)
    left = 0
    right = length - 1
    while left < right:
        sums = nums[left] + nums[right]
        if sums < target:
            left += 1
        elif sums > target:
            right -= 1
        else:
            return [nums[left], nums[right]]

这样时间复杂度就分成了两部分,排序部分一般都是快速排序,时间复杂度为 O ( n log ⁡ n ) O(n\log{n}) O(nlogn),对排好序的数组进行双指针操作,时间复杂度为 O ( n ) O(n) O(n),最终的时间复杂度为 O ( n log ⁡ n ) + O ( n ) = O ( n log ⁡ n ) O(n\log{n}) + O(n)=O(n\log{n}) O(nlogn)+O(n)=O(nlogn)

三数之和

两数之和算是开胃菜,方便理解三数之和,三数之和的题解思路只是比两数之和多了一层循环。
原题如下:

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 请你找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。

示例:
给定数组 nums = [-1, 0, 1, 2, -1, -4]
满足要求的三元组集合为:[[-1, 0, 1], [-1, -1, 2]]

这道题算是在求和问题上再包装些东西,有陷阱,比如需要去除重复解,指定target为0可以让代码有优化空间,不过总有一点不会变,需要你用双指针去求和,其他的不过是修剪枝叶或者添砖加瓦。

图解三数之和问题_第2张图片
首先对于已经排好序的数组,当 n u m s [ i ] > 0 nums[i] > 0 nums[i]>0时,三数之和永远大于0,此时就不用再遍历下去了,这是一个优化点。
其次,为了防止找到重复组,在每次针对 n u m s [ i ] nums[i] nums[i]进行遍历时,将其余前一次的 n u m s [ i − 1 ] nums[i-1] nums[i1]进行比较,如果相同则跳过此次遍历。
针对左右指针在遍历过程产生的重复是在找到目标数组时进行处理的,即当 n u m s [ i ] + n u m s [ l e f t ] + n u m s [ r i g h t ] = 0 nums[i]+nums[left]+nums[right]=0 nums[i]+nums[left]+nums[right]=0时在进行循环片段,判断左界和右界是否和下一个重复,除去重复的同时将 l e f t left left r i g h t right right移动到下一个位置。
代码如下:

def threeSum(nums):
    n = len(nums)        
    out = []
    nums = sorted(nums)
    
    for i in range(n-2):
        if nums[i] > 0:
            return out
        # 去除重复解
        if i > 0 and nums[i]==nums[i-1]:
            continue
        left = i+1
        right = n-1
        while left < right:
            sum_three = nums[i] + nums[left] + nums[right]
            if sum_three > 0:
                right -= 1
            elif sum_three < 0:
                left += 1
            else:
            	# 添加解数组
                out.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 out

时间复杂度计算:
数组排序 O ( n log ⁡ n ) O(n\log{n}) O(nlogn) + 遍历数组 O ( n ) O(n) O(n) × \times × 双指针遍历 O ( n ) O(n) O(n) = O ( n 2 ) O(n^2) O(n2)

一般都是先写好求和部分的代码,其余的部分在出现问题后不断修改和优化,针对三数之和问题有不少变种,抓住解题思路,剩下的就是调代码了。

最接近的三数之和

针对这道题应该可以熟练解决了,LeedCode也是这么刷题的,同一类型的题下虽有多种解,不过总有共同点,一种解题思路可以适应不同的变种。
原题如下:

给定一个包括 n 个整数的数组 nums 和 一个目标值 target。
找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。
假定每组输入只存在唯一答案。

例如,给定数组 nums = [-1,2,1,-4], 和 target = 1.
与 target 最接近的三个数的和为 2. (-1 + 2 + 1 = 2).

这个比上面的三数之和还要简单不少,没啥陷阱,只需要进行的距离计算,同时更新三数之和。
不多说了,只要上面的三数之和理解了,这就是道送分题。

代码如下:

def threeSumClosest(nums, target):
    length = len(nums)
    distance = abs(target - (nums[0] + nums[1] + nums[2]))
    result = nums[0] + nums[1] + nums[2]
    
    nums = sorted(nums)
    for i in range(length-2):            
        left = i + 1
        right = length - 1
        while left < right:
            sums = nums[i] + nums[left] + nums[right]
            if sums < target:
                left += 1
            elif sums > target:
                right -= 1
            else:
                return sums
            # 计算当前距离
            temp = abs(target - sums)
            if temp < distance:
                distance = temp
                result = sums
    return result

你可能感兴趣的:(算法)