Leetcode 回溯 3
题号直达: 79 单词搜索 | 131 分割回文串 | 51 N 皇后
LeetCode 79. 单词搜索 ↑
题目
给定一个 m × n 的字符网格 board 和一个字符串 word。若 word 可由网格中若干格子中的字母按顺序拼出,则返回 true,否则返回 false。
规则: 在网格中依次选相邻格子(上下左右四连通);同一格子在同一条搜索路径中只能使用一次。
示例图
题目示例示意(0407_2.png`):
提示
m == len(board),n == len(board[i])1 ≤ m, n ≤ 61 ≤ len(word) ≤ 15board与word仅由大小写英文字母组成
代码(Python):DFS + 访问标记回溯
从每个格子 (i, j) 尝试作为起点,当前需匹配 word[k]。字符一致则标记 visited[i][j] = True,向四邻未访问格子递归 k + 1;任一分支成功即成功;回溯前 visited[i][j] = False 撤销标记。
class Solution(object):
def exist(self, board, word):
"""
:type board: List[List[str]]
:type word: str
:rtype: bool
"""
visited = [
[False for _ in range(len(board[0]))] for _ in range(len(board))
]
def check(i, j, k):
if board[i][j] != word[k]:
return False
if k == len(word) - 1:
return True
visited[i][j] = True
test = []
if i > 0 and not visited[i - 1][j]:
test.append([i - 1, j])
if j > 0 and not visited[i][j - 1]:
test.append([i, j - 1])
if i < len(board) - 1 and not visited[i + 1][j]:
test.append([i + 1, j])
if j < len(board[0]) - 1 and not visited[i][j + 1]:
test.append([i, j + 1])
judge = False
for m in range(len(test)):
judge = judge or check(test[m][0], test[m][1], k + 1)
if judge:
break
visited[i][j] = False
return judge
for i in range(len(board)):
for j in range(len(board[0])):
if check(i, j, 0):
return True
return False
思路解析
- 起点枚举:单词首字母可能与多个格匹配,需对每个
(i, j)尝试check(i, j, 0)。 - 剪枝:当前格字符不等于
word[k]时直接返回false;k == len(word) - 1且字符已匹配则整条路径成功。 - 回溯:
visited防止同一路径重复踩格;递归返回后必须恢复为False,以便其他起点或分支复用该格。 - 复杂度:网格规模小(至多 6×6),单词至多 15;最坏搜索与路径数相关,在题目约束下可接受。
LeetCode 131. 分割回文串 ↑
题目
给定字符串 s,将 s 分割成若干子串,使每个子串都是回文串。返回所有可能的分割方案。
测试样例
-
示例 1:
s = "aab"
输出:[["a","a","b"],["aa","b"]] -
示例 2:
s = "a"
输出:[["a"]]
提示
1 ≤ len(s) ≤ 16s仅由小写英文字母组成
代码(Python):DP 预处理回文 + DFS 回溯
先用二维数组 f[i][j] 表示 s[i:j+1] 是否为回文:s[i]==s[j] 且中间 s[i+1:j-1] 为回文(i 从大到小填表)。DFS 从位置 i 出发,枚举结束位置 j,若 f[i][j] 为真则把 s[i:j+1] 加入当前方案并递归 dfs(j+1);返回前 pop 回溯。
class Solution(object):
def partition(self, s):
"""
:type s: str
:rtype: List[List[str]]
"""
n = len(s)
f = [[True] * n for _ in range(n)]
for i in range(n - 1, -1, -1):
for j in range(i + 1, n):
f[i][j] = (s[i] == s[j]) and f[i + 1][j - 1]
ret = []
ans = []
def dfs(i):
if i == n:
ret.append(ans[:])
return
for j in range(i, n):
if f[i][j]:
ans.append(s[i : j + 1])
dfs(j + 1)
ans.pop()
dfs(0)
return ret
思路解析
- 回文 DP:
f[i][j]为真当且仅当两端字符相同且内侧子串(f[i+1][j-1])为回文;i从n-1递减,保证内层已算好。 - 分割 DFS:在
i处枚举第一段结束于j,仅当f[i][j]为真才切分;递归处理剩余部分j+1;i==n时复制一份ans加入ret。 - 复杂度:DP O(n^2);DFS 与回文分割方案数相关,最坏指数级,但
n ≤ 16可接受。
LeetCode 51. N 皇后 ↑
题目
困难。 国际象棋中,皇后可以攻击同一行、同一列、同一斜线上的棋子。n 皇后问题:在 n × n 的棋盘上放置 n 个皇后,使它们互不攻击。
给定整数 n,返回所有不同的解决方案。每种方案用字符串棋盘表示:'Q' 表示皇后,'.' 表示空位。
示例图
题目示例示意(0408.png`):
提示
1 ≤ n ≤ 9
代码(Python)解法一:回溯 + 列与斜线占用
按行递归:第 i 行尝试列 j。用列表记录已占用的列、主对角线(j - i)、副对角线(j + i),若均未冲突则放置并递归下一行;回溯时同步 pop。t 存每行皇后所在列,凑满 n 行后得到一种列向量,再经 transform 转成 n 条字符串。
class Solution(object):
def transform(self, info):
ans = []
for i in range(len(info)):
row = ["."] * len(info)
row[info[i]] = "Q"
row = "".join(row)
ans.append(row)
return ans
def solveNQueens(self, n):
"""
:type n: int
:rtype: List[List[str]]
"""
columns = []
diagonals1 = []
diagonals2 = []
t = []
ans = []
def check(i):
if i == n:
ans.append(t[:])
else:
for j in range(n):
if (
j not in columns
and j + i not in diagonals2
and j - i not in diagonals1
):
t.append(j)
columns.append(j)
diagonals1.append(j - i)
diagonals2.append(j + i)
check(i + 1)
t.pop()
columns.pop()
diagonals1.pop()
diagonals2.pop()
check(0)
for N in range(len(ans)):
ans[N] = self.transform(ans[N])
return ans
思路解析(解法一)
- 一行一后:每行恰放一个皇后,递归深度为
n;列与两条斜线不重复即可覆盖「互不攻击」。 - 斜线编号:同一主对角线上
j - i不变,同一副对角线上j + i不变(与行i、列j对应)。 - 输出:
t为长度为n的列下标序列,transform逐行把Q放在对应列,其余为'.'。 - 复杂度:搜索树在
n ≤ 9内可接受;可用set代替列表判重以常数优化。
代码(Python)解法二:回溯 + 每行可用列布尔表
仍在第 i 行决策,但不用全局 columns / 对角线集合:每进入一行先建 bin_record 长为 n,初值全为可放。对已放置的上一行皇后 (j, t[j]),令 diff = i - j(行距),则当前行被攻击的列为 t[j](同列)、t[j] - diff 与 t[j] + diff(两条斜线落在第 i 行的位置),在范围内则标为不可放。再枚举 bin_record[k] 为真的列 k 递归。
class Solution(object):
def transform(self, info):
ans = []
for i in range(len(info)):
row = ["."] * len(info)
row[info[i]] = "Q"
row = "".join(row)
ans.append(row)
return ans
def solveNQueens(self, n):
"""
:type n: int
:rtype: List[List[str]]
"""
t = []
ans = []
def check(i):
if i == n:
ans.append(t[:])
else:
bin_record = [True] * n
for j in range(len(t)):
diff = i - j
bin_record[t[j]] = False
if t[j] - diff >= 0:
bin_record[t[j] - diff] = False
if t[j] + diff <= n - 1:
bin_record[t[j] + diff] = False
for k in range(n):
if bin_record[k]:
t.append(k)
check(i + 1)
t.pop()
check(0)
for i in range(len(ans)):
ans[i] = self.transform(ans[i])
return ans
思路解析(解法二)
- 与解法一等价:已放皇后
(j, t[j])对第i行的禁放列,正是同列与两条斜线延伸到第i行的交点列下标t[j] ± (i - j)。 - 实现:每行 O(n) 根据已有
t刷新bin_record,再 O(n) 尝试可放列;总思路仍是回溯,仅冲突检测的数据结构不同。 - 复杂度:与解法一同阶;
n ≤ 9时足够。

浙公网安备 33010602011771号