训练营:回溯篇

 回溯就是穷举,for循环的嵌套,有固定的结构

def track(参数,通常就是起点,结果):
  if 终止条件:
    return
  for 遍历:
    track(起点+1之类)

1、
77. 组合

经典的回溯题,主要是for里面的剪枝,能将时间从336降到40

def combine77(n, k) :
    def track(i,res):
        if len(res)==k:
            res2 = res.copy()
            all.append(res2)
            return
         for j in range(i,n+1-k+len(res)+1):
            res.append(j)
            track(j+1,res)
            res.pop()
    all =[]
    track(1,[])
    return all

2、
216. 组合总和 III

 跟前面套路一模一样,多了和的限制条件,就可以再剪枝的时候多一层判断

  def combinationSum3(self, k: int, n: int) -> List[List[int]]:
        def track(i,res):
            if sum(res)>n:
                return
            if len(res)==k:
                if sum(res)==n:
                    res2 = res.copy()
                    all.append(res2)
                return
            for j in range(i,10-k+len(res)+1):
                res.append(j)
                track(j+1, res)
                res.pop()
        all=[]
        track(1,[])
        return all

3、
17. 电话号码的字母组合

终止条件:就是长度=数字长度 

回溯的点:要找到for,就是数字,不一样的点就是还要对数字对应的字符遍历,所以里面还有一层

 
 def track(res,s):
        if len(res)==len(digits):
            all.append("".join(res))
            return
        for i in range(s,len(digits)):
            tmp = dict[digits[i]]
            for j in range(0,len(tmp)):
                res.append(tmp[j])
                track(res,i+1)
                res.pop()

 这么看好像上面的不对,,,嗯,,还是有点理不清楚

    def track1(res,s):
        if len(res)==len(digits):
            all.append("".join(res))
            return
        tmp = dict[digits[s]]
        for j in range(len(tmp)):
            res.append(tmp[j])
            track(res,s+1)
            res.pop()

4、
39. 组合总和

 从大到小排比从小到大排快的多,72到48,因为会少很多2,2,往大找的遍历

def combinationSum39(candidates, target):
    def track(sum,res,s):
        if sum>=target:
            if sum==target:
                all.append(res.copy())
            return []
        for i in range(s,len(candidates)):
            res.append(candidates[i])
            sum = sum+candidates[i]
            track(sum,res,i)
            res.pop()
            sum = sum-candidates[i]

    all=[]
    candidates = sorted(candidates,reverse=True)
    track(0,[],0)

    return all

5、
40. 组合总和 II

与前面的题不同,多了一个去重,如果用 not in 判断,会超出时间限制

所以需要加if,注意if的判断是i>s,不是i>0,

def combinationSum40(candidates, target):
    def track(sum, res, s):
        if sum == target:
            if res not in all:
                all.append(res.copy())
            return
        if sum > target:
            return
        for i in range(s, len(candidates)):
            if i > s and candidates[i] == candidates[i - 1]:
                continue
            sum = sum + candidates[i]
            res.append(candidates[i])
            track(sum, res, i + 1)
            sum = sum - candidates[i]
            res.pop()

    all = []
    candidates = sorted(candidates)
    track(0, [], 0)
    return all

5、
131. 分割回文串

 这题一开始没想明白,以为是求所有组合,关键是遍历分割线的位置,一直到最后终止

def partition131(s):
    def track(res,st):
        if st==len(s):
            all.append(res.copy())
            return
        for i in range(st+1,len(s)+1):
            tmp = s[st:i]
            if tmp==tmp[::-1]:
                res.append(tmp)
                track(res,i)
                res.pop()

    all = []
    track([],0)
    return all

6、
93. 复原 IP 地址

 跟分割回文串的思路一样 ,就是遍历分割的位置,外面加一层len判断,速度从48到40,但里面加一次不知道为什么反而更慢了,变成了52,没想明白,总感觉速度和网速有关系

def restoreIpAddresses9302( s):
    def track(res,st):
        if st==len(s):
            if len(res)==4:
                all.append(res[0]+"."+res[1]+"."+res[2]+"."+res[3])
            return
        if len(res)>3:
            return
  
        for i in range(st+1,len(s)+1):
            tmp = s[st:i]
            if len(tmp)>1 and int(tmp[0])==0:
                break
            if int(tmp)<256:
                res.append(str(tmp))
                track(res,i)
                res.pop()

    all = []
    if len(s)>12:
        return all
    track([],0)
    return all

      if len(s)-st>(4-len(res))*3:
                return

7、
78. 子集

 这没啥好说的就是最基本的回溯

  def subsets(self, nums: List[int]) -> List[List[int]]:
        def track(res,i):
            #if res not in all:
            all.append(res.copy())
            for j in range(i,len(nums)):
                res.append(nums[j])
                track(res,j+1)
                res.pop()
        all = []
        track([],0)
        return all

8、
​​​​​​90. 子集 II

 比前面的题多了一个去重,同层一样的话就跳过

def subsetsWithDup90(nums):
    def track(res, i):
        if res not in all:
            all.append(res.copy())
        for j in range(i, len(nums)):
            if j>i and nums[j]==nums[j-1]:
                continue
            res.append(nums[j])
            track(res, j + 1)
            res.pop()

    all = []
    nums = sorted(nums)
    track([], 0)
    return all

9、
332. 重新安排行程

感觉这题的关键是先排序,如果不排序所有的找完再排序找最小组合,会超时,,先排序,有一个结果就退出

刚开始通过删增的方式,要注意插入的位置,避免重复

题解用的use感觉更方便一点,思路差不多,好像也没有抓住这道题的主要方法

def findItinerary332(tickets):
    def track(rest,res,cur):
        if len(rest)==0:
            res.append(cur)
            if res not in all:
                all.append(res.copy())
            res.pop()
            return
        for i in range(len(rest)):
            #if i==1 and cur=="JFK":
            if len(rest)==5:
                print(cur)
            tmp = rest[i]
            if tmp[0]==cur:
                res.append(cur)
                #res.append(tmp[1])
                rest.pop(i)
                track(rest,res,tmp[1])
                if len(all)>0:
                    break
                rest.insert(i,tmp)
                cur = res.pop()

    tickets = sorted(tickets)
    rest = tickets
    all=[]
    track(rest, [], "JFK")
    all = sorted(all)
    print(all)
    return all[0]
    #(all)

10、
51. N 皇后

终点是判断isvaild,for遍历是对列,自己写是list转str,应该直接写成str,但发现写成str也没有快

def solveNQueens51(n):
    def isvaild(res,i,j):
        for ki in range(n):
            if res[ki][j]=="Q":
                return False
            if res[i][ki]=="Q":
                return False
        ki = i-1
        kj = j-1
        while(ki>-1 and kj>-1):
            if res[ki][kj]=="Q":
                return False
            ki = ki -1
            kj = kj-1
        ki = i-1
        kj = j+1
        while(ki>-1 and kj

11、
37. 解数独

好难啊,真的不会,看题解都要看几遍

def solveSudoku37(board):
    def isvalid(board, i, si, sj):
        for ki in range(9):
            if board[ki][sj]==str(i):
                return False
            if board[si][ki] == str(i):
                return False
        startRow = (si // 3) * 3
        startCol = (sj // 3) * 3
        for ki in range(startRow,startRow + 3):
            for kj in range(startCol,startCol + 3):
                if (board[ki][kj] == str(i) ):
                    return False
        return True


    def track(board):
        for i in range(9):
            for j in range(9):
                if board[i][j]!=".":
                    continue
                for k in range(1,10):
                    if isvalid(board,k,i,j):
                        board[i][j] = str(k)
                        if track(board): return True
                        board[i][j]="."
                return False
        print(board)
        return True
    # for i in range(9):
    #     res.append([0] * 9)
    track(board)
    print(board)
    return True

总结:

回溯三部曲,终止条件,遍历的内容,按照模板写就行,

你可能感兴趣的:(代码随想录训练营,python,leetcode,回溯)