leetcode 搜索与回溯


回溯r如果要展示所有的内容,三步走:设置现场,dfs,恢复现场。
需要这三步走的原因是因为要把所有的可能性都穷举出来。

写在最前前面,python的指针变量

python的变量都是指针,所以没有数据类型,即使现在能写出来也只是让大家看一下,不是java有实际的意义。变量的=就是指针的指向。

# 这个会真的改变变量,因为c虽然先指向了a,但是后面又指向新的地址[456],所以和a无关了,改变c不会改变a
a=[[1,2],[3,4],[5,6]]
c = a
c = [456]
print(a)
print(c)

# 变化
# 这是指向a里的具体元素作变化,所以a,b都跟着变
a=[[1,2],[3,4],[5,6]]
b=[num for num in a if num[-1]>2]
for e in b :
    e.append(999)
print(a)
print(b)

# 如果是func形参也会改变
lst = [1,3,4]
def junge(lst_jun):
    lst_jun.append(666)
    return lst_jun
print(junge(lst))
print(lst)

组合 lc 77

给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。

import copy
class Solution:
    def combine(self, n: int, k: int) -> List[List[int]]:
        lst = [i for i in range(1,n+1)]

        def dfs(start,lst,path,res,length):
            if length==k:
                res.append(copy.deepcopy(path))
                return
            for i in range(start,len(lst)):
                path.append(lst[i])
                dfs(i+1,lst,path,res,len(path))
                path.pop()

        path = []
        res = []
        length = 0
        start = 0
        dfs(start,lst,path,res,length)
        return res

组合求和

  • 能重复用每一次都是i;每个只能用一次i+1;能重复用但是不能重复数字有一个visited的矩阵;看后面那个排列。
  • 加和等于某一个数return,字符串被遍历完return 用index,结果的长度等于某个长度return,子集的不用return直接往里装,直到装完。

组合求和 lc 39

given candidate set [2, 3, 6, 7] and target 7,
A solution set is: [[7],[2, 2, 3]]

import copy
class Solution:
    def combinationSum(self,candidates,target):
        if not candidates:
            return []

        def dfs(start_index,candidates,target,path,res):
            if target == 0:
                res.append(copy.deepcopy(path))
                return

            for i in range(start_index,len(candidates)):
                if candidates[i]>target:
                    continue
                #设置现场
                path.append(candidates[i])
                #是否能减成0,dfs
                dfs(i,candidates,target-candidates[i],path,res)
                #恢复现场
                path.pop()
        path = []
        res = []
        start_index = 0
        dfs(start_index,candidates,target,path,res)
        return res
  • 注意:有i 还是顺序往下走一次入栈,没有i是所有在都来一次,会出现2,3,3, 3,2,3所谓的顺序

组合2

给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的每个数字在每个组合中只能使用一次。

import copy
class Solution:
    def combinationSum2(self,candidates,target):
        #有重复数字,不重复的组合要县排序
        #40
        if not candidates:
            return []

        def dfs(start_index, candidates, target, path, res):
            if target == 0:
                res.append(copy.deepcopy(path))
                return

            for i in range(start_index, len(candidates)):
                if candidates[i] > target:
                    continue
                if i>start_index and candidates[i]==candidates[i-1]:
                    continue
                # 设置现场
                path.append(candidates[i])
                # 是否能减成0,dfs
                dfs(i+1, candidates, target - candidates[i], path, res)
                # 恢复现场
                path.pop()

        path = []
        res = []
        start_index = 0
        candidates.sort()
        dfs(start_index, candidates, target, path, res)
        return res
  • 剪枝方法都一样,先排序,目标是同一层,就是for那个位置,相同的后面在递归结果一样,剪了continue。

组合求和3 lc 216

找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。

import copy
class Solution:
    def combinationSum3(self,k, n):
        lst = [i for i in range(1,10)]

        def dfs(start_index,lst,path,res,length,sum_val):
            if length == k and sum_val==n:
                res.append(copy.deepcopy(path))
                return
            for i in range(start_index,len(lst)):
                path.append(lst[i])
                dfs(i+1,lst,path,res,len(path),sum_val+lst[i])
                path.pop()

        start_index=0
        path = []
        res =[]
        length =0
        sum_val = 0
        dfs(start_index,lst,path,res,length,sum_val)
        return res

排列

全排列 lc 46

import copy
class Solution:
    def permute(self,nums):
        visited = [False]*len(nums)
        def dfs(nums,visited,path,res,detph):
        #每一次都要有一个走到最后的标识符
            if detph == len(nums):
                res.append(copy.deepcopy(path))
                return
            for i in range(len(nums)):
                if visited[i] == False:
                    visited[i] = True
                    path.append(nums[i])
                    dfs(nums,visited,path,res,detph+1)
                    visited[i] = False
                    path.pop()
        path = []
        res = []
        detph = 0
        dfs(nums, visited, path, res, detph)
        return res
  • 与上面的不同是带标记的,如果dfs里有satart,,每次走start+1则是一直往下走,这里是每次都从头走,但是走过的不能再用。
  • -这个题非常好,与前面的两个类型有鲜明的对比。

全排列 II lc 47

数组元素可能含有相同的元素,进行排列时就有可能出现重复的排列,要求重复的排列只返回一个。

import copy
class Solution:
    def permuteUnique(self,nums):
        visited = [False]*len(nums)
        def dfs(nums,visited,path,res,detph):
        #每一次都要有一个走到最后的标识符
            if detph == len(nums):
            #没必要写set直接看是否在res中,不在才添加
            	if path not in res:
                	res.append(copy.deepcopy(path))
                return
            for i in range(len(nums)):
                if visited[i] == False:
                    visited[i] = True
                    path.append(nums[i])
                    dfs(nums,visited,path,res,detph+1)
                    visited[i] = False
                    path.pop()
        path = []
        res = []
        detph = 0
        dfs(nums, visited, path, res, detph)
        return res

#剪枝版本
import copy
class Solution:
    def permuteUnique(self,nums):
        visited = [False]*len(nums)
        def dfs(nums,visited,path,res,detph):
        #每一次都要有一个走到最后的标识符
            if detph == len(nums):
            #没必要写set直接看是否在res中,不在才添加
                res.append(copy.deepcopy(path))
                return
            for i in range(len(nums)):
            #相同层里,和后面的递归是一样的
            	if i>0 and nums[i] == nums[i-1] and not visited[i-1]:
            		continue 
                if visited[i] == False:
                    visited[i] = True
                    path.append(nums[i])
                    dfs(nums,visited,path,res,detph+1)
                    visited[i] = False
                    path.pop()
        nums.sort()
        path = []
        res = []
        detph = 0
        dfs(nums, visited, path, res, detph)
        return res
  • 剪枝要排序,第一层拿了1,第二层就不要拿1了,因为是层数往里走才会找到最后的答案越来越长,但是同层可以拿。同层是不同的列表,相当于是一个表格的感觉。

子集

子集 lc 78

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

import copy
class Solution:
    def subsets(self,nums):
        #这个和之前的不同,条件是依靠for到头了之后跳出来,不是独立的条件让之停止循环,就往里放就好了
        if not nums:
            return

        def dfs(start_index,nums,path,res):
            # if len(path)<=len(nums):
            #     res.append(copy.deepcopy(path))
            #     return
            res.append(copy.deepcopy(path))
            for i in range(start_index,len(nums)):
                path.append(nums[i])
                dfs(i+1,nums,path,res)
                path.pop()
        res = []
        start_index=0
        path = []
        dfs(start_index,nums,path,res)
        return res
  • 这是关键,和之前不一样的是这没有return,是依靠for遍历结束就停下了。

子集2

给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。说明:解集不能包含重复的子集。

import copy
class Solution:
    def subsets2(self,nums):
        #90
        #这个和之前的不同,条件是依靠for到头了之后跳出来,不是独立的条件让之停止循环,就往里放就好了
        if not nums:
            return

        def dfs(start_index,nums,path,res):
            # if len(path)<=len(nums):
            #     res.append(copy.deepcopy(path))
            #     return
            res.append(copy.deepcopy(path))
            for i in range(start_index,len(nums)):
                if i>start_index and nums[i]==nums[i-1]:
                    continue
                path.append(nums[i])
                dfs(i+1,nums,path,res)
                path.pop()
        res = []
        start_index=0
        path = []
        nums.sort()
        dfs(start_index,nums,path,res)
        return res
  • ==一样的方法去剪枝 if i>start_index and nums[i]nums[i-1]:

分割等和子集 lc 461

Input: [1, 5, 11, 5] Output: true Explanation: The array can be
partitioned as [1, 5, 5] and [11].

(1)回溯

class Solution:
	def canPartition(self,nums):
        nums_sum = sum(nums)
        if nums_sum%2!=0:
            return False
        else:
            target = nums_sum/2

        def dfs(start_index,nums,target)#,path):
            if target == 0:
                res[0] = True
                return
            for i in range(start_index,len(nums)):
                if nums[i]>target:
                    continue
                #path.append(nums[i])
                dfs(i+1,nums,target-nums[i])#,path)
                #path.pop()
        res=[False]
        path = []
        dfs(0,nums,target)#,path)
        return res[0]
  • 不需要穷举所有结果,只需要有一个正确即可。所以不需要设置现场和恢复现场。
  • 因为不正确不代表说没有,即不能停,但是正确是找到了需要停,所以只有找到的地方才有return。
  • 回溯的是nums里的元素,即使没有现场的设置恢复,但是仍然是顺序的套路,专门用框架写出来,并且把没用的代码注释掉,对比看下。
  • 不能使用重复的元素,所以在dfs往下走的时候需要+1

(2)递归法

class Solution:
	def canPartition_(self, nums):
	    nums_sum=sum(nums)
	    if nums_sum%2!=0:
	        return False
	    else:
	        nums_sum//=2
	    rec=[False]
    
	    def search(nums,i,s=0):
	        if i==len(nums) or s>nums_sum:
	            return
	        if nums[i]+s==nums_sum:
	            rec[0]=True
	            return
	        else:
	            search(nums,i+1,s)
	            search(nums,i+1,s+nums[i])
	    search(nums,0,0)
	    return rec[0]
  • 这里递归非常的优秀,和最长子串异曲同工,要求在递归函数里的内容必须一样的,所以递归里都是要+当前的数字,在递归的两个分叉路口走不一样的方法。
  • 因为有错的时候要退出,所以对错都有return。

硬币找零2 lc 518

找到所有找零的方案。

class Solution:
    def change(self,amount, coins):
        def dfs(start_index, amount, coins,res):
            if amount == 0:
                res[0]+=1
                return
            if amount<0:
                return
            for i in range(start_index,len(coins)):
                dfs(i,amount-coins[i],coins,res)
        res = [0]
        start_index = 0
        dfs(start_index, amount, coins, res)
        return res[0]

数字键盘组合 lc 17

在这里插入图片描述

Input:Digit string “23”
Output: [“ad”, “ae”, “af”, “bd”, “be”, “bf”, “cd”, “ce”, “cf”].

import copy
class Solution:
    def __init__(self):
        self.letterMap = [
            ' ',
            '',
            'abc',
            'def',
            'ghi',
            'jkl',
            'mno',
            'pqrs',
            'tuv',
            'wxyz'
        ]

    def flash_back(self,digits,index,res,path):
        if index==len(digits):
            res.append(''.join(copy.deepcopy(path)))
            return
        char_ = digits[index]
        letters = self.letterMap[int(char_)]
        for i in range(0,len(letters)):
            path.append(letters[i])
            self.flash_back(digits,index+1,res,path)
            path.pop()

    def letterCombinations(self, digits):
        res = []
        path = []
        if not digits:
            return res
        # 不需要start,因为这个回溯的内容和具体的东西是分离的,只有都是自己才需要start
        self.flash_back(digits,0,res,path)
        return res

s = Solution()
print(s.letterCombinations('23'))
  • 回溯那是进行回溯的内容。
  • for的记录是记录要记录的内容,完全可以分离开,甚至不记录内容。
  • 这个题目非常好,很灵活的套用回溯的框架。

IP 地址划分 lc 93

有效的 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组

成,且不能含有前导 0),整数之间用 ‘.’ 分隔。

例如:“0.1.2.201” 和 “192.168.1.1” 是 有效的 IP 地址,但是
“0.011.255.245”、“192.168.1.312” 和 “192.168@1.1” 是 无效的 IP 地址。

Given “25525511135”,
return [“255.255.11.135”, “255.255.111.35”].
给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。
输入:s = “101023”
输出:[“1.0.10.23”,“1.0.102.3”,“10.1.0.23”,“10.10.2.3”,“101.0.2.3”]

class Solution:
    def restoreIpAddresses(self, s: str) -> List[str]:
        def isNum(string):
            if 0<=int(string)<=255 and str(int(string)) == string:
                return True
            return False

        def dfs(s,n,index,path,res):
            if n==0 and index == len(s):
                res.append('.'.join(copy.deepcopy(path)))
                return
            for i in range(index,len(s)):
                if isNum(s[index:i+1]):
                    path.append(s[index:i+1])
                    dfs(s,n-1,i+1,path,res)
                    path.pop()
                else:
                    break
        path = []
        res = []
        if not s or len(s)<4 or len(s)>12:
            return res
        dfs(s,4,0,path,res)    
        return res
  • 两个条件的回溯。
  • 问题解耦,需要遍历到字符串的结尾,需要满足四段,但是每一段是否是符合条件的数字组合。
  • 一个数字一个数字加上去不能判断是否是四段,想用这个框架就需要让数字形成一个坨即可。
  • 遍历字符串的方法,再循环内部的遍历+1,固定起始位置,让结尾往后推。本题和上一题一样,for循环的都不是仅仅的一个固定的内容,这个是找str
  • 首位数字不能为0的标准检查方法。num=num*10+c,s.lstrip(‘0’)

在矩阵中寻找字符串 lc 79

给定一个二维网格和一个单词,找出该单词是否存在于网格中。

单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
For example,
Given board =
[
[‘A’,‘B’,‘C’,‘E’],
[‘S’,‘F’,‘C’,‘S’],
[‘A’,‘D’,‘E’,‘E’]
]
word = “ABCCED”, -> returns true,
word = “SEE”, -> returns true,
word = “ABCB”, -> returns false.

棋盘终于和回溯见面了

class Solution:
    def __init__(self):
        self.dir = [(0, 1), (0, -1), (1, 0), (-1, 0)]

    def exist(self,board,word):
        if not word or not board:return False
        r = len(board)
        c = len(board[0])
        visited = [[False] * c for _ in range(r)]
        res=[False]
        for i in range(r):
            for j in range(c):
                if board[i][j] == word[0]:
                    self.dfs(board,word,i,j,0,visited,res)
                    #不管走通没走通,递归出来都要还原现场
                    visited[i][j] = False
        return res[0]

    def dfs(self,board,word,i,j,index,visited,res):
    #能进入递归说明可行,要设置该值
        visited[i][j] = True

        if index+1 == len(word):
            res[0] = True
            return

        for r,c in self.dir:
            nr = i+r
            nc = j+c
            if 0<=nr<len(board) and 0<=nc<len(board[0]) and visited[nr][nc]==False and index+1<len(word):
                if board[nr][nc]==word[index+1]:
                    self.dfs(board, word, nr, nc, index+1,visited,res)
                    if res[0]==True:
                        return
                    visited[nr][nc] = False

分割字符串使得每个部分都是回文数 lc131

For example, given s = “aab”,
Return
[
[“aa”,“b”],
[“a”,“a”,“b”]
]

这个题目能想到回溯本身就非常抽象,想到要遍历所有的所有。

import copy
class Solution:
    def partition(self,s):
        #131
        # 一共res要的个数,被加到某个值,字符串被遍历完
        #是一个比较复合的事情,所以要拆解开,回文,所有的
        if not s:
            return

        def dfs(start_index,s,path,res):
            #直接用这个指针,到底了就说明遍历好了
            if start_index==len(s):
                res.append(copy.deepcopy(path))
                return
            for i in range(start_index,len(s)):
                #问题拆解在这里,之前每一个解都是可用的,现在是只有符合条件的才能加入
                if s[start_index:i+1] == s[start_index:i+1][::-1]:
                    path.append(s[start_index:i+1])
                    dfs(i+1,s,path,res)
                    path.pop()
       	res = []
        start_index = 0
        path = []
        dfs(start_index, s, path, res)
        return res

  • 之前的题目都是解空间没问题,去找边界条件,这个题是解耦是每一次先去找到解空间,然后再遍历,很明显解空间是不重复的。
  • 字符串还是双指针,一个指向头一个指向尾部,然后往后去循环。

字符是否可以拼接成给定的文本 lc 139

给定一个非空字符串 s 和一个包含非空单词的列表 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。

说明:
拆分时可以重复使用字典中的单词。
你可以假设字典中没有重复的单词。

输入: s = “leetcode”, wordDict = [“leet”, “code”]
输出: true
解释: 返回 true 因为 “leetcode” 可以被拆分成 “leet code”。

输入: s = “applepenapple”, wordDict = [“apple”, “pen”] 输出: true 解释: 返回
true 因为 “applepenapple” 可以被拆分成 “apple pen apple”。
注意你可以重复使用字典中的单词。

输入: s = “catsandog”, wordDict = [“cats”, “dog”, “sand”, “and”, “cat”]
输出: false

class Solution:
    '''
    之前的想法是顺序思考,不断增长字符串,反向思考,不断去除列表中字符串长度,
    直到正好长度为0
    '''
    def wordBreak(self, s: str, wordDict: List[str]) -> bool:
        def dfs(s,res):
            if len(s)==0:
                res[0]=True
                return 
            for word in wordDict:
                if s.startswith(word):
                    dfs(s[len(word)::],res)
        res=[False]
        dfs(s,res)
        return res[0]
  • 和回文串一样,抽象成给定了一个解空间,然后去探索这个解空间能不能完成一个事情。
  • startswith是精髓,要不然长度还是非常麻烦的。
  • 和电话号码一样,dfs和遍历的内容不一样。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值