五、分治
241. 为运算表达式设计优先级
给定一个含有数字和运算符的字符串,为表达式添加括号,改变其运算优先级以求出不同的结果。你需要给出所有可能的组合的结果。有效的运算符号包含 +, - 以及 * 。
示例 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
96. 不同的二叉搜索树
给定一个整数 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]
95. 不同的二叉搜索树 II
给定一个整数 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
六、搜索
BFS
1091. 二进制矩阵中的最短路径
在一个 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
279. 完全平方数
给定正整数 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
127. 单词接龙
给定两个单词(beginWord 和 endWord)和一个字典,找到从 beg