数据结构算法刷题(27)回溯(子集型)

回溯思想:

思路:这种出现全部xx组合的,基本都是回溯算法。首先,当digits是空,那返回也是空。当回溯到边界条件的时候,就更新答案,在非边界条件的时候,循环该数值下的全部情况。

class Solution:

    def letterCombinations(self, digits: str) -> List[str]:

        dict_letter = {'2':['a','b','c'],

                        '3':['d','e','f'],

                        '4':['g','h','i'],

                        '5':['j','k','l'],

                        '6':['m','n','o'],

                        '7':['p','q','r','s'],

                        '8':['t','u','v'],

                        '9':['w','x','y','z']}

        if len(digits) == 0:

            return []

        ans = []

        def letter(path,num): #将每次的新组合传递下去

            if len(num) == 0: #何时停止递,更新答案?

                ans.append(path)

                return

            for c in dict_letter[num[0]]: #循环的子问题

                letter(path+c,num[1:])

        letter('',digits)

        return ans

子集型回溯:

 

思路:对于nums中的每一个数字,都有选和不选两种可能,一共有n次选择的机会,用n来控制递归,每选择一次,就给n减一再去递归,直到n是0,就要将path写入答案。注意path是全局变量,要用copy固定答案,以及某一次选择子集之后,要恢复现场。(退回到根节点)

class Solution:

    def subsets(self, nums: List[int]) -> List[List[int]]:

        ans = []

        path = []

        n = len(nums)

        def sets(n):

            if n == 0:

                ans.append(path.copy())#固定答案

                return

            sets(n-1) #不选该子集,直接递归

            path.append(nums[n-1]) #选该子集

            sets(n-1) #选该子集

            path.pop() #恢复现场

        sets(n)

        return ans

 

思路一:遍历子串的结束位置,对于每一个传入的i,遍历子串的结束位置,如果是回文串,就加入path中,然后继续递归之后的子串。

class Solution:

    def partition(self, s: str) -> List[List[str]]:

        ans = []

        path = []

        n = len(s)

        def part(i): #子串的开始位置

            if i == n:

                ans.append(path.copy())

                return

            for j in range(i,n):#遍历子串的结束位置

                t = s[i:j+1]

                if t == t[::-1]: #是回文串

                    path.append(t)

                    part(j+1) #递归剩下的子串

                    path.pop()#恢复现场

        part(0)

        return ans

思路二:假设每个字母之间存在一个逗号,每次遇到逗号都要选择或者不选择。当不选择逗号时,初始位置start不变,但是终止位置i+1(i截止到n-1),当选择逗号时,意味着从逗号处开始分割,取此时的s[start:i+1],判断是否是回文串,如果是,就加入到path中,继续递归剩下的字符串(s[i+1:],开始位置和结束位置都重新设置为i+1),递归之后要恢复现场。

class Solution:

    def partition(self, s: str) -> List[List[str]]:

        ans = []

        path = []

        n = len(s)

        def part(start,i): #子串的开始位置,结束的位置

            if i == n:

                ans.append(path.copy())

                return

            #不选择逗号

            if i < n-1:

                part(start,i+1) #只增加结束位置

            #选择逗号

            t = s[start:i+1] #取当前分割的字符串

            if t == t[::-1]: #是回文串

                path.append(t)

                part(i+1,i+1) #递归剩下的子串

                path.pop()#恢复现场

        part(0,0)

        return ans

 思路一:对于每个s[i]选择反转或者选择不反转

class Solution:

    def letterCasePermutation(self, s: str) -> List[str]:

        ans = []

        n = len(s)

        def bfs(i,path): #将层数和路径一起传递下去

            if i == n:

                ans.append(path)

                return

            #选择不反转

            bfs(i+1,path+s[i])

            #选择反转

            if 'a' <= s[i] <= 'z' or 'A' <= s[i] <= 'Z':

                bfs(i+1,path+s[i].swapcase())

        bfs(0,'')

        return ans

 思路二:枚举s[i]判断是否是字母并修改,由于没有包含不修改选项,所以递归入口处就要把答案写进去,然后对剩下的字符串修改。

class Solution:

    def letterCasePermutation(self, s: str) -> List[str]:

        ans = []

        n = len(s)

        path = list(s)

        def bfs(i):

            ans.append(''.join(path)) #先写入答案

            if i == n:

                return

            for j in range(i,n):#枚举下标并修改

                if 'a' <= s[j] <= 'z' or 'A' <= s[j] <= 'Z':

                    path[j] = path[j].swapcase() #修改

                    bfs(j+1) #递归

                    path[j] = path[j].swapcase() #恢复现场

        bfs(0)

        return ans

思路:按照由简单到复杂的顺序来思考这个题:我们需要一个path来存储满足累加序列的list或者是只有两个元素的list。首先我们假设path不需要任何限制条件,就是找num的全部分割子集(代码中的橘色部分),然后我们需要考虑到,如果一个数首位是0,就不符合分割,加一个限制条件。然后我们path中要求满足累加,把path分成两种情况,第一种是有了两个元素了,就要判断是否满足累加,满足就加到path中。另一种情况是不足两个元素,那就直接加到path中,不需要判断。最后在递归的边界条件,要判断一下,path是否是大于两个元素,大于才能放入ans中。

class Solution:

    def isAdditiveNumber(self, num: str) -> bool:

        ans = []

        path = []

        n = len(num)

        if n < 3:

            return False

        def dfs(i):

            if i == n:

                if len(path) >= 3: #长度大于2才能放入ans

                    ans.append(path.copy())

                return

            for j in range(i, n):

                t = num[i:j+1]

                if len(t) > 1 and t[0] == '0': #不符合分割的限制条件

                    continue

                if len(path) >= 2:

                    if path[-1] + path[-2] == int(t): #是否满足累加

                        path.append(int(t))

                        dfs(j+1)

                        path.pop()

                if len(path) < 2: #直接加到path中,不需要判断

                    path.append(int(t))

                    dfs(j+1)

                    path.pop()

        dfs(0)

        return len(ans) > 0

     

 思路:对于[1,n]中的数字,每一个我们都要判断一下是否是符合

设置一个函数sumi,如果函数返回true,就符合,那就将其平方和加入答案中。sumi中先分割i*i的十进制,在边界条件中判断path和是否是i,是就加入ans,最后输出len(ans) > 0.。

class Solution:

    def sumi(self,i):

        power2 = str(i*i)

        ans = []

        path = []

        n = len(power2)

        def dfs(a):

            nonlocal i

            if a == n:

                if sum(path) == i:

                    ans.append(path.copy())

                return

            for j in range(a,n):

                t = power2[a:j+1]

                path.append(int(t))

                dfs(j+1)

                path.pop()

        dfs(0)

        return len(ans) > 0

    def punishmentNumber(self, n: int) -> int:

        ans = 0

        for i in range(1,n+1):

            if self.sumi(i):

                ans += i*i

        return ans

思路:将问题拆分开,我们首先计算出[0,len(mat[0])](一共多少列)有多少种可能的不重复的子集 (橘色),这些子集中,我们需要的path长度是cols,当长度符合时,去更新答案。对于任何一个确定的子集,计算行的和是否等于被覆盖的数的和,是则更新答案。

class Solution:

    def rows(self,path,matrix):

        ans = 0

        for i in matrix:

            sumj = 0

            for j in path:

                sumj += i[j] #被覆盖的数的和

            if sum(i) == sumj: #行和是否等于被覆盖的数和

                ans += 1 #被覆盖的行数

        return ans

    def maximumRows(self, matrix, numSelect: int) -> int:

        res = -1

        path = []

        nums = [i for i in range(len(matrix[0]))] #求nums的可能子集

        def dfs(i):

            nonlocal res,path,nums

            if i == len(nums):

                if len(path) == numSelect: #当子集长度符合时,更新答案

                    res = max(self.rows(path.copy(),matrix),res)

                return

            dfs(i+1) #选

            path.append(i) #不选

            dfs(i+1)

            path.pop()

        dfs(0)

        return res

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值