LeetCode | Backtracking

组合问题

77. Combinations

class Solution:
    def combine(self, n, k):
        res = []
        def dfs(k, tmp, start):
            if k == 0:
                res.append(tmp.copy())
                return

            for num in range(start, n + 1):
                tmp.append(num)
                dfs(k - 1, tmp, num + 1)
                tmp.pop()

        dfs(k, [], 1)
        return res

227. Basic Calculator II

class Solution:
    def combinationSum3(self, k, n):
        res = []
        def dfs(k, start, sum, tmp):
            if k == 0:
                if sum == 0:
                    res.append(tmp.copy())
                return

            for i in range(start, 10):
                tmp.append(i)
                dfs(k-1, i+1, sum-i, tmp)
                tmp.pop()
        dfs(k, 1, n, [])
        return res

    def combinationSum3_2(self, k: int, n: int) -> List[List[int]]:
        res = []
        def dfs(k, start, sum, tmp):
            if k == 0:
                if sum == 0:
                    res.append(tmp.copy())
                return

            for i in range(start, 10):
                # 剪枝
                if k > 1 and sum - i <= 0 :
                    continue
                else:
                    tmp.append(i)
                    dfs(k-1, i+1, sum-i, tmp)
                    tmp.pop()
        dfs(k, 1, n, [])
        return res

17. Letter Combinations of a Phone Number

class Solution:
    def letterCombinations(self, digits):
        if digits == '':
            return []

        dic = {'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']}

        res = []
        def dfs(index, tmp):
            if index == len(digits):
                res.append(tmp)
                return

            for char in dic[digits[index]]:
                tmp += char
                dfs(index+1, tmp)
                tmp = tmp[:-1]
        dfs(0, '')
        return res

39. Combination Sum

class Solution:
    def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
        res = []
        candidates = sorted(candidates)  # 剪枝需要
        def dfs(start, target, tmp):
            if target == 0:
                res.append(tmp.copy())
                return

            for index in range(start, len(candidates)):
           		# 剪枝
                if target - candidates[index] < 0:
                    break
                tmp.append(candidates[index])
                dfs(index, target-candidates[index], tmp)
                tmp.pop()
        dfs(0, target, [])
        return res

40. Combination Sum II

  • 只能用一次!
    • 去重trick:搜过的就不用再搜了,continue掉;ps,先sort,那么就是跟前面对比下一不一样就知道搜过没了;
    • if index > start and candidates[index - 1] == candidates[index]: continue!!!重要!
    • 另外剪枝了
      1. 因为只能用一次,判断下当前到最后还够不够,一个点不够了,后面的点就都不够了;break掉;
      2. 和超过target;由于sort过了,所以break掉;
class Solution:
    def combinationSum2(self, candidates, target):
        res = []
        candidates = sorted(candidates)
        def dfs(start, target, tmp):
            if target == 0:
                if tmp not in res:
                    res.append(tmp.copy())
                return

            for index in range(start, len(candidates)):
                if sum(candidates[start:]) < target:
                    break
                if target - candidates[index] < 0:
                    break
                if index > start and candidates[index - 1] == candidates[index]:
                    continue
                tmp.append(candidates[index])
                dfs(index+1, target-candidates[index], tmp)
                tmp.pop()
        dfs(0, target, [])
        return res

分割问题

131. Palindrome Partitioning

在这里插入图片描述

2022-5-1:五一好!今天想把backtracking攻下来耶!好吧!

先说了,又掉进🕳里了,这里树杈的的划分是按当前元素到结尾的划分case:如上图:aab当前元素假设是第一个a,那么我有三种划分方式a,aa.aab;是吧!我掉什么🕳里了呢?跟集合元素要不要那种🕳想搞二叉哈哈哈哈,后来发现tmp不好加;

class Solution:
    def partition(self, s: str) -> List[List[str]]:
        res, tmp = [], []
        
        def dfs(i):
            if i == len(s):
                res.append(tmp.copy())
            
            for j in range(i, len(s)):
                if self.helper(s, i, j):
                    tmp.append(s[i:j+1])  # 不要以为你变了我就认不出来了,我的确第一次没认出来!哭!
                    dfs(j+1)
                    tmp.pop()
        dfs(0)
        return res
    
    def helper(self, s, i, j):
    	# 基操双指针~
        left, right = i, j
        while left < right:
            if s[left] != s[right]:
                return False
            left, right = left + 1, right - 1
        return True

93. Restore IP Addresses

  • 几个点需要处理:
    1. 每个数字都要用到,且树高为4,所以以k>4i==len(s)作为判断条件;
    2. 不是个位数的前导零的处理;
    3. debug的时候出现ValueError: invalid literal for int() with base 10 的情况,测试示例为"101023",存在情况当tmp=[10,10,23],此时树深没有达到要求,仍会往下走,看了一下应该是在单层循环时for j in range(i, i+4)这里出现问题,有可能j会超出len(s),所以下面加了判断条件超过了就可以break;
class Solution:
    def restoreIpAddresses(self, s: str) -> List[str]:
        res, tmp = [], []

        def dfs(i, k):
            if k > 4:
                if i == len(s):
                    res.append('.'.join(tmp.copy()))
                return

            for j in range(i, i+4):
                if j == len(s):  # 剪枝
                    break
                if self.helper(s[i:j+1]):
                    tmp.append(s[i:j+1])
                    dfs(j+1, k+1)
                    tmp.pop()
        dfs(0, 1)
        return res

    def helper(self, s):
        if len(s) > 1 and s[0] == '0':
            return
        if 0 <= int(s) <= 255:
            return True

子集问题

78. Subsets

  • 解法2:区别于组合和分割的解法(restore的根节点),这里restore的是所有节点!妙蛙简直大一统!
class Solution:
    def subsets(self, nums):
        res, tmp = [], []

        def dfs(i):
            if i == len(nums):
                res.append(tmp.copy())
                return
                
            tmp.append(nums[i])
            dfs(i+1)

            tmp.pop()
            dfs(i+1)

        dfs(0)
        return res

    def subsets2(self, nums):
        res, tmp = [], []

        def dfs(i):
            res.append(tmp.copy())
            if i == len(nums):
                return

            for j in range(i, len(nums)):
                tmp.append(nums[j])
                dfs(j + 1)
                tmp.pop()

        dfs(0)
        return res

90. Subsets II

  • 两个注意点:
    1. 需要sorted;[辅助后面剪枝]
    2. 同层判断是否前面有重复元素,剪枝;
class Solution:
    def subsetsWithDup(self, nums):
        res, tmp = [], []
        nums = sorted(nums)

        def dfs(i):
            res.append(tmp.copy())
            if i == len(nums):
                return

            for j in range(i, len(nums)):
                if j > i and nums[j] == nums[j-1]:
                    continue
                tmp.append(nums[j])
                dfs(j+1)
                tmp.pop()
        dfs(0)
        return res

491. Increasing Subsequences

逐渐上瘾哈哈哈哈哈

  • 几个注意点:
    1. code中标记为1处:res加的还是叶子节点,不过需要len至少为2的叶子节点;
    2. code中标记为2处:判断不是递增序列,由于是递归所以只需和当前tmp的最后一位check一下就够了;
    3. code中标记为3处:解决重复元素剪枝问题;
class Solution:
    def findSubsequences(self, nums: List[int]) -> List[List[int]]:
        res, tmp = [], []

        def dfs(i):
            if len(tmp) > 1:                            # 1. 
                res.append(tmp.copy())
            if i == len(nums):
                return

            for j in range(i, len(nums)):
                if len(tmp) > 0 and nums[j] < tmp[-1]:  # 2.
                    continue
                if nums[j] not in nums[i:j]:            # 3.
                    tmp.append(nums[j])
                    dfs(j+1)
                    tmp.pop()

        dfs(0)
        return res

排列问题

46. Permutations

  • 排列问题:不用再有startindex,因为是排列(顺序无关);
class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        res, tmp = [], []

        def dfs(k):
            if k == len(nums):
                res.append(tmp.copy())

            for j in range(len(nums)):  # 1.
                if nums[j] not in tmp:  # 2. 
                    tmp.append(nums[j])
                    dfs(k + 1)
                    tmp.pop()

        dfs(0)
        return res

47. Permutations II

  • 这道题有点意思,去重真的哈哈哈哈
  • 困难的点在于哪里呢:
    1. 全排列;
    2. 存在重复元素;
  • sorted基操了,然后需要注意的是不仅仅要判断当前和之前的元素是不是相同的,因为是全排列所以当之前的元素访问过了说明现在是下一层,而全排列的下一层是可以访问相同元素(重复的元素,index不同);但是如果是当前层之前的元素的visited的值为0,由于是在之前就已经restore好了结果,之后回溯恢复了现场哈哈哈,就是说当前层的若又重复元素则不用考虑!
    • visited[j-1]==0用的好!!!
class Solution:
    def permuteUnique(self, nums: List[int]) -> List[List[int]]:
        res, tmp = [], []
        nums = sorted(nums)
        visited = [0] * len(nums)

        def dfs(k):
            if k == len(nums):
                res.append(tmp.copy())

            for j in range(len(nums)):
                if j > 0 and nums[j] == nums[j-1] and visited[j-1] == 0:
                    continue
                if visited[j]==0:
                    tmp.append(nums[j])
                    visited[j] = 1
                    dfs(k+1)
                    visited[j] = 0
                    tmp.pop()
        dfs(0)
        return res

332. Reconstruct Itinerary

  • 跟的nc
  • 几个点注意下:
    1. 存在多种有效的行程,需要按字典排序;即在构建树时每个节点先进行排序,那么先搜索到的路径(一条就够了)就是最后的答案;
    2. 维护一个adj字典:key为当前的城市,value为当前城市可以飞到的城市list;这里又有trick:

ex:adj=[city1:[city2, city3]]

  • 第一个是当在key=city1按已经sorted的value中选择了第一个元素city2,那么当飞机又飞到city1时此时我们就不能再选择city2了,所以我们应该把他从adj[city1]中删除;
  • 但是考虑另一种情况,当我们选择的这个city2他不能飞到任何城市,而此时还没到结束的时候;既然我们先从city2开始dfs发现此路不通,于是我们要开始恢复现场,而为了保证下一次需要从city3开始dfs,同时我们也需要city1访问到city2!该怎么恢复city2才能让下次先从city3开始同时city2还在呢?
    BINGO!把city2添加到list尾部就好啦!【所以实际上是一个queue哈】

说了这么多不如放图

在这里插入图片描述
说的就是这个特殊情况

class Solution:
    def findItinerary(self, tickets: List[List[str]]) -> List[str]:
        adj = {u: [] for u, _ in tickets}
        for u, v in sorted(tickets):
            adj[u].append(v)

        res = ['JFK']

        def dfs(cur):
            if len(res) == len(tickets) + 1:  # Happy Ending ~
                return True
            if cur not in adj:           # 没结束,但是此路不通
                return False

            tmp = list(adj[cur])
            for city in tmp:
                res.append(city)
                adj[cur] = adj[cur][1:]  # 飞过了,暂时删了你
                if dfs(city):            # 有一条成了就返回吧
                    return res
                res.pop()  				 # 恢复现场
                adj[cur].append(city)

        dfs('JFK')
        return res

棋盘问题

79. Word Search

  • 哭了 花费好多内存哈哈哈不过没有看啥思路自己尝试了一下~竟然可以!!!中间去debug两次记录一下出错的情况以及怎么去debug呀!其实我感觉debug的过程也是一种在误差中学习的感觉,给你一个feedback去penalty哈哈哈,又开始伪哲学,但是其实很多case比如cost function我们要想找到的是假设空间中的局部最优解,梯度下降的过程其实也是在误差中学习撒,哔哔赖赖已上线orz,自动屏蔽![反正也没人看 擅长自娱自乐哈哈哈哈]

  • ver1

    1. 首先是去找第一个word的char,也就是说我们要找到树根的位置,注意到board里是可以存放重复元素的,所以打算用start这个list存放所有根节点的index位置;
    2. visited这个数组存的是否在这棵树的这条路劲被访问了,注意哈这里有两个恢复现场的地方:一个是对根节点(这棵树行不行,他不当根节点还可以当别的树的非根节点呀),一个是对非根节点(这个节点被访问过了没,访问过就不用dfs了pass你);
    3. dfs中选择用tmp去存当前节点的所有可能的分支(一般来说是上下左右,但是考虑边界以及是否被访问过,估计会有更简洁的写法蹲蹲);
    4. dfs的终止条件:第一个是成功找到了,还有一个是还没check完所有的char,但是当前的char它没有分支了(tmp==[]),就是此路不通了,回溯吧那就,去别的节点看看~

debug:

  1. [[‘a’,‘a’]] ‘aaa’:查看了是根节点的visited忘记设置了;
  2. [[“C”,“A”,“A”],[“A”,“A”,“A”],[“B”,“C”,“D”]]
    “AAB”:忘记把前一个根节点的visited回退了SAD
class Solution:
    def exist(self, board: List[List[str]], word: str) -> bool:
        visited = [[0 for _ in range(len(board[0]))] for _ in range(len(board))]
        start = []
        # 找第一个元素的位置
        for i in range(len(board)):
            for j in range(len(board[0])):
                if board[i][j] == word[0]:
                    start.append([i, j])

        def dfs(i, j, k):
            tmp = []
            if i - 1 >= 0 and visited[i - 1][j] == 0:
                tmp.append([i - 1, j])
            if i + 1 < len(board) and visited[i + 1][j] == 0:
                tmp.append([i + 1, j])
            if j - 1 >= 0 and visited[i][j - 1] == 0:
                tmp.append([i, j - 1])
            if j + 1 < len(board[0]) and visited[i][j + 1] == 0:
                tmp.append([i, j + 1])

            if k == len(word):
                return True
            if tmp == []:
                return False

            for index in tmp:
                if board[index[0]][index[1]] == word[k] and visited[index[0]][index[1]] == 0:
                    visited[index[0]][index[1]] = 1  # 访问这个节点
                    if dfs(index[0], index[1], k + 1):
                        return True
                    visited[index[0]][index[1]] = 0  # 这个节点不行,恢复现场
            return False

        # 对每个可能的位置
        for index in start:
            visited[index[0]][index[1]] = 1 # 访问根
            if dfs(index[0], index[1], 1):
                return True
            visited[index[0]][index[1]] = 0 # 这棵树不行,恢复现场
        return False

51. N-Queens

  • 衰卡在一个点上没过去:在nc的解法下豁然开朗
  • 皇后所在的行列对角线的位置需要更新,不需要访问;
    • 卡住的点是:如何存放对角线的信息,需要存放index吗?如果把行列对角线的所有不需要访问的index放在一起,回溯的时候假设是当前对角线的位置恢复了,但是实际上是前一个皇后的列位置,这个时候位置是不能恢复的,该如何处理呢?
    • nc的思路是单独存放列,对角线1,对角线2的信息;然后他有个trick很有意思,对对角线的处理妙蛙不用存放index了,用对角线的性质:
      • diag1:行列index之差为某个数 save到set diag1中;
      • diag2:行列index之和为某个数 save到set diag2中;
        在这里插入图片描述
        在这里插入图片描述

记一下这题还有set的初始化,add和remove操作,之前用的少,记录一哈!

  • 另 修改现场和恢复现场 有丢丢感觉哈哈哈可以!
class Solution:
    def solveNQueens(self, n):
        res = []
        board = [['.'] * n for _ in range(n)]
        col = set()
        diag1 = set()
        diag2 = set()

        def dfs(r):
            if r == n:
                tmp = ["".join(row) for row in board]
                res.append(tmp)
                return

            for c in range(n):
                if c in col or (r + c) in diag1 or (r - c) in diag2:
                    continue

                board[r][c] = 'Q'
                col.add(c)
                diag1.add(r + c)
                diag2.add(r - c)

                dfs(r + 1)

                board[r][c] = '.'
                col.remove(c)
                diag1.remove(r + c)
                diag2.remove(r - c)

        dfs(0)
        return res

暂时完结 撒花~

晕感觉快把层序遍历忘完了【2022-5-2】

“平凡的我啊,哪有时间低头回望啊”
小排球田中仙贝的这句话好戳啊,飛べ!

22. Generate Parentheses

我又回来了orz
nc把这道题归在了stack那里,回溯做了一版

在这里插入图片描述

  • 看上图就知道是个什么回事了:
    1. 两个变量l,r分别记录当前左右括号的个数,终止条件为当左括号个数达到n(右括号补齐就好了);
    2. 剪枝是考虑到符合条件的括号的情况,若当前左右括号数相等的时候只需要选择加左括号,即加右括号剪枝(啪!没了)
class Solution:
    def generateParenthesis(self, n):
        res = []
        path = '('

        def dfs(l, r, path):
            if l == n:
                tmp = path + ')' * (n-r)
                res.append(tmp)
                return
            
            for i in [0, 1]:
                if l == r and i == 1:
                    continue
                if i == 0:
                    path += '('
                    l += 1
                    dfs(l, r, path)
                    path = path[:-1]
                    l -= 1
                if i == 1:
                    path += ')'
                    r += 1
                    dfs(l, r, path)
                    path = path[:-1]
                    r -= 1
        dfs(1, 0, path)
        return res
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值