给定一个含有数字和运算符的字符串,为表达式添加括号,改变其运算优先级以求出不同的结果。你需要给出所有可能的组合的结果。有效的运算符号包含 +, - 以及 * 。
示例 1:
输入: "2-1-1"
输出: [0, 2]
解释:
((2-1)-1) = 0
(2-(1-1)) = 2
示例 2:
输入: "2*3-4*5"
输出: [-34, -14, -10, -10, 10]
解释:
(2*(3-(4*5))) = -34
((2*3)-(4*5)) = -14
((2*(3-4))*5) = -10
(2*((3-4)*5)) = -10
(((2*3)-4)*5) = 10
思路: 以运算符作为分隔,递归的求解左右两侧的算式。利用分治思想三步走:1.分解:按运算符分成左右两部分,分别求解;2.解决:实现一个递归函数,输入算式,返回算式解;3.合并:根据运算符,合并左右两部分的解,得出最终解。
代码实现:
class Solution:
def diffWaysToCompute(self, input: str):
if input.isdigit(): # 如果只有数字,直接返回
return [int(input)]
res = []
for index, char in enumerate(input):
if char in ['+','-','*']:
# 1.分解:遇到运算符,计算左右两侧的结果集
# 2.解决:diffWaysToCompute 递归函数求出子问题的解
left = self.diffWaysToCompute(input[:index])
right = self.diffWaysToCompute(input[index+1:])
# 3.合并:根据运算符,合并子问题的解
for l in left:
for r in right:
if char == '+':
res.append(int(l) + int(r)) # py3 input类型默认是字符串
elif char == '-':
res.append(int(l) - int(r))
elif char == '*':
res.append(int(l) * int(r))
return res
给定一个整数 n,求以 1 … n 为节点组成的二叉搜索树有多少种?
示例:
输入: 3
输出: 5
解释:
给定 n = 3, 一共有 5 种不同结构的二叉搜索树:
1 3 3 2 1
\ / / / \ \
3 2 1 1 3 2
/ / \ \
2 1 2 3
思路: 使用动态规划。假设n个节点存在G(n)个不同的二叉搜索树,遍历每个节点,以每个节点为根构建二叉搜索树,令 f(i) 为以i为根的二叉搜索树的个数,则:G(n) = f(1) + f(2) + f(3) + … + f(n)。例如当n=7,[1,2,3,4,5,6,7]时,假设 i=3 为根节点,其左子树节点个数为2个,其右子树节点个数为4个,可表示为:f(i) = G(2) * G(4),写成通式则为:f(i) = G(i-1) * G(n-i)。综合上面两个公式可得著名的卡特兰数公式:G(n) = G(0) * G(n-1) + G(1) * G(n-2) + … + G(n-1) * G(0)。对于本题,dp[0]和dp[1]很容易得到均为1,根据动态规划递推式可得dp[n]的值。
代码实现:
class Solution:
def numTrees(self, n: int) -> int:
dp = [0 for _ in range(n+1)]
dp[0] = 1 # 空二叉树也算一种
dp[1] = 1 # 一个节点
for i in range(2,n+1):
for j in range(1,i+1):
dp[i] += dp[j-1] * dp[i-j]
return dp[n]
给定一个整数 n,生成所有由 1 … n 为节点所组成的 二叉搜索树 。
示例:
输入:3
输出:
[
[1,null,3,2],
[3,2,null,1],
[3,1,null,null,2],
[2,1,3],
[1,null,2,null,3]
]
解释:
以上的输出对应以下 5 种不同结构的二叉搜索树:
1 3 3 2 1
\ / / / \ \
3 2 1 1 3 2
/ / \ \
2 1 2 3
提示: 0 <= n <= 8
思路: 根据96题的思路,首先定义一个用于生成树的函数build_Trees(left, right),表示生成 [left,…,right]的所有可能二叉搜索树。若left > right,说明为空树,返回None;遍历每个节点,递归的生成左右子树,所有可能的左子树列表 left_trees = build_Trees(left, i-1);所有可能的右子树列表 right_trees = build_Trees(i+1, right),用两层循环组合左右子树,生成二叉搜索树。
代码实现:
# Definition for a binary tree node.
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
class Solution:
def generateTrees(self, n: int):
if (n == 0):
return []
def build_Trees(left, right):
all_trees = []
if (left > right): # 说明是空树
return [None]
for i in range(left, right + 1):
left_trees = build_Trees(left, i - 1)
right_trees = build_Trees(i + 1, right)
for l in left_trees:
for r in right_trees:
cur_tree = TreeNode(i)
cur_tree.left = l
cur_tree.right = r
all_trees.append(cur_tree)
return all_trees
res = build_Trees(1, n)
return res
在一个 N × N 的方形网格中,每个单元格有两种状态:空(0)或者阻塞(1)。
一条从左上角到右下角、长度为 k 的畅通路径,由满足下述条件的单元格 C_1, C_2, …, C_k 组成:
相邻单元格 C_i 和 C_{i+1} 在八个方向之一上连通(此时,C_i 和 C_{i+1} 不同且共享边或角)
C_1 位于 (0, 0)(即,值为 grid[0][0])
C_k 位于 (N-1, N-1)(即,值为 grid[N-1][N-1])
如果 C_i 位于 (r, c),则 grid[r][c] 为空(即,grid[r][c] == 0)
返回这条从左上角到右下角的最短畅通路径的长度。如果不存在这样的路径,返回 -1 。
示例 1:
输入:[[0,1],[1,0]]
输出:2
示例 2:
输入:[[0,0,0],[1,1,0],[1,1,0]]
输出:4
提示:
1 <= grid.length == grid[0].length <= 100
grid[i][j] 为 0 或 1
思路: 使用广度优先搜索BFS,把要搜索的点加入队列,对每层进行遍历判断,每个点都向8个方向进行扩展,探索通路,新的通路的点再加入队列,直到走到终点。BFS中,最先到达终点的就是最短路径,每一层遍历的节点都与根节点距离相同。
代码实现:
class Solution:
def shortestPathBinaryMatrix(self, grid):
from collections import deque
if len(grid) == 1:
return 1
# 起点或终点是堵塞状态,没有路
if grid[0][0] == 1 or grid[len(grid)-1][len(grid)-1] == 1:
return -1
res = 1 # res代表路径长度
path = deque()
path.append([0,0]) # 先把起点加入队列
while path: # BFS广度优先搜索
for _ in range(len(path)): # 对BFS的某一层的中所有点向8个方向进行扩展
x, y = path.popleft()
for new_x, new_y in [(x-1, y-1),(x, y-1),(x+1, y-1),(x+1, y),
(x+1, y+1),(x, y+1),(x-1, y+1),(x-1, y)]:
if new_x == len(grid)-1 and new_y == len(grid)-1: # 若走到了终点
return res + 1
if new_x < 0 or new_x > len(grid)-1 or new_y < 0 or new_y > len(grid)-1: # 扩展的点超出边界
continue
if grid[new_x][new_y] == 1 or grid[new_x][new_y] == -1: # 扩展的点为阻塞或已经访问过
continue
if grid[new_x][new_y] == 0: # 扩展的点是通路
grid[new_x][new_y] = -1 # 将该点设置成已访问
path.append([new_x,new_y]) # 将该点加入队列
res += 1 # 对某一层的元素都判定后,距离加1(同一个层中所有点距起点距离相等)
return -1
给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
示例 1:
输入: n = 12
输出: 3
解释: 12 = 4 + 4 + 4.
示例 2:
输入: n = 13
输出: 2
解释: 13 = 4 + 9.
思路:
本题可以转化为BFS思路,使用广度优先遍历顺序遍历每一行,所以当节点差出现0时,此时一定是最短的路径。如绿色所示,若此时节点为0,表示根节点可以由路径上的平方数{1,1,9}构成,返回此时的路径长度为3,后续不再执行。如红色所示,若节点值在之前已经出现,则不需要再计算,一定不会比最短路径短,最短路径还未出现。实现广度优先搜索一般都需要借助队列,循环遍历,每次计算 x = num - i^2,i 的范围是[1, int(num**0.5)+1)。若x==0说明找到了路径;若x不在visited里说明当前节点未出现过,将该节点假如visited并加入队列。
此题另一种解法见:动态规划 -> 分割整数 -> 279. 完全平方数。
代码实现:
class Solution:
def numSquares(self, n: int):
from collections import deque
queue = deque()
queue.append(n)
visited = set()
step = 1
while queue:
for _ in range(len(queue)):
num = queue.popleft()
for i in range(1, int(num**0.5)+1):
x = num - i*i
if x == 0:
return step
if x not in visited:
visited.add(x)
queue.append(x)
step += 1
return step
给定两个单词(beginWord 和 endWord)和一个字典,找到从 beginWord 到 endWord 的最短转换序列的长度。转换需遵循如下规则:
每次转换只能改变一个字母。
转换过程中的中间单词必须是字典中的单词。
说明:
如果不存在这样的转换序列,返回 0。
所有单词具有相同的长度。
所有单词只由小写字母组成。
字典中不存在重复的单词。
你可以假设 beginWord 和 endWord 是非空的,且二者不相同。
示例 1:
输入:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]
输出: 5
解释: 一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog",
返回它的长度 5。
示例 2:
输入:
beginWord = "hit"
endWord = "cog"
wordList = ["hot","dot","dog","lot","log"]
输出: 0
解释: endWord "cog" 不在字典中,所以无法进行转换。
思路:
单词接龙问题可以把每个单词看作节点,差距只有一个字母的两个单词连成一条边,问题变成找到从起点到终点的最短路径,使用广度优先搜索BFS。其中最重要的步骤是找到相邻的节点,也就是只差一个字母的单词,我们可以将wordlist中单词处理成通用状态,将单词中的某个字母用_代替例如:hot 有三个通用状态分别是:_ot, h_t, ho_。通过遍历每个单词的所有通用状态,并在字典里找到并遍历该通用状态对应的所有单词,字典里对应的这些单词都和当前单词有同样的通用状态,所以是当前单词的相邻节点,同时也处于BFS中当前节点的下一层。
代码实现:
class Solution:
def ladderLength(self, beginWord: str, endWord: str, wordList):
from collections import defaultdict
from collections import deque
if endWord not in wordList or not wordList:
return 0
L = len(beginWord) # 所有单词具有相同长度
all_combo_dict = defaultdict(list) # defaultdict(list)构建一个默认值为list的字典
for word in wordList: # 对wordList中的单词做预处理
for i in range(L):
# 字典的键是通用状态,例如:d*g
# 字典的值是一个列表,里面存着拥有相同通用状态的所有单词
all_combo_dict[word[:i] + "*" + word[i+1:]].append(word)
queue = deque()
queue.append([beginWord,1])
visited = {beginWord: True} # Visited 确保不会重复处理同一个单词
while queue:
current_word, level = queue.popleft()
for i in range(L):
intermediate_word = current_word[:i] + "*" + current_word[i + 1:]
for word in all_combo_dict[intermediate_word]:
if word == endWord:
return level + 1
if word not in visited:
visited[word] = True # 该单词标记为已访问
queue.append((word, level+1))
all_combo_dict[intermediate_word] = []
return 0
给定一个包含了一些 0 和 1 的非空二维数组 grid 。
一个 岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在水平或者竖直方向上相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。
找到给定的二维数组中最大的岛屿面积。(如果没有岛屿,则返回面积为 0 。)
示例 1:
[[0,0,1,0,0,0,0,1,0,0,0,0,0],
[0,0,0,0,0,0,0,1,1,1,0,0,0],
[0,1,1,0,1,0,0,0,0,0,0,0,0],
[0,1,0,0,1,1,0,0,1,0,1,0,0],
[0,1,0,0,1,1,0,0,1,1,1,0,0],
[0,0,0,0,0,0,0,0,0,0,1,0,0],
[0,0,0,0,0,0,0,1,1,1,0,0,0],
[0,0,0,0,0,0,0,1,1,0,0,0,0]]
对于上面这个给定矩阵应返回 6。注意答案不应该是 11 ,因为岛屿只能包含水平或垂直的四个方向的 1 。
示例 2:
[[0,0,0,0,0,0,0,0]]
对于上面这个给定的矩阵, 返回 0。
注意: 给定的矩阵grid 的长度和宽度都不超过 50。
思路: 利用深度优先搜索DFS,把接下来要遍历的土地加入到栈里。访问每一片土地时,都对他的四个方向进行探索,找到未访问的土地,加入到栈中,只要栈不为空,就说明还有土地待访问,那么就从栈中取出一个元素并访问。为了确保每个土地访问不超过一次,我们每次经过一块土地时,将这块土地的值置为 0。这样我们就不会多次访问同一土地。
代码实现:
class Solution:
def maxAreaOfIsland(self, grid):
ans = 0
for i, m in enumerate(grid):
for j, n in enumerate(m):
area = 0
stack = [(i,j)]
while stack: # 当栈为空表示无路可走,返回上一层循环
x, y = stack.pop()
if x<0 or y<0 or x == len(grid) or y == len(grid[0]) or grid[x][y] != 1:
continue
area += 1 # 连通岛屿面积+1
grid[x][y] = 0 # 将已经访问过的点置为0,避免再次访问
for dx, dy in [(0,-1),(0,1),(-1,0),(1,0)]:
new_x = x + dx
new_y = y + dy
stack.append((new_x,new_y))
ans = max(ans, area)
return ans
给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
示例 1:
输入:
11110
11010
11000
00000
输出: 1
示例 2:
输入:
11000
11000
00100
00011
输出: 3
解释: 每座岛屿只能由水平和/或竖直方向上相邻的陆地连接而成。
思路: 使用DFS,遍历整个矩阵,当遇到 grid[ i ][ j ] == '1’时,对该点的上下左右进行深度优先搜索,岛屿数num+1。探索岛屿的同时将 grid[ i ][ j ] == ‘0’,避免之后重复搜索相同的岛屿。
代码实现:
class Solution:
def numIslands(self, grid):
def dfs(grid, i, j):
if i < 0 or j < 0 or i >= len(grid) or j >= len(grid[0]) or grid[i][j] != '1':
return
grid[i][j] = '0' # 已访问过的节点置为0
# for new_i,new_j in [(i,j+1),(i,j-1),(i-1,j),(i+1,j)]:
# if 0<= new_i < len(grid) and 0<= new_j < len(grid[0]) and grid[new_i][new_j] == '1':
# dfs(grid,new_i,new_j)
dfs(grid, i+1, j)
dfs(grid, i, j+1)
dfs(grid, i-1, j)
dfs(grid, i, j-1)
num = 0
for i in range(len(grid)):
for j in range(len(grid[0])):
if grid[i][j] == '1':
dfs(grid, i, j)
num += 1
return num
班上有 N 名学生。其中有些人是朋友,有些则不是。他们的友谊具有是传递性。如果已知 A 是 B 的朋友,B 是 C 的朋友,那么我们可以认为 A 也是 C 的朋友。所谓的朋友圈,是指所有朋友的集合。
给定一个 N * N 的矩阵 M,表示班级中学生之间的朋友关系。如果M[i][j] = 1,表示已知第 i 个和 j 个学生互为朋友关系,否则为不知道。你必须输出所有学生中的已知的朋友圈总数。
示例 1:
输入:
[[1,1,0],
[1,1,0],
[0,0,1]]
输出: 2
说明:已知学生0和学生1互为朋友,他们在一个朋友圈。
第2个学生自己在一个朋友圈。所以返回2。
示例 2:
输入:
[[1,1,0],
[1,1,1],
[0,1,1]]
输出: 1
说明:已知学生0和学生1互为朋友,学生1和学生2互为朋友,所以学生0和学生2也是朋友,所以他们三个在一个朋友圈,返回1。
注意:
N 在[1,200]的范围内。
对于所有学生,有M[i][i] = 1。
如果有M[i][j] = 1,则有M[j][i] = 1。
思路: 本题中的每个人都可以看作一个节点,如果两个人互为朋友,那么这两个节点之间就有一条边相连。因为该题表示的是朋友关系,所以二维表的长宽一定相等(长度宽度都表示共有几个人),利用深度优先搜索dfs,先访问一个节点,再访问其相邻的任一节点,再访问这一节点的任一相邻节点,不断遍历没有访问过的节点。这一过程相当于去询问每个人,让他说出跟谁是朋友,每个人只用问一遍,所以用一个一维列表记录是否访问过该节点即可。所以在主代码设置一层循环,dfs函数里设置一层循环;在主代码首先访问第0个人,进入dfs深度优先搜索,如果在dfs中把剩下的所有人都访问到了,说明他们几个都是朋友,只在主代码里调用了一次dfs,就有一个朋友圈;如果在dfs中谁也访问不到,就说明第0个人没有朋友,他自己是一个朋友圈,就接着在主代码遍历第1个人,以此类推。
代码实现:
class Solution:
def findCircleNum(self, M) -> int:
def dfs(i, visited):
visited[i] = 1
for j in range(len(M)):
if visited[j] == 0 and M[i][j] == 1:
dfs(j, visited)
num = 0
visited = [0 for _ in range(len(M))]
for i in range(len(M)):
if visited[i] == 0:
dfs(i, visited)
num += 1
return num
给定一个二维的矩阵,包含 ‘X’ 和 ‘O’(字母 O)。
找到所有被 ‘X’ 围绕的区域,并将这些区域里所有的 ‘O’ 用 ‘X’ 填充。
示例:
X X X X
X O O X
X X O X
X O X X
运行你的函数后,矩阵变为:
X X X X
X X X X
X X X X
X O X X
解释: 被围绕的区间不会存在于边界上,换句话说,任何边界上的 ‘O’ 都不会被填充为 ‘X’。 任何不在边界上,或不与边界上的 ‘O’ 相连的 ‘O’ 最终都会被填充为 ‘X’。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。
思路: 从边界开始深度优先搜索,把"O"和与边界"O"连通的"O"置为"Y",然后再遍历整个二维数组,把"O"置为"X",把"Y"置为"O"。
代码实现:
class Solution:
def solve(self, board):
"""
Do not return anything, modify board in-place instead.
"""
def dfs(board, i, j):
if i<0 or j<0 or i >= len(board) or j >= len(board[0]) or board[i][j] != "O":
return
board[i][j] = "Y" # 把与边界连通的"O"变成"Y"
dfs(board, i, j-1)
dfs(board, i, j+1)
dfs(board, i-1, j)
dfs(board, i+1, j)
if len(board) == 0:
return []
for i in range(len(board)):
if board[i][0] == "O": # 第一列
dfs(board, i, 0)
if board[i][len(board[0])-1] == "O": # 最后一列
dfs(board, i, len(board[0])-1)
for j in range(len(board[0])):
if board[0][j] == "O": # 第一行
dfs(board, 0, j)
if board[len(board)-1][j] == "O": # 最后一行
dfs(board, len(board)-1, j)
for i in range(len(board)):
for j in range(len(board[0])):
if board[i][j] == "O": # 把"O"变成"X"
board[i][j] = "X"
elif board[i][j] == "Y": # 把"Y"变成"O"
board[i][j] = "O"
print(board)
给定一个 m x n 的非负整数矩阵来表示一片大陆上各个单元格的高度。“太平洋”处于大陆的左边界和上边界,而“大西洋”处于大陆的右边界和下边界。
规定水流只能按照上、下、左、右四个方向流动,且只能从高到低或者在同等高度上流动。
请找出那些水流既可以流动到“太平洋”,又能流动到“大西洋”的陆地单元的坐标。
提示:
输出坐标的顺序不重要
m 和 n 都小于150
示例:
给定下面的 5x5 矩阵:
太平洋 ~ ~ ~ ~ ~
~ 1 2 2 3 (5) *
~ 3 2 3 (4) (4) *
~ 2 4 (5) 3 1 *
~ (6) (7) 1 4 5 *
~ (5) 1 1 2 4 *
* * * * * 大西洋
返回: [[0, 4], [1, 3], [1, 4], [2, 2], [3, 0], [3, 1], [4, 0]] (上图中带括号的单元).
思路: 本题按照水流方向正向找比较困难,而从大西洋或太平洋出发逆着水流找相对容易,利用DFS,找下一个高度大于等于当前点的位置,最后取同时被大西洋出发和太平洋出发遍历到的点,就是答案。
代码实现:
class Solution:
def pacificAtlantic(self, matrix):
m = len(matrix)
if m == 0: return []
n = len(matrix[0])
if n == 0: return []
res1, res2 = set(), set() # 流向太平洋与大西洋的位置
def dfs(i, j, res):
res.add((i,j))
for x, y in [(-1,0),(1,0),(0,-1),(0,1)]:
new_i = i + x
new_j = j + y
if 0<=new_i<m and 0<=new_j<n and matrix[i][j] <= matrix[new_i][new_j] \
and (new_i,new_j) not in res:
dfs(new_i, new_j, res)
for i in range(m): # 从左侧的太平洋开始
dfs(i, 0, res1)
for j in range(n): # 从上方的太平洋开始
dfs(0, j, res1)
for i in range(m): # 从右侧的大西洋开始
dfs(i, n-1, res2)
for j in range(n): # 从下方的大西洋开始
dfs(m-1, j, res2)
return res1 & res2 # 两个位置结果取交集
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
输入:"23"
输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
说明:
尽管上面的答案是按字典序排列的,但是你可以任意选择答案输出的顺序。
思路:
这类让求全排列的问题,通常都是使用回溯法。关于回溯法,推荐看这篇文章:https://leetcode-cn.com/problems/permutations/solution/hui-su-suan-fa-xiang-jie-by-labuladong-2/
代码实现:
class Solution:
def letterCombinations(self, digits: str):
if len(digits) == 0:
return []
hash = {"2":["a","b","c"],"3":["d","e","f"],"4":["g","h","i"],
"5":["j","k","l"],"6":["m","n","o"],"7":["p","q","r","s"],
"8":["t","u","v"],"9":["w","x","y","z"]}
def backtrack(conbination, nextdigit):
if len(nextdigit) == 0:
res.append(conbination)
return
for letter in hash[nextdigit[0]]:
backtrack(conbination + letter, nextdigit[1:])
res = []
backtrack('', digits)
return res
给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。
有效的 IP 地址正好由四个整数(每个整数位于 0 到 255 之间组成),整数之间用 ‘.’ 分隔。
示例:
输入: "25525511135"
输出: ["255.255.11.135", "255.255.111.35"]
思路: 使用回溯法,根据IP地址的规则限制,每次只能截取一位、两位或三位;截取一位没有规则限制;截取两位需要保证最左不能为0;截取三位需要保证最左不能为0且这三位数不能大于256。
代码实现:
class Solution:
def restoreIpAddresses(self, s: str):
r = []
def restore(count=0, ip='', s=''):
if count == 4:
if s == '':
r.append(ip[:-1]) # 最后一个不取是因为最后一个是"."
return
if len(s) > 0: # 截取一位
restore(count+1, ip+s[0]+'.', s[1:])
if len(s) > 1 and s[0] != '0': # 截取两位,且最左不能为0
restore(count+1, ip+s[:2]+'.', s[2:])
if len(s) > 2 and s[0] != '0' and int(s[0:3]) < 256: # 截取三位,且最左不能为0,且这三位数不能大于256
restore(count+1, ip+s[:3]+'.', s[3:])
restore(0, '', s)
return r
给定一个二维网格和一个单词,找出该单词是否存在于网格中。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
示例:
board =
[
['A','B','C','E'],
['S','F','C','S'],
['A','D','E','E']
]
给定 word = "ABCCED", 返回 true
给定 word = "SEE", 返回 true
给定 word = "ABCB", 返回 false
提示:
board 和 word 中只包含大写和小写英文字母。
1 <= board.length <= 200
1 <= board[i].length <= 200
1 <= word.length <= 10^3
思路: 对每个格子都要从头搜索,如果搜索不到返回False,只要有一个搜索到就返回True。在DFS时,如果遇到不匹配的需要回溯并且状态重置。
代码实现:
class Solution:
direction = [(-1, 0), (0, -1), (1, 0), (0, 1)]
def exist(self, board, word: str) -> bool:
def backtrack(x, y, index):
if index == len(word)-1: # 递归终止条件
return board[x][y] == word[index]
if board[x][y] == word[index]:
mark[x][y] = True # 先把位置标记为已访问,如果下一个不匹配则要释放
for dir in self.direction:
new_x = x + dir[0]
new_y = y + dir[1]
if 0 <= new_x < m and 0 <= new_y < n and not mark[new_x][new_y] \
and backtrack(new_x,new_y,index+1):
# 说明一路搜索到最后
return True
mark[x][y] = False # 回溯,将这个位置释放为未访问状态
return False
m = len(board)
n = len(board[0])
mark = [[False for _ in range(n)] for _ in range(m)]
for i in range(m):
for j in range(n):
# 对每个格子都从头开始搜
if backtrack(i, j, 0):
return True
return False
给定一个二叉树,返回所有从根节点到叶子节点的路径。
说明: 叶子节点是指没有子节点的节点。
示例:
输入:
1
/ \
2 3
\
5
输出: ["1->2->5", "1->3"]
解释: 所有根节点到叶子节点的路径为: 1->2->5, 1->3
思路: 利用path存储每次走的路径,用res存储最终结果。从根节点开始一直往下找,假如到达叶子节点则把这条路径添加到res;假如不是叶子节点就继续递归,每次递归都在路径里加上 “->” 作为连接符。
代码实现:
# Definition for a binary tree node.
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
class Solution:
def binaryTreePaths(self, root: TreeNode):
def backtrack(root, path):
if root:
path += str(root.val)
if not root.left and not root.right: # 叶子节点
res.append(path)
else: # 非叶子节点
path += "->"
backtrack(root.left, path)
backtrack(root.right, path)
res = []
backtrack(root,"")
return res
给定一个 没有重复 数字的序列,返回其所有可能的全排列。
示例:
输入: [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):
def backtrack(nums, li):
if not nums:
res.append(li)
return
for i in range(len(nums)):
backtrack(nums[:i]+nums[i+1:], li+[nums[i]]) # 利用列表的加法
res = []
backtrack(nums,[])
return res
给定一个可包含重复数字的序列,返回所有不重复的全排列。
示例:
输入: [1,1,2]
输出:
[
[1,1,2],
[1,2,1],
[2,1,1]
]
思路: 本题在46题基础上,增加了重复数,为了提高效率,需要在深搜过程中合理剪枝。首先把数组排序,使得重复的数挨在一起,在深搜函数遍历数组那里,先对每个数进行判断,如果当前数和他前一个数相等,就不对他进行深搜。
代码实现:
class Solution:
def permuteUnique(self, nums) :
def backtrack(nums,li):
if not nums:
res.append(li)
return
for i in range(len(nums)):
if i > 0 and nums[i] == nums[i-1]: # 不对重复的数进行深搜
continue
backtrack(nums[:i] + nums[i+1:], li+[nums[i]])
res = []
nums.sort() # 这里排序可以保证把重复的数放在一起以便被检测到
backtrack(nums,[])
return res
给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。
示例:
输入: n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
思路: 利用回溯,用i来控制每次从哪个数开始深搜,用k来控制深搜的个数(k个数的组合)。
代码实现:
class Solution:
def combine(self, n: int, k: int):
def backtrack(i,k,li):
if k == 0:
res.append(li)
return
for i in range(i,n+1):
backtrack(i+1, k-1, li+[i])
res = []
backtrack(1,k,[])
return 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]
]
思路: 利用回溯,每个数可以重复使用任意次,对于数组中每个数nums[i],在递归时就有选与不选两种情况:1.如果选nums[i],递归到下一层时,由于nums[i]可以重复使用,因此下一层的起始下标还是i,但temp和target发生变化;2.如果不选nums[i],递归到下一层时,由于在这一层递归的时候什么都不做,因此temp和target不用变化,但i需要+1。
代码实现:
class Solution:
def combinationSum(self, candidates, target: int):
if (not candidates):
return []
n = len(candidates)
res = []
candidates.sort() # 排序是为了后面判断target是否小于候选数,以便剪枝
def helper(i, tmp, target):
if (target == 0):
res.append(tmp)
return
if (i == n or target < candidates[i]):
return
helper(i, tmp + [candidates[i]], target - candidates[i])
helper(i + 1, tmp, target)
helper(0, [], target)
return res
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
说明:
所有数字(包括目标数)都是正整数。
解集不能包含重复的组合。
示例 1:
输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]
示例 2:
输入: candidates = [2,5,2,1,2], target = 5,
所求解集为:
[
[1,2,2],
[5]
]
思路: 利用回溯,重点在于避免重复:
例1 1
/ \
2 2 这种情况不会发生 但是却允许了不同层级之间的重复即:
/ \
5 5
例2 1
/
2 这种情况确是允许的
/
2
首先 i-1 == i 是用于判定当前元素是否和之前元素相同的语句。这个语句就能砍掉例1。可是问题来了,如果把所有当前与之前一个元素相同的都砍掉,那么例二的情况也会消失。 因为当第二个2出现的时候,他就和前一个2相同了。
那么如何保留例2呢?就用 i > begin 来避免这种情况,你发现例1中的两个2是处在同一个层级上的,例2的两个2是处在不同层级上的。在一个for循环中,所有被遍历到的数都是属于一个层级的。我们要让一个层级中,必须出现且只出现一个2,那么就放过第一个出现重复的2,但不放过后面出现的2。第一个出现的2的特点就是 i == begin. 第二个出现的2 特点是 i > begin。
代码实现:
class Solution:
def combinationSum2(self, candidates, target: int):
def backtrack(begin, li, target):
if target == 0:
res.append(li[:]) # li[:]是对li作浅拷贝,与li地址不同,对li的pop并不影响li[:]
return
for i in range(begin,len(candidates)):
if target < candidates[i]:
break
if i > begin and candidates[i] == candidates[i-1]:
continue
li.append(candidates[i])
backtrack(i+1, li, target-candidates[i])
li.pop()
if not candidates:
return []
candidates.sort()
res = []
backtrack(0, [], target)
return res
找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
说明:
所有数字都是正整数。
解集不能包含重复的组合。
示例 1:
输入: k = 3, n = 7
输出: [[1,2,4]]
示例 2:
输入: k = 3, n = 9
输出: [[1,2,6], [1,3,5], [2,3,4]]
思路: 利用回溯,count表示当前已经使用的数字数,index表示当前访问的数字,li记录当前中间结果,target表示下一步的目标和。
代码实现:
class Solution:
def combinationSum3(self, k: int, n: int):
def backtrack(count, index, li, target):
if count == k:
if target == 0:
res.append(li)
return
for j in range(index,10):
if j > target:
break
backtrack(count+1, j+1, li+[j], target-j)
res = []
backtrack(0, 1, [], n)
return res
给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例:
输入: nums = [1,2,3]
输出:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]
思路: 利用回溯,不用符合某个条件再把 li 添加到 res 中,每次循环都 append 当前 li 到 res 中。
代码实现:
class Solution:
def subsets(self, nums):
def backtrack(i,li):
res.append(li)
for j in range(i,len(nums)):
backtrack(j+1, li+[nums[j]])
res = []
backtrack(0, [])
return res
给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例:
输入: [1,2,2]
输出:
[
[2],
[1],
[1,2,2],
[2,2],
[1,2],
[]
]
思路: 为了去除重复的子集,第一时间想到排序。利用回溯,其余的处理和78题与40题类似。
代码实现:
class Solution:
def subsetsWithDup(self, nums):
def backtrack(index,li):
res.append(li)
for j in range(index,len(nums)):
if j > index and nums[j] == nums[j-1]:
continue
backtrack(j+1,li+[nums[j]])
res = []
nums.sort()
backtrack(0, [])
return res
给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。
返回 s 所有可能的分割方案。
示例:
输入: "aab"
输出:
[
["aa","b"],
["a","a","b"]
]
思路: 利用回溯,每次遍历检查当前字符串是否是回文串。
代码实现:
class Solution:
def partition(self, s: str):
def backtrack(s, li):
if not s:
res.append(li)
return
for i in range(1,len(s)+1):
if s[:i] == s[:i][::-1]:
backtrack(s[i:], li+[s[:i]])
res = []
backtrack(s, [])
return res
编写一个程序,通过已填充的空格来解决数独问题。
一个数独的解法需遵循如下规则:
数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
Note:
给定的数独序列只包含数字 1-9 和字符 '.' 。
你可以假设给定的数独只有唯一解。
给定数独永远是 9x9 形式的。
思路: 一个回溯函数backtrack对board[i][j] 进行穷举尝试,一个isValid函数判断要填入的数字是否合法(符合数独规则)。
代码实现:
class Solution:
def solveSudoku(self, board) -> None:
"""
Do not return anything, modify board in-place instead.
"""
# 对 board[i][j] 进行穷举尝试
def backtrack(board, i, j):
m, n = 9, 9
if j == n: # 走到9才越界,进入下一行
return backtrack(board, i+1, 0)
if i == m: # 走到最后一行,找到一个可行解
return True
if board[i][j] != '.': # 当前是预设数字,直接跳到下一个
return backtrack(board, i, j+1)
ch_list = ['1','2','3','4','5','6','7','8','9']
for ch in ch_list:
if not isValid(board, i, j, ch): # 如果遇到不合法的数字,则跳过
continue
board[i][j] = ch # 做选择
if backtrack(board, i, j+1): # 如果找到一个可行解,立即结束
return True
board[i][j] = '.' # 撤销选择
# 穷举完 1~9,依然没有找到可行解,此路不通
return False
# 判断 board[i][j] 是否可以填入 n
def isValid(board, r, c, n):
for i in range(9):
# 判断行是否存在重复
if board[r][i] == n: return False
# 判断列是否存在重复
if board[i][c] == n: return False
# 判断 3 * 3 方框是否存在重复
if board[(r//3)*3 + i//3][(c//3)*3 + i%3] == n:
return False
return True
backtrack(board, 0, 0)
n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
上图为 8 皇后问题的一种解法。
给定一个整数 n,返回所有不同的 n 皇后问题的解决方案。
每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。
示例:
输入: 4
输出: [
[".Q..", // 解法 1
"...Q",
"Q...",
"..Q."],
["..Q.", // 解法 2
"Q...",
"...Q",
".Q.."]
]
解释: 4 皇后问题存在两个不同的解法。
提示: 皇后,是国际象棋中的棋子,意味着国王的妻子。皇后只做一件事,那就是“吃子”。当她遇见可以吃的棋子时,就迅速冲上去吃掉棋子。当然,她横、竖、斜都可走一到七步,可进可退。(引用自 百度百科 - 皇后 )
思路: 本题与37数独题不相同的地方在于如果已经在某行某列放置了皇后,那么该行就不可能再放置皇后了,直接进入下一行。并且要注意触发结束条件时,把所有结果放在二维列表返回。由于是一行一行搜索,当前行上面是已经放置皇后的,下面还是空棋盘,在判断当前位置是否合法时,只用查看同列以及左斜上方和右斜上方是否有皇后互相冲突即可。
代码实现:
class Solution:
def solveNQueens(self, n: int):
board = [['.' for _ in range(n)] for _ in range(n)] # 初始化空棋盘
# 路径:board中小于row的那些行都已经成功放置了皇后
# 选择列表:第row行的左右列都是放置皇后的选择
# 结束条件:row超过board的最后一行
def backtrack(board, row):
if row == n: # 触发结束条件
li = []
for row in board:
li.append("".join(row))
res.append(li)
return
for col in range(n):
if not isValid(board,row,col): # 排除不合法选择
continue
board[row][col] = 'Q' # 做选择
backtrack(board, row+1) # 进入下一行
board[row][col] = '.' # 撤销选择
# 判断 board[row][col] 是否可以填入 'Q'
def isValid(board, row, col):
for i in range(n):
if board[i][col] == 'Q': # 检查列是否有皇后互相冲突
return False
i, j = row-1, col+1
while i >= 0 and j < n: # 检查右斜上方是否有皇后互相冲突
if board[i][j] == 'Q':
return False
i -= 1
j += 1
i, j = row-1, col-1
while i >= 0 and j >= 0: # 检查左斜上方是否有皇后互相冲突
if board[i][j] == 'Q':
return False
i -= 1
j -= 1
return True
res = []
backtrack(board, 0)
return res
================================================================
以上为个人学习笔记总结,供学习参考交流,未经允许禁止转载或商用。