回溯法(以DFS为例)例题总结

回溯法(以DFS为例)例题总结

框架:

		#定义一个结果数组
		self.result = []
        def dfs(candidates,res):
			#满足题目,返回条件
            if target==sum(res):
                self.result.append(res[:])
            for i in range(len(candidates)):
            	#临时结果添加
                res.append(candidates[i])
                #下一步的访问路径(非必须,若无则是原数组)
                list1 = candidates[i:]
                #递归
                dfs(list1,res)
                #移除选择
                res.remove(candidates[i])
        #调用函数
        dfs(candidates,[])
        return self.result

至于什么时候用self.result.append(res[:]),什么时候用self.result.append(res),取决于你的调用函数的传入参数,若传入参数中有res时,就可以使用self.result.append(res),若没有就只能使用self.result.append(res[:]).

例题一:

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
说明:
所有数字(包括 target)都是正整数。
解集不能包含重复的组合。
示例 1:
输入: candidates = [2,3,6,7], target = 7,
所求解集为:
[
[7],
[2,2,3]
]
示例 2:
输入: candidates = [2,3,5], target = 8,
所求解集为:
[
[2,2,2,2],
[2,3,3],
[3,5]
]

class Solution:
    def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
        self.result = []
        def dfs(candidates,res):
        	#因为我们的目标是target==当前res的和,
        	#所以只要当前res和大于target就应该return
            if target<sum(res):
                return 
            if target==sum(res):
                self.result.append(res[:])
            for i in range(len(candidates)):
                res.append(candidates[i])
                list1 = candidates[i:]
                dfs(list1,res)
                res.remove(candidates[i])
        dfs(candidates,[])
        return self.result

例题二:

若上面的数组中有重复数字,且解集不能有重复数组。

输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]

代码:(条件判断和排序操作是为了防止结果数组中有重复,list1 = candidates[i+1:]是为了不反复取当前元素)

class Solution:
    def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
        self.result = []
        def dfs(candidates,res):
            if target < sum(res):
                return 
            if target == sum(res):
                self.result.append(res[:])
            for i in range(len(candidates)):
                #数组常见去重复的方法,对于重复的数值,
                #我们只让第一个进入循环,后面的就不要再进入循环了
                if i > 0 and candidates[i-1] == candidates[i]:  
                    continue
                res.append(candidates[i])
                list1 = candidates[i+1:]
                dfs(list1,res)
                res.remove(candidates[i])
        #必须排序,这样前一位等于后一位的时候就是重复了!
        candidates.sort() 
        dfs(candidates,[])
        return self.result

例题三:(标准套路)

给定一个 没有重复 数字的序列,返回其所有可能的全排列。
示例:
输入: [1,2,3]
输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]

class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        self.result = []
        size = len(nums)
        def dfs(res):
            if len(res)==size:
                self.result.append(res[:])
                return
            for i in range(len(nums)):
                if nums[i] in res:
                    continue
                res.append(nums[i])
                dfs(res)
                res.pop()
        dfs([])
        return self.result

例题四:(与上面的去重不同的是,全排列问题,每次返回的数组还是原数组)

给定一个可包含重复数字的序列,返回所有不重复的全排列。
示例:
输入: [1,1,2]
输出:
[
[1,1,2],
[1,2,1],
[2,1,1]
]

class Solution:
    def permuteUnique(self, nums: List[int]) -> List[List[int]]:
        self.result = []
        size = len(nums)
        def dfs(res):
            if len(res)==size:
                self.result.append(res[:])
                return
            for i in range(len(nums)):
                #去掉相邻重复元素数组
                if i>0 and nums[i]==nums[i-1] and not list1[i-1]:
                    continue
                if list1[i]:
                    continue
                 #是否选用,建立标志位
                list1[i] = True
                res.append(nums[i])
                dfs(res)
                list1[i] = False
                res.pop()
        list1 = [False for _ in range(len(nums))]
        nums.sort()
        dfs([])
        return self.result

特殊一:

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
示例:
输入:n = 3
输出:[
“((()))”,
“(()())”,
“(())()”,
“()(())”,
“()()()”
]

class Solution:
    def generateParenthesis(self, n: int) -> List[str]:
        res = []
        curstr = ''

        def dfs(curstr,left,right):
            if left==0 and right == 0:
                res.append(curstr)
                return res
            if left>0:
                dfs(curstr+'(',left-1,right)
            if left<right:
                dfs(curstr+')',left,right-1)
            return curstr
        dfs(curstr,n,n)
        return res

特殊二:

N皇后问题

class Solution:
    def __init__(self):
        self.result = list()
        self.num = 0
    def solveNQueens(self, n: int) -> List[List[str]]:
        board = ['.'*n] *n
        self.backTrace(board,0)
        return self.result      
    def backTrace(self,board,row):
        n = len(board)
        if row == n:
            # print('board:',board)
            self.result.append(board[:])
            self.num +=1
            return
        for col in range(n):
            if not self.isValid(board,row,col):
                continue
            board[row] = '.'*col + 'Q' + '.'*(n-col-1)
            self.backTrace(board,row+1)
            board[row] = '.'*n
    def isValid(self,board,row,col):
        n = len(board)
        #列
        for i in range(n):
            if board[i][col] == 'Q':
                return False
        #右上角
        for i,j in zip(range(row-1,-1,-1), range(col+1,n)):
            if board[i][j] == 'Q':
                return False
        #左上方
        for i, j in zip(range(row - 1, -1, -1), range(col-1, -1,-1)):
            if board[i][j] == 'Q':
                return False
        return True

特殊四:(类似于特殊1)

给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。
有效的 IP 地址正好由四个整数(每个整数位于 0 到 255 之间组成),整数之间用 ‘.’ 分隔。
示例:
输入: “25525511135”
输出: [“255.255.11.135”, “255.255.111.35”]

class Solution:
    def restoreIpAddresses(self, s: str) -> List[str]:
        res = []
        if len(s)>12 or len(s)<4:
            return []
        def isvalid(ip):
            if ip.count('.') != 3:
                return False
            x = ip.split('.')
            for i in x:
                if not i or int(i)>255 or (len(i)>1 and i[0]=='0'):
                    return False
            return True
        def dfs(cur,idx,cnt):
            if cnt == 3:
                if isvalid(cur):
                    res.append(cur[:])
                return 
            if idx > len(cur)-1:
                return
            dfs(cur[:idx]+'.'+cur[idx:],idx+2,cnt+1)
            dfs(cur,idx+1,cnt)
        dfs(s,0,0)
        return res

求和为n,且元素的最大值小于k大于d的所有组合。(不去重)

不去重就是说:112和211是两个组合。

n,k,d = list(map(int,input().strip().split()))
res = 0
def dfs(n,k,d,flag):
    if n==0 and flag:
        return 1
    if n<0:
        return 0
    for i in range(1,k+1):
        if i>=d:
            flag = True
        res += dfs(n-i,k,d,flag)
    return res
print(dfs(n,k,d,False))

去重的写法,之所以循环中是start是为了去重(只选择最后一位的数字大小,上面不去重的写法是随便选择一个数字。)

def helper():
    log = []
    def calc(n,start,k,d,flag):
        if n==0 and flag:
            print(log)
        else:
            for i in range(start,n+1):
                if i>=d and i<=k:
                    flag = True
                else:
                    flag = False
                log.append(i)
                calc(n-i,i,k,d,flag)
                log.pop()
    calc(5,1,3,2,0)
print(helper())

934 最短的桥

在给定的二维二进制数组 A 中,存在两座岛。(岛是由四面相连的 1 形成的一个最大组。)
现在,我们可以将 0 变为 1,以使两座岛连接起来,变成一座岛。
返回必须翻转的 0 的最小数目。(可以保证答案至少是 1 。)
示例 1:
输入:A = [[0,1],[1,0]]
输出:1
示例 2:
输入:A = [[0,1,0],[0,0,0],[0,0,1]]
输出:2
示例 3:
输入:A = [[1,1,1,1,1],[1,0,0,0,1],[1,0,1,0,1],[1,0,0,0,1],[1,1,1,1,1]]
输出:1

class Solution:
    def shortestBridge(self, grid: List[List[int]]) -> int:
        row = len(grid)
        col = len(grid[0])
        queues = collections.deque()
        find = False
        def bfs(i,j):
            if i<0 or i>=row or j<0 or j>=col or grid[i][j] == 0:
                return 
            grid[i][j] = 0
            queues.append((i,j))
            for x,y in [(i-1,j),(i+1,j),(i,j-1),(i,j+1)]:
                bfs(x,y)
        for i in range(row):
            for j in range(col):
                if grid[i][j] == 1 and not find:
                    bfs(i,j)
                    find = True
        seen = set(queues)
        level = 0
        while queues:
            size = len(queues)
            for s in range(size):
                (l,r) = queues.popleft()
                for x,y in [(l-1,r),(l+1,r),(l,r-1),(l,r+1)]:
                    if x<0 or x>=row or y<0 or y>=col or (x,y) in seen:
                        continue
                    if grid[x][y] == 0:
                        queues.append((x,y))
                        seen.add((x,y))
                    else:
                        return level
            level +=1
        return level

113 路径总和②

给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。
叶子节点 是指没有子节点的节点。
示例 1:
输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
输出:[[5,4,11,2],[5,8,4,5]]
在这里插入图片描述
示例 2:
输入:root = [1,2,3], targetSum = 5
输出:[]
在这里插入图片描述
示例 3:
输入:root = [1,2], targetSum = 0
输出:[]

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def pathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]:
        res = []
        result = []
        def dfs(root,targetSum):
            if not root:
                return 0
            targetSum -= root.val
            res.append(root.val)
            if not root.left and not root.right:
                if targetSum == 0:
                    result.append(res[:])
            dfs(root.left,targetSum)
            dfs(root.right,targetSum)
            res.pop()
        dfs(root,targetSum)
        return result

124 二叉树中的最大路径和

路径 被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。
路径和 是路径中各节点值的总和。
给你一个二叉树的根节点 root ,返回其 最大路径和 。
在这里插入图片描述

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def maxPathSum(self, root: TreeNode) -> int:
        res = []
        max_pro = -inf
        def dfs(root,max_pro):
            if not root:
                return 0
            left = max(dfs(root.left,max_pro),0)#注意要与0取max
            right = max(dfs(root.right,max_pro),0)

            temp_max = root.val + left + right#储存临时最大值
            max_pro = max(max_pro,temp_max)
            res.append(max_pro)
            return root.val + max(left,right)#递归计算中的最大值
        dfs(root,max_pro)
        return max(res)

685 冗余连接②

在本问题中,有根树指满足以下条件的 有向 图。该树只有一个根节点,所有其他节点都是该根节点的后继。该树除了根节点之外的每一个节点都有且只有一个父节点,而根节点没有父节点。
输入一个有向图,该图由一个有着 n 个节点(节点值不重复,从 1 到 n)的树及一条附加的有向边构成。附加的边包含在 1 到 n 中的两个不同顶点间,这条附加的边不属于树中已存在的边。
结果图是一个以边组成的二维数组 edges 。 每个元素是一对 [ui, vi],用以表示 有向 图中连接顶点 ui 和顶点 vi 的边,其中 ui 是 vi 的一个父节点。
返回一条能删除的边,使得剩下的图是有 n 个节点的有根树。若有多个答案,返回最后出现在给定二维数组的答案。
在这里插入图片描述
在这里插入图片描述

class UnionFind:
    def __init__(self,n):
        self.parent = list(range(n))
    def find(self,node):
        if self.parent[node] != node:
            self.parent[node] = self.find(self.parent[node])
        return self.parent[node]
    def union(self,node1,node2):
        self.parent[self.find(node1)] = self.find(node2)
            
class Solution:
    def findRedundantDirectedConnection(self, edges: List[List[int]]) -> List[int]:
        count = len(edges)
        uf = UnionFind(count+1)
        parent = list(range(count+1))
        conflict = -1
        cycle = -1
        for idx,(x,y) in enumerate(edges):
            #节点重复出现,导致冲突,对应示例1
            if parent[y] != y:
                conflict = idx
            else:
                parent[y] = x
                #祖先相同,环路出现,对应示例1和示例2
                if uf.find(x) == uf.find(y):
                    cycle = idx
                else:
                    uf.union(x,y)
        if conflict<0:
            return [edges[cycle][0],edges[cycle][1]]
        else:
            conflictedge = edges[conflict]
            if cycle>0:
                return [parent[conflictedge[1]],conflictedge[1]]
            else:
                return [conflictedge[0],conflictedge[1]]

332 重新安排行程

给你一份航线列表 tickets ,其中 tickets[i] = [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。
所有这些机票都属于一个从 JFK(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK 开始。如果存在多种有效的行程,请你按字典排序返回最小的行程组合。
例如,行程 [“JFK”, “LGA”] 与 [“JFK”, “LGB”] 相比就更小,排序更靠前。
假定所有机票至少存在一种合理的行程。且所有的机票 必须都用一次 且 只能用一次。
在这里插入图片描述
在这里插入图片描述

class Solution:
    def findItinerary(self, tickets: List[List[str]]) -> List[str]:
        tickets_dict = defaultdict(list)
        path = []
        for item in tickets:
            tickets_dict[item[0]].append(item[1])
        n = len(tickets)
        ori_point = 'JFK'
        path.append(ori_point)
        def dfs(ori_point):
            if len(path) == n+1:
                return True
            tickets_dict[ori_point].sort()
            for _ in tickets_dict[ori_point]:
                new_point = tickets_dict[ori_point].pop(0)
                path.append(new_point)
                if dfs(new_point):
                    return True
                path.pop()
                tickets_dict[ori_point].append(new_point)
        dfs(ori_point)
        return path

365 水壶问题

有两个容量分别为 x升 和 y升 的水壶以及无限多的水。请判断能否通过使用这两个水壶,从而可以得到恰好 z升 的水?
如果可以,最后请用以上水壶中的一或两个来盛放取得的 z升 水。
你允许:
装满任意一个水壶
清空任意一个水壶
从一个水壶向另外一个水壶倒水,直到装满或者倒空
示例 1: (From the famous “Die Hard” example)
输入: x = 3, y = 5, z = 4
输出: True
示例 2:
输入: x = 2, y = 6, z = 5
输出: False

class Solution:
    def canMeasureWater(self, jug1Capacity: int, jug2Capacity: int, targetCapacity: int) -> bool:
        stack = [(0,0)]
        self.seen = set()
        while stack:
            remain_x,remain_y = stack.pop()
            if remain_x == targetCapacity or remain_y == targetCapacity or remain_y+remain_x == targetCapacity:
                return True
            if (remain_x,remain_y) in self.seen:
                continue
            self.seen.add((remain_x,remain_y))
            stack.append((remain_x,0))
            stack.append((0,remain_y))
            stack.append((jug1Capacity,remain_y))
            stack.append((remain_x,jug2Capacity))
            stack.append((remain_x-min(remain_x,jug2Capacity-remain_y),remain_y+min(remain_x,jug2Capacity-remain_y)))
            stack.append((remain_x+min(remain_y,jug1Capacity-remain_x),remain_y-min(remain_y,jug1Capacity-remain_x)))
        return False

419 甲板上的战舰

给定一个二维的甲板, 请计算其中有多少艘战舰。 战舰用 'X’表示,空位用 '.'表示。 你需要遵守以下规则:
给你一个有效的甲板,仅由战舰或者空位组成。
战舰只能水平或者垂直放置。换句话说,战舰只能由 1xN (1 行, N 列)组成,或者 Nx1 (N 行, 1 列)组成,其中N可以是任意大小。
两艘战舰之间至少有一个水平或垂直的空位分隔 - 即没有相邻的战舰。
示例 :
X…X
…X
…X
在上面的甲板中有2艘战舰。
无效样例 :
…X
XXXX
…X
你不会收到这样的无效甲板 - 因为战舰之间至少会有一个空位将它们分开。
进阶:
你可以用一次扫描算法,只使用O(1)额外空间,并且不修改甲板的值来解决这个问题吗?

class Solution:
    def countBattleships(self, board: List[List[str]]) -> int:
        row = len(board)
        col = len(board[0])
        flag = 0
        self.res = 0
        def dfs(x):
            for y in range(col):
                if board[x][y] == 'X':
                    bfs(x, y)
                    self.res +=1
        def bfs(x,y):
            if 0<=x<row and 0<=y<col and board[x][y] == 'X':
                board[x][y] = '.'
                for i,j in [(1,0),(0,1)]:
                    bfs(x+i,y+j)
        for x in range(row):
            dfs(x)
        return self.res
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值