目录
回溯法题目总结(转自:从全排列问题开始理解「回溯」算法(深度优先遍历 + 状态重置 + 剪枝))
Leetcode22:括号生成
Leetcode46:全排序(不含重复)
Leetcode47:全排序(含重复)
Leetcode39:组合总和(以下几题题解参考汇总)
Leetcode40:组合总和II
Leetcode78:子集/Subset
Leetcode90:子集II/SubsetII
Leetcode200:岛屿数量
题目 | 提示 |
---|---|
47. 全排列 II | 思考一下,为什么造成了重复,如何在搜索之前就判断这一支会产生重复,从而“剪枝”。 |
17 .电话号码的字母组合 | |
22. 括号生成 | 这是字符串问题,没有显式回溯的过程。这道题广度优先遍历也很好写,可以通过这个问题理解一下为什么回溯算法都是深度优先遍历,并且都用递归来写。 |
39. 组合总和 | 使用题目给的示例,画图分析。 |
40. 组合总和 II | |
51. N皇后 |
其实就是全排列问题,注意设计清楚状态变量。 |
60. 第k个排列 |
利用了剪枝的思想,减去了大量枝叶,直接来到需要的叶子结点。 |
77. 组合 |
组合问题按顺序找,就不会重复。并且举一个中等规模的例子,找到如何剪枝,这道题思想不难,难在编码。 |
78. 子集 |
为数不多的,解不在叶子结点上的回溯搜索问题。解法比较多,注意对比。 |
90. 子集 II |
剪枝技巧同 47 题、39 题、40 题。 |
93. 复原IP地址 |
|
784. 字母大小写全排列 |
题目:数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
基本思路:回溯算法,左边'('有剩余的情况下可以继续增加'(';')'剩余数大于左括号剩余数时可添加右括号;
class Solution(object):
def generateParenthesis(self, n):
"""
:type n: int
:rtype: List[str]
"""
def dfs(path, left, right): # left and right表示左右括号剩余的个数
if left == 0 and right == 0:
ans.append(path)
return
if left > 0:
dfs(path + '(', left - 1, right)
if right > left:
dfs(path + ')', left, right - 1)
ans = []
path = ''
dfs(path, n, n)
return ans
题目:给定一个 没有重复 数字的序列,返回其所有可能的全排列。eg. [1, 2, 3]
基本思路:回溯法,track记录路线,元素之前不在track中,就append上,接着继续dfs;记住最后要pop();满了就返回return;
class Solution(object):
def permute(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
def dfs(nums, track):
if len(track) == len(nums):
ans.append(track[:])
return
for i in nums:
if i in track: continue
track.append(i)
dfs(nums, track)
track.pop() # pop掉最后一个数
ans = []
track = []
dfs(nums, track)
return ans
题目:给定一个可包含重复数字的序列,返回所有不重复的全排列。
基本思路:需要有一个剪枝的操作,同时利用一个used()记录该元素是否被使用过,同时还需要对nums进行sort();
class Solution(object):
def permuteUnique(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
def dfs(nums, ans, path, used):
if len(nums) == len(path):
ans.append(path[:])
return
else:
for i in range(len(nums)):
if used[i] or (i > 0 and nums[i] == nums[i-1] and not used[i-1]):
continue # 剪枝操作
path.append(nums[i])
used[i] = True
dfs(nums, ans, path, used)
used[i] = False
path.pop()
ans = []
path = []
used = [False for _ in range(len(nums))]
nums.sort() # 一定要进行sort()
dfs(nums, ans, path, used)
return ans
题目:给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的数字可以无限制重复被选取。说明:所有数字(包括 target)都是正整数;解集不能包含重复的组合。 eg. input: candidates = [2, 3, 6, 7], target = 7;output: [[7], [2, 2, 3]]
基本思路:回溯算法的套路;同时为了避免重复,要记录一个start index,每次从start index处开始查找;
class Solution(object):
def combinationSum(self, candidates, target):
"""
:type candidates: List[int]
:type target: int
:rtype: List[List[int]]
"""
def dfs(ans, candidates, remain, path, start):
if remain < 0: return
if remain == 0:
ans.append(path[:])
else:
for i in range(start, len(candidates)):
path.append(candidates[i])
dfs(ans, candidates, remain-candidates[i], path, i) # not i + 1 because we can reuse same elements
path.pop()
path = []
ans = []
candidates.sort()
dfs(ans, candidates, target, path, 0)
return ans
题目: 给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的每个数字在每个组合中只能使用一次。
基本思路:相对于可以重复的leetcode39,仅有2点变化,提前剪枝 + start从i+1开始
class Solution(object):
def combinationSum2(self, candidates, target):
"""
:type candidates: List[int]
:type target: int
:rtype: List[List[int]]
"""
def dfs(ans, path, candidates, remain, start):
if remain < 0: return
if remain == 0:
ans.append(path[:])
else:
for i in range(start, len(candidates)):
if i > start and candidates[i] == candidates[i-1]: # skip duplicate
continue
path.append(candidates[i])
dfs(ans, path, candidates, remain - candidates[i], i+1) # 不能重复,变为i+1
path.pop()
ans = []
path = []
candidates.sort()
dfs(ans, path, candidates, target, 0)
return ans
题目:给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。说明:解集不能包含重复的子集。
基本思路:1. 没有条件要求,直接返回path;2. 记录start;3. 不能用重复元素,所以start从i+1开始;
class Solution(object):
def subsets(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
def dfs(ans, path, nums, start):
ans.append(path[:]) # 不用判定条件,直接append
for i in range(start, len(nums)):
path.append(nums[i])
dfs(ans, path, nums, i + 1) # 不能重复使用,则用i+1
path.pop()
ans = []
path = []
nums.sort()
dfs(ans, path, nums, 0)
return ans
题目:给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。说明:解集不能包含重复的子集。
基本思路:1. 没有条件要求,直接返回path;2. 记录start;3. 不能用重复元素,所以start从i+1开始;4. 有重复元素,skip duplicate.
class Solution(object):
def subsetsWithDup(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
def dfs(ans, nums, path, start):
ans.append(path[:])
for i in range(start, len(nums)):
if i > start and nums[i] == nums[i-1]: # 有重复元素,用于去重
continue
path.append(nums[i])
dfs(ans, nums, path, i+1) # 不能重复用同一个元素,从i+1开始计算
path.pop()
ans = []
path = []
nums.sort()
dfs(ans, nums, path, 0)
return ans
题目:给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。岛屿总是被水包围,并且每座岛屿只能由水平方向或竖直方向上相邻的陆地连接形成。此外,你可以假设该网格的四条边均被水包围。
基本思路:方法1:bfs;方法2:dfs;
class Solution(object):
"""
方法1:bfs
"""
def numIslands(self, grid):
def bfs(grid, i, j):
queue = [[i, j]]
while queue:
[i, j] = queue.pop(0)
if not len(grid) > i >= 0 or not len(grid[0]) > j >= 0 or grid[i][j] == '0':continue # 不满足边界条件or值为‘0’,则跳过
grid[i][j] = '0' # 将当前的值为‘1’的格子变为‘0’
queue += [[i, j-1], [i-1, j], [i, j+1], [i+1, j]] # 将接壤的值为‘1’的格子都变为‘0’
ans = 0
for i in range(len(grid)):
for j in range(len(grid[0])):
if grid[i][j] == '1': # 找到了一个为‘1’的格子
bfs(grid, i, j) # 将接壤的值为‘1’的格子都变为‘0’
ans += 1
return ans
"""
方法2:dfs
"""
def numIslands(self, grid):
def dfs(grid, i, j):
grid[i][j] = '0'
tmp = [(i, j-1), (i-1, j), (i, j+1), (i+1, j)]
for (i, j) in tmp:
if not len(grid) > i >= 0 or not len(grid[0]) > j >= 0 or grid[i][j] == '0':continue
dfs(grid, i, j)
ans = 0
for i in range(len(grid)):
for j in range(len(grid[0])):
if grid[i][j] == '1':
dfs(grid, i, j)
ans += 1
return ans
题目:给定一个包含了一些 0 和 1 的非空二维数组 grid 。一个 岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在水平或者竖直方向上相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。找到给定的二维数组中最大的岛屿面积。(如果没有岛屿,则返回面积为 0 。)
基本思路:跟上题找岛屿数量的思路相同;注意每次的限制条件以及沉岛思想;
class Solution(object):
def maxAreaOfIsland(self, grid):
"""
:type grid: List[List[int]]
:rtype: int
"""
def bfs(grid, i, j):
if not 0 <= i < m or not 0 <= j < n or grid[i][j] == 0:
return 0
grid[i][j] = 0 # 沉岛思想
cnt = 1
cnt += bfs(grid, i-1, j)
cnt += bfs(grid, i+1, j)
cnt += bfs(grid, i, j-1)
cnt += bfs(grid, i, j+1)
return cnt
m, n = len(grid), len(grid[0])
ans = 0
for i in range(m):
for j in range(n):
if grid[i][j] == 1:
ans = max(ans, bfs(grid, i, j))
return ans
题目:给定一个整数 n, 返回从 1 到 n 的字典顺序。
示例:给定 n =13,返回 [1,10,11,12,13,2,3,4,5,6,7,8,9] 。
基本思路:dfs;从1..10,再在每个数进行dfs;主要终止条件以及append有效值即可;
时间复杂度:O(n) -- 每个数都要遍历一遍;
空间复杂度:O(1) -- 不考虑递归调用函数栈;
相关面试题:1. n个数求安字典序排序第m小的数
class Solution(object):
def lexicalOrder(self, n):
"""
:type n: int
:rtype: List[int]
"""
if n < 1: return []
def dfs(ans, cur):
if cur > n: return # 终止条件,剪枝操作
ans.append(cur)
for j in range(10):
dfs(ans, cur * 10 + j)
ans = []
for i in range(1, 10):
dfs(ans, i)
return ans