¶剑指offer – 搜索与回溯算法
¶剑指 Offer 12. 矩阵中的路径
Difficulty: 中等
给定一个 m x n
二维字符网格 board
和一个字符串单词 word
。如果 word
存在于网格中,返回 true
;否则,返回 false
。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
例如,在下面的 3×4 的矩阵中包含单词 “ABCCED”(单词中的字母已标出)。
示例 1:
1 | 输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED" |
示例 2:
1 | 输入:board = [["a","b"],["c","d"]], word = "abcd" |
提示:
1 <= board.length <= 200
1 <= board[i].length <= 200
board
和word
仅由大小写英文字母组成
Solution
1 | class Solution { |
时间复杂度: $O(3^KMN)$ , 最差情况下,需要遍历矩阵中长度为 K 字符串的所有方案,时间复杂度为 $O(3^K)$;矩阵中共有 $MN $ 个起点,时间复杂度为 $O(MN)$。
- 方案数计算: 设字符串长度为 KK ,搜索中每个字符有上、下、左、右四个方向可以选择,舍弃回头(上个字符)的方向,剩下 3 种选择,因此方案数的复杂度为 $O(3^K)$。
空间复杂度: $O(K)$, 搜索过程中的递归深度不超过 K ,因此系统因函数调用累计使用的栈空间占用 $O(K)$(因为函数返回后,系统调用的栈空间会释放)。最坏情况下 $K = MNK=MN$ ,递归深度为 $MN$ ,此时系统栈使用 $O(MN)$ 的额外空间。
¶剑指 Offer 13. 机器人的运动范围
Difficulty: 中等
地上有一个m行n列的方格,从坐标 [0,0]
到坐标 [m-1,n-1]
。一个机器人从坐标 [0, 0]
的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?
示例 1:
1 | 输入:m = 2, n = 3, k = 1 |
示例 2:
1 | 输入:m = 3, n = 1, k = 0 |
提示:
1 <= n,m <= 100
0 <= k <= 20
Solution
方法一:DFS
1 | class Solution { |
方法二:BFS
1 | class Solution { |
两种方法时间复杂度,空间复杂度分析相同。
时间复杂度: $O(MN)$ ,最差情况下,机器人遍历矩阵所有单元格,此时时间复杂度为 $O(MN)$ 。
空间复杂度: $O(MN)$, 最差情况下,访问矩阵存储所有单元格的索引,使用 $O(MN)$ 的额外空间。
¶剑指 Offer 26. 树的子结构
Difficulty: 中等
输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)
B是A的子结构, 即 A中有出现和B相同的结构和节点值。
例如:
给定的树 A:
1 | 3 |
给定的树 B:
1 | 4 |
返回 true,因为 B 与 A 的一个子树拥有相同的结构和节点值。
示例 1:
1 | 输入:A = [1,2,3], B = [3,1] |
示例 2:
1 | 输入:A = [3,4,5,1,2], B = [4,1] |
限制:
0 <= 节点个数 <= 10000
Solution
1 | /** |
时间复杂度:$O(MN)$, 其中 M, NM,N 分别为树 A 和 树 B 的节点数量;先序遍历树 A 占用 $O(M)$ ,每次调用 recur(A, B) 判断占用 $O(N)$ 。
空间复杂度: $O(M)$ , 当树 A 和树 B 都退化为链表时,递归调用深度最大。当 $M≤N$ 时,遍历树 A 与递归判断的总递归深度为 M ;当 $M>N$ 时,最差情况为遍历至树 A 的叶节点,此时总递归深度为 M。
¶剑指 Offer 27. 二叉树的镜像
Difficulty: 简单
请完成一个函数,输入一个二叉树,该函数输出它的镜像。
例如输入:
1 | 4 |
镜像输出:
1 | 4 |
示例 1:
1 | 输入:root = [4,2,7,1,3,6,9] |
限制:
0 <= 节点个数 <= 1000
Solution
方法一:递归
1 | /** |
时间复杂度:$O(N)$, 其中 N 为二叉树的节点数量,建立二叉树镜像需要遍历树的所有节点。
空间复杂度:$O(N)$, 最差情况下(当二叉树退化为链表),递归时系统需使用 $O(N)$ 大小的栈空间。
方法二:迭代
1 | class Solution { |
时间复杂度:$O(N)$, 其中 N 为二叉树的节点数量,建立二叉树镜像需要遍历树的所有节点。
空间复杂度:$O(N) $, 最差情况下,栈 stack 最多同时存储 $\frac{N + 1}{2}$ ,占用 $O(N)$ 额外空间。
¶剑指 Offer 28. 对称的二叉树
Difficulty: 简单
请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。
例如,二叉树 [1,2,2,3,4,4,3] 是对称的。
1 | 1 |
但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:
1 | 1 |
示例 1:
1 | 输入:root = [1,2,2,3,4,4,3] |
示例 2:
1 | 输入:root = [1,2,2,null,3,null,3] |
限制:
0 <= 节点个数 <= 1000
Solution
方法一:递归
1 | /** |
时间复杂度:$O(N)$,其中 N 为二叉树的节点数量,每次执行 judge()
可以判断一对节点是否对称,因此最多调用 N*/2 次 judge()
方法。
空间复杂度:$O(N)$, 最差情况下(当二叉树退化为链表),递归时系统需使用 $O(N)$ 大小的栈空间。
方法二:迭代
1 | class Solution { |
时间复杂度:$O(N)$, 其中 N 为二叉树的节点数量,建立二叉树镜像需要遍历树的所有节点。
空间复杂度:$O(N) $, 这里需要用一个队列来维护节点,每个节点最多进队一次,出队一次,队列中最多不会超过 n 个点,故渐进空间复杂度为 $O(N) $
¶剑指 Offer 32 - I. 从上到下打印二叉树
Difficulty: 中等
从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。
例如:
给定二叉树: [3,9,20,null,null,15,7]
,
1 | 3 |
返回:
1 | [3,9,20,15,7] |
提示:
节点总数 <= 1000
Solution
1 | /** |
¶剑指 Offer 32 - II. 从上到下打印二叉树 II
Difficulty: 简单
从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。
例如:
给定二叉树: [3,9,20,null,null,15,7]
,
1 | 3 |
返回其层次遍历结果:
1 | [ |
提示:
节点总数 <= 1000
Solution
1 | /** |
¶剑指 Offer 32 - III. 从上到下打印二叉树 III
Difficulty: 中等
请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。
例如:
给定二叉树: [3,9,20,null,null,15,7]
,
1 | 3 |
返回其层次遍历结果:
1 | [ |
提示:
节点总数 <= 1000
Solution
1 | /** |
剑指offer32 3 道题目时间空间复杂度分析相同
时间复杂度: $O(N)$, N 为二叉树的节点数量,即 BFS 需循环 N 次。
空间复杂度 :$O(N)$,最差情况下,即当树为平衡二叉树时,最多有 N/2 个树节点同时在 queue 中,使用 $O(N)$ 大小的额外空间。
¶剑指 Offer 34. 二叉树中和为某一值的路径
Difficulty: 中等
输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。
示例:
给定如下二叉树,以及目标和 target = 22
,
1 | 5 |
返回:
1 | [ |
提示:
节点总数 <= 10000
Solution
1 | /** |
时间复杂度:$O(N)$, 其中 N 为二叉树的节点数量,需要遍历树的所有节点。
空间复杂度:$O(N)$, 最差情况下(当二叉树退化为链表),path 存储所有节点,使用额外 $O(N)$ 空间
¶剑指 Offer 36. 二叉搜索树与双向链表
Difficulty: 中等
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。
为了让您更好地理解问题,以下面的二叉搜索树为例:
我们希望将这个二叉搜索树转化为双向循环链表。链表中的每个节点都有一个前驱和后继指针。对于双向循环链表,第一个节点的前驱是最后一个节点,最后一个节点的后继是第一个节点。
下图展示了上面的二叉搜索树转化成的链表。“head” 表示指向链表中有最小元素的节点。
特别地,我们希望可以就地完成转换操作。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继。还需要返回链表中的第一个节点的指针。
Solution
1 | /* |
时间复杂度:$O(N)$, 其中 N 为二叉树的节点数量,需要遍历树的所有节点。
空间复杂度:$O(N)$, 最差情况下(当二叉树退化为链表),递归深度 N,使用系统 $O(N)$ 栈空间。
¶剑指 Offer 37. 序列化二叉树
Difficulty: 困难
请实现两个函数,分别用来序列化和反序列化二叉树。
**示例: **
1 | 你可以将以下二叉树: |
Solution
1 | /** |
序列化,反序列化时间空间复杂度相同。其中 N 为可能的节点数。
时间复杂度: $O(N)$
空间复杂度:$O(N)$
¶剑指 Offer 38. 字符串的排列
Difficulty: 中等
输入一个字符串,打印出该字符串中字符的所有排列。
你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。
示例:
1 | 输入:s = "abc" |
限制:
1 <= s 的长度 <= 8
Solution
方法一:常规回溯
1 | // 该方法遍历所有可能,最后在输入的时候通过再一次的查找重复项来去重,会超时 ! |
方法二:记忆化回溯
1 | class Solution { |
备注 :carl 大佬写的两篇相关文章,感受颇深!
去重思路:回溯算法:求组合总和(三)
树层上去重,与树枝上去重的区别:回溯算法:排列问题(二) (qq.com)
方法三:交换思想求取排列
1 | class Solution { |
¶剑指 Offer 54. 二叉搜索树的第k大节点
Difficulty: 简单
给定一棵二叉搜索树,请找出其中第k大的节点。
示例 1:
1 | 输入: root = [3,1,4,null,2], k = 1 |
示例 2:
1 | 输入: root = [5,3,6,2,4,null,null,1], k = 3 |
限制:
1 ≤ k ≤ 二叉搜索树元素个数
Solution
1 | /** |
¶剑指 Offer 55 - I. 二叉树的深度
Difficulty: 简单
输入一棵二叉树的根节点,求该树的深度。从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径,最长路径的长度为树的深度。
例如:
给定二叉树 [3,9,20,null,null,15,7]
,
1 | 3 |
返回它的最大深度 3 。
提示:
节点总数 <= 10000
Solution
1 | /** |
时间复杂度 :$O(N)$, N 为树的节点数量,计算树的深度需要遍历所有节点。
空间复杂度:$O(N)$, 最差情况下(当树退化为链表时),递归深度可达到 N 。
¶剑指 Offer 55 - II. 平衡二叉树
Difficulty: 简单
输入一棵二叉树的根节点,判断该树是不是平衡二叉树。如果某二叉树中任意节点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。
示例 1:
给定二叉树 [3,9,20,null,null,15,7]
1 | 3 |
返回 true
。
示例 2:
给定二叉树 [1,2,2,3,3,null,null,4,4]
1 | 1 |
返回 false
。
限制:
0 <= 树的结点个数 <= 10000
Solution
方法一:先序遍历 + 判断深度
1 | /** |
方法二:后序遍历 + 剪枝
1 | /** |
时间复杂度 :$O(N)$, N 为树的节点数量,计算树的深度需要遍历所有节点。
空间复杂度:$O(N)$, 最差情况下(当树退化为链表时),递归深度可达到 N 。
¶剑指 Offer 64. 求1+2+…+n
Difficulty: 中等
求 1+2+...+n
,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
示例 1:
1 | 输入: n = 3 |
示例 2:
1 | 输入: n = 9 |
限制:
1 <= n <= 10000
Solution
1 | class Solution { |
时间复杂度:$O(n) $, 计算 $n + (n-1) + … + 2 + 1n+(n−1)+…+2+1$ 需要开启 n 个递归函数。
空间复杂度: $O(n)$, 递归深度达到 n ,系统使用 $O(n)$ 大小的额外空间。
¶剑指 Offer 68 - I. 二叉搜索树的最近公共祖先
Difficulty: 简单
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5]
示例 1:
1 | 输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8 |
示例 2:
1 | 输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4 |
说明:
- 所有节点的值都是唯一的。
- p、q 为不同节点且均存在于给定的二叉搜索树中。
Solution
方法一:递归
1 | /** |
时间复杂度: $O(N)$ ,其中 N 为二叉树节点数;每循环一轮排除一层,二叉搜索树的层数最小为 $\log N$(满二叉树),最大为 N (退化为链表)。
空间复杂度: $O(N)$ , 最差情况下,即树退化为链表时,递归深度达到树的层数 N 。
方法二:迭代
1 | class Solution { |
时间复杂度: $O(N)$ ,其中 N 为二叉树节点数;每循环一轮排除一层,二叉搜索树的层数最小为 $\log N$(满二叉树),最大为 N (退化为链表)。
空间复杂度: $O(1)$ ,使用常数大小的额外空间。
¶剑指 Offer 68 - II. 二叉树的最近公共祖先
Difficulty: 简单
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]
示例 1:
1 | 输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1 |
示例 2:
1 | 输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4 |
说明:
- 所有节点的值都是唯一的。
- p、q 为不同节点且均存在于给定的二叉树中。
Solution
1 | /** |
时间复杂度 :$O(N)$, N 为树的节点数量,计算树的深度需要遍历所有节点。
空间复杂度:$O(N)$, 最差情况下(当树退化为链表时),递归深度可达到 N 。
¶参考资料
图解算法数据结构 - LeetBook - 力扣(LeetCode)全球极客挚爱的技术成长平台 (leetcode-cn.com)