46. Permutations
class Solution:def permute(self, nums: List[int]) -> List[List[int]]:def dfs(i): # 当前固定nums的第i位if i == n-1:res.append(nums[:]) # copy而非传地址for j in range(i, n):nums[i], nums[j] = nums[j], nums[i]dfs(i+1)nums[i], nums[j] = nums[j], nums[i]n = len(nums)res = []dfs(0)return res
- 时间复杂度:
,
dfs()调用占,每次复制答案数组占
- 空间复杂度:
,取决于递归调用深度
47. Permutations II
给定数组含有重复元素,注意使用set来去重。
class Solution:def permuteUnique(self, nums: List[int]) -> List[List[int]]:def dfs(i):if i == n-1:res.append(nums[:])num_set = set() # 对每个位置,使用一个set来去重for j in range(i, n):if nums[j] in num_set:continuenum_set.add(nums[j])nums[i], nums[j] = nums[j], nums[i]dfs(i+1)nums[i], nums[j] = nums[j], nums[i]n = len(nums)res = []dfs(0)return res
- 时间复杂度:
- 空间复杂度:
39. Combination Sum
class Solution:def combinationSum(self, nums: List[int], target: int) -> List[List[int]]:def dfs(i, path, remain):if remain == 0:res.append(path)returnfor j in range(i, len(nums)):if remain - nums[j] < 0: # 剪枝breakdfs(j, path + [nums[j]], remain - nums[j])nums.sort() # 剪枝的预处理:排序res = []dfs(0, [], target)return res
40. Combination Sum II
中可能包含重复元素,且每个元素只能使用一次:
- 考虑剪枝:先排序,再判断剩余填充的值是否
考虑重复元素:
j > i and nums[j] == nums[j-1]class Solution: def combinationSum2(self, nums: List[int], target: int) -> List[List[int]]: ''' i: 可以选择元素的起始坐标 ''' def dfs(i, path, remain): if remain == 0: res.append(path) return for j in range(i, len(nums)): if remain - nums[j] < 0: break if j > i and nums[j] == nums[j-1]: # 可选的重复元素:只能使用一次 continue dfs(j + 1, path + [nums[j]], remain - nums[j]) nums.sort() res = [] dfs(0, [], target) return res
77. Combinations
class Solution:
def combine(self, n: int, k: int) -> List[List[int]]:
def dfs(i, path):
if len(path) == k:
res.append(path)
return
for j in range(i, n + 1):
if j + (k - len(path)) >= n + 2: # 剪枝
break
dfs(j + 1, path + [j])
res = []
dfs(1, [])
return res
78. Subsets
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
def dfs(i, path):
res.append(path)
for j in range(i, n):
dfs(j + 1, path + [nums[j]])
n, res = len(nums), []
dfs(0, [])
return res
79. Word Search
class Solution:
def exist(self, board: List[List[str]], word: str) -> bool:
# 搜索到board的(i, j)位置,word的k位置
def dfs(i, j, k):
if not 0 <= i < m or not 0 <= j < n or board[i][j] != word[k]:
return False
if k == len(word) - 1: # 先进行char相等判断后,再进行index判断
return True
board[i][j] = '.' # or 可采用visited = set()来记录(i, j)
flag = dfs(i-1, j, k+1) or dfs(i+1, j, k+1) or \
dfs(i, j-1, k+1) or dfs(i, j+1, k+1)
# 依序搜索,遇到True便剪枝(or具有短路特性)
board[i][j] = word[k] # 回溯
return flag
m, n = len(board), len(board[0])
for i in range(m):
for j in range(n):
if dfs(i, j, 0):
return True
return False
- 时间复杂度:
,除了第一次可以进入
个分支以外,其余时间我们最多会进入
个分支(因为每个位置只能使用一次,所以走过来的分支没法走回去)
- 空间复杂度:
,递归调用的栈深度
51. N-Queens
**N-皇后**问题中,棋子🚩的摆放规则是:
- 不能在同一行/同一列;
- 不能在同一主/副对角线。
将N个皇后摆放在N × N的棋盘上,必然是每一行、每一列均有一个皇后,且没有两个皇后在同一对角线上。
考虑使用回溯法解决:
以**行**为基准放置皇后,判断该行的每一**列**是否能够放置皇后。显然每一行可放置的选择数依次是,故仅考虑遍历的时间复杂度为
。
为了降低总的时间复杂度,考虑空间换时间:能否使用的时间判断该位置是否能否放置皇后?
Approach I** 基于`集合`的回溯**
- 列:
-
记录
- 主对角线:对于同一主对角线的元素,
恒定
副对角线:对于同一主对角线的元素,
恒定 ```python class Solution: def solveNQueens(self, n: int) -> List[List[str]]:
def generate_board(): board = [] row = ['.'] * n for i in range(n): row[queens[i]] = 'Q' board.append(''.join(row)) # 每一行均是'str'而非'list' row[queens[i]] = '.' return board
def dfs(i): # 在第i行放置棋子🚩
if i == n:
res.append(generate_board())
return
for j in range(n):
if j in columns or i - j in diagonal_main or i + j in diagonal_sub:
continue
queens[i] = j
columns.add(j)
diagonal_main.add(i - j)
diagonal_sub.add(i + j)
dfs(i + 1)
queens[i] = -1
columns.remove(j)
diagonal_main.remove(i - j)
diagonal_sub.remove(i + j)
queens = [-1] * n # queens[i] == j: [i, j]处放置有棋子🚩
columns, diagonal_main, diagonal_sub = set(), set(), set()
res = []
dfs(0)
return res
- 时间复杂度:
- 空间复杂度:,递归调用层数、数组长度、集合长度均为
**Approach II**** 基于**`**bit**`**的回溯**<br />之后再学吧。
<a name="N8M6T"></a>
#### [93. Restore IP Addresses](https://leetcode-cn.com/problems/restore-ip-addresses/)
```python
class Solution:
def restoreIpAddresses(self, s: str) -> List[str]:
SEG_COUNT = 4 # ip地址固定分为4段
segments = [''] * SEG_COUNT
res = []
# index: ip地址的段号
# start: s中可选位置的起点
def dfs(index, start):
if index == SEG_COUNT:
if start == len(s):
res.append('.'.join(segments))
return
if start == len(s):
return
if s[start] == '0': # 该bit为0,不能有前导0,直接加入
segments[index] = '0'
dfs(index + 1, start + 1)
# return
for end in range(start, len(s)): # 有边界不能为start+3(比n大,越界)
ip = s[start:end+1]
if 0 < int(ip) <= 255: # 不能使用str比较(字典序) and 不能包含0
segments[index] = ip
dfs(index + 1, end + 1)
# segments[index] = ''
else:
break
dfs(0, 0)
return res
- 时间复杂度:
,递归最大深度为
,每次最多有三种选择(1bit, 2bits, 3bits),每次复制到答案数组的时间开销为
- 空间复杂度:
,递归最大深度为
