Leetcode四数之和为特定值 + 全排列 Python案例讲解:以递归算法行万里路

(案例一)四数和为特定值

题目

        找出列表中四数和为特定值的不重复所有情况,比如原列表[1,3,5,6,0,4,2],特定值为10,那么返回的应该是[[2,3,5,0],[1,4,5,0],[1,0,3,6]].

目标细化

①必须是列表中的四个位置不重复的数(但如果列表本身是[2,2,2,2,2,2,2,2],特定值为8,那返回的是[[2,2,2,2]]是可以接受的,因为这里的数字重复理解为来自原列表不同位置的2)

②四个数和为特定值

返回的是这四个数,以列表中的列表  list[list[int]] 的形式,且其中每种组合是不重复的:[1,2,3,0]和[2,3,0,1]认为是同一组合

④时间复杂度不要超过O(n2)

暴力算法筑理解根基

        实不相瞒,这道题把我逼疯了一整天,我们先来暴力算法理解一下这道题目的做法:暴力思路hin简单,不过就是初中的排列组合思路:要找到四个数,那先确定第一个数,对后面三个数进行遍历搜索;对后面三个数,先确定第一个数,再对剩下两个遍历搜索;再两个数中,确定第一个数,实际最先自由的仅仅是最后一个数:可以在其指定的范围内任意取值。如果最后的这个数再前面三个既定某值的时候在自己可行区域能找到满足要求的就储存下来四数现在所指的结果,如果没有满足的就先移动倒数第三个一位,然后第四个数再在更新后的可行区域内重复;第三个数在可行区域遍历完以后,再移动第二位。。。第二位在自己的可行区域遍历完后,再移动第一位,第一位全都移动完后,这件事就算了了。这事虽是了了,这复杂度也是非常可观的O(n4)    {乌鸦飞过}

        以下就是这个暴力算法的展示,初入编程的小白应该都懂我:“只要我们够坚强!嵌套循环没人比我更清醒” 哈哈哈当然能理解到已经说明对题目有清晰的认识了,但是好的算法需要时间空间的efficiency,那好,我们开始升级!

Leetcode四数之和为特定值 + 全排列 Python案例讲解:以递归算法行万里路_第1张图片

 递归算法实现升级

递归中的理解难点

写递归函数的时候日常三问,run出来即可获得不报错通过惊喜

①终点的返回是什么 

②每次递归的返回值如何拼接

③每次递归的变化值要在递归函数里展现

递归步骤总结

①确定基:递归回到的最终步骤就是这里的基,比如这道题,我们熟悉的是:两数之和为特定值选这类问题,所以我们把找四个数递归到找两个数的基。找两个数是好处理的,因为我们只需要两个指针,从列表的两头同时出发进行双向奔赴的遍历检索。

②确定回归到基的递归方式:我们想把找4个数的问题转换为不断的找2个数的问题,什么方式呢?最简单的思路是,固定一个数,把剩下的部分当作一个整体,强行实现二分,视作是两个数的问题,然后依次递归下去,直到剩下的部分就是一个数为止。

③递归函数的动态变量:递归函数的参数应该是随着每一次递归动态变化的,这样才能实现问题到简单版本的基的回归,在此题中,递归函数的申明是:

                                                       nSum(self, nums, n, target)

*nums是搜索列表;n是还待搜索的不确定数个数;target是和为特定值的特定值

但在调用中是:

                                    self.nSum(nums[fix_idx+1:], n-1, target-nums[fix_idx])

可见每强行二分一次,单独挑出来固定的值的位置索引fix_idx不仅决定了每次迭代放入的列表

Ⅰ nums->nums[fix_idx+1:],翻译为搜索特定值的区域从全列表区域缩小为该固定值后的列表区域

Ⅱ n->n-1  因为强行二分固定了一个值,剩下待搜索的数目从n个变为n-1个

Ⅲ target-> target-nums[fix_idx] 因为强行二分固定了一个值,那么待检索的另一部分的和是总和减去确定值

④递归函数的调用:这一点不是递归的知识,是面向对象的知识。没有学过python OOP的同志也不用紧张,大体就是class是一个类,这里面的功能实现,也就是函数的第一个参数都是self也就是类的实例化对象,所以在这个类的函数中递归调用类中的函数,需要self.函数名,如果self.函数名了那函数的参数就可以直接跳过self。所以下面在递归时调用nSum的时候是self.nSum(nums,n,target)

④如何拼接次次递归结果:明确需要最后返回的是什么,此题中是一个列中列,一个大列表囊括所有组合,每个组合都是以列表形式盛放四个满足条件的数。首先,每种组合中四个数的拼接可以用[]+[],因为在python中[1,2,3]+[4,5]=[1,2,3,4,5];其次,组合与组合之间的拼接应该是直接用一整个列表all_set进行append(列表),因为list.append(sub_list)=[[],[],[]]

!!!最大难点,在于基,即递归的终点应该返回什么,因为递归实际上是把一个大问题全都划分为这个基问题,最终的返回值是取决于每次递归到基的返回值。注意在这里我们是用了sub来盛放所有的递归接结果,这个是一个列表有各种结果,这个sub的内容每一次递归都会随着递归的深度覆盖为当前递归阶段的分支,比如第一次递归提出一个固定值后,sub中的每一种情况长度都应该为3,第二次递归,sub分支中每一种情况的返回长度都是2。但是在递归中它是有记忆的,他最终会回到要求的总长度,可以理解为他先知道了大局,然后再深入解决了每个细节后大局得到了改善。至于基,返回的也就应该是基问题的所有结果,以列表形式,因为如前面所说我们可以通过在python中[1,2,3]+[4,5]=[1,2,3,4,5]实现总列表的得到,所以我们可以不断将分支sub和[nums[fix_idx]]相加,,把四个数拼接成长度为4的列表。

class Solution:
    def fourSum(self, nums, target):
        nums.sort() #先将列表排序,提高遍历效率,并可以用从小到大的性质进行移动
        return self.nSum(nums, 4, target)
    def nSum(self, nums, n, target):#自定义一个递归用函数,目标是将所有大于2的nums都递归到等于2的情况
        all_set = [] #用于储存所有满足条件的组合
        #递归函数第一步,设置递归的终结情况,注意返回的结果是多种情况,可用列表盛放
        if n == 2:
            #找两个满足条件的数时,设置左右指针指向列表中的两个数对象
            left=0  
            right=len(nums)-1
            while left < right: #设置继续循环的先决条件——左指针在右指针的左边
                if nums[left] + nums[right] == target:
                    all_set.append([nums[left], nums[right]])
                    left += 1
                    right -= 1 
#满足条件后,由于列表已排过序,所以必须同时移动才能找到下一个满足两数和为特定值的组合
                elif nums[left] + nums[right] > target: 
#说明两者中较大的数太大了,右指针左移一位
                    right -= 1 
                else: 
#说明两者中较小的数太小了,左指针右移一位
                    left += 1
            return all_set
        else:
#下面是递归的起点步骤,对列表中的每一个数依次进行固定,强行构造出(1个数vs1组)的二分结构
            for fix_idx in range(len(nums)):
                #以下是递归待返回的分支一部分
                sub = self.nSum(nums[fix_idx+1:], n-1, target-nums[fix_idx]) 
                for i in range(len(subres)):
                    option=[nums[fix_idx]] + sub[i] #递归部分和固定部分的拼接
                    if option not in res:
                        all_set.append(option) #保证所有组合的唯一性
            return all_set

(案例二)全排列输出

        应用如上相同的自问自答检验和递归算法实现步骤解决如下问题,得到印证。会了就是内化了~~

题目

     Leetcode四数之和为特定值 + 全排列 Python案例讲解:以递归算法行万里路_第2张图片

 目标细化

①确定迭代的终止条件:递归到我们能解决的简单低维问题,这里即输入列表长度只有2 的情况,返回列表本身及列表的逆序

②迭代的设置:仍然是每次确定一个数,然后对剩下的数调用递归函数,设置temp用来作为当前状态除去当前固定值的待迭代新列表,通过len(temp)也可以实现递归中该变量的动态更新

 代码详解

Leetcode四数之和为特定值 + 全排列 Python案例讲解:以递归算法行万里路_第3张图片

import numpy as np
class Solution(object):
    def permute(self, nums):
        if len(nums)==1:
            return [nums]
        else: 
            return self.nPermute(len(nums),nums)
    def nPermute(self,rest_n,nums):
        res=[]
        if len(nums)==2:
            return nums,nums[::-1] #长度为2的列表的所有排列组合就是它本身和本身的逆序
        else:
            for i in range(len(nums)): #依次对列表中的每一个数进行固定
#用temp列表复制nums以防止每次迭代对原列表改变从而影响正常遍历固定列表每一值
                temp=nums  
                fix_point=temp[i]
            #实现从列表删除固定值
                temp=temp[:i]+temp[i+1:]
#以下ll盛放所有的可能情况分支,根据基的返回可知并不是列表形式,但可以用for in调取每一种情况
                ll=self.nPermute(len(temp),temp)
                for x in ll:
                    one=x+[fix_point] 
#这里不用再讨论insert位置是因为通过每次固定一个数已经可避免重复
                    res.append(one)
            return res  

写在后面

        这是我刷 leetcode 的第三天,发现有意思了!!从完全跑不出代码到可以得到正确的输出,从提交时显示超时无效到可以鸡肋地击败5%。。。5%就5%我已经超满意了,希望能继续进步,欢迎大神指正以上全部可改进之处,希望大家多多支持,赞赞俺,谢谢咯

你可能感兴趣的:(量化面试积累,python,leetcode,递归法)