剑指 Offer 34. 二叉树中和为某一值的路径
题目描述
给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。
叶子节点 是指没有子节点的节点。
提示:
- 树中节点总数在范围 [0, 5000] 内
- − 1000 < = N o d e . v a l < = 1000 -1000 <= Node.val <= 1000 −1000<=Node.val<=1000
-
−
1000
<
=
t
a
r
g
e
t
S
u
m
<
=
1000
-1000 <= targetSum <= 1000
−1000<=targetSum<=1000
DFS 预防污染
-写法1
思路
- 确定递归参数、返回类型
需要遍历整颗树,所以不需要返回类型。dfs(TreeNode root, int target, List<Integer> cntList)
- 终止条件
当前节点为 叶子节点(root.left == null && root.right == null
),终止遍历:- 若总和为 t a r g e t target target,则收集当前路径;
- 否则,不作收集,继续迭代。
- 单层逻辑
分别递归遍历 左、右子树。
Note:
- 这里采用的是“
避免污染
”的递归写法,即 不允许 r o o t = = n u l l root == null root==null 的节点 进入 dfs。 - 这种方法,收集单条路径结果 (
cntList.add(root.val)
)放在 dfs 的最开头,而不是和 左、右子树 dfs 的上面。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> pathSum(TreeNode root, int target) {
if (root == null) return res;
dfs(root, target - root.val, new ArrayList<>());
return res;
}
public void dfs(TreeNode root, int target, List<Integer> cntList) {
cntList.add(root.val); // 保证root为null不会进入dfs
// System.out.println("cntList = " + cntList);
// 终止条件(遇到叶子节点,则终止)
if (root.left == null && root.right == null) {
if (target == 0) {
res.add(new ArrayList<>(cntList));
}
return;
}
// 左
if (root.left != null) {
dfs(root.left, target - root.left.val, cntList);
cntList.remove(cntList.size() - 1); // 回溯
}
// 右
if (root.right != null) {
dfs(root.right, target - root.right.val, cntList);
cntList.remove(cntList.size() - 1); // 回溯
}
}
}
- 时间复杂度: O ( n ) O(n) O(n) (先序遍历二叉树中所有节点 O ( n ) O(n) O(n),每次dfs时间复杂度为 O ( 1 ) O(1) O(1),所以最终时间复杂度为 O ( 1 ∗ n ) O(1 * n) O(1∗n))
- 空间复杂度: O ( n ) O(n) O(n) (树退化为链表时,即递归深度为 O ( n ) O(n) O(n))
DFS 预防污染
-写法2
如果想将 每次收集节点/回溯节点 都和
DFS
放在一起的话,则可以采用如下写法。
- 若
root == null
,则不进入 dfs,直接返回即可; - 否则,将
root.val
在进入 dfs 就加入cnt
中; dfs
终止条件:若当前节点为叶子节点,且target == root.val
,则说明当前路径符合题意,需要收集之;- 否则,左子树非空,则
dfs
遍历左子树(此时,收集节点和回溯 都是和 dfs 紧密在一起的); - 否则,右子树非空时,同理。
class Solution {
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> pathSum(TreeNode root, int target) {
if (root == null) return res;
List<Integer> cnt = new ArrayList<>();
cnt.add(root.val); // 避免污染的写法
dfs(root, target, cnt);
return res;
}
void dfs(TreeNode root, int target, List<Integer> cnt) {
// 叶子节点(保证null不会进入递归函数dfs中)
if (root.left == null && root.right == null) {
// 该路径和为target,则收集
if (target == root.val) { // 避免污染的写法,所以是 == root.val,而不是 == 0
res.add(new ArrayList<>(cnt));
}
return;
}
// 保证null不会进入下层递归dfs
if (root.left != null) {
cnt.add(root.left.val);
dfs(root.left, target - root.val, cnt); // target的回溯暗含在递归中
cnt.remove(cnt.size() - 1); // 回溯
}
if (root.right != null) {
cnt.add(root.right.val);
dfs(root.right, target - root.val, cnt);
cnt.remove(cnt.size() - 1); // 回溯
}
}
}
- 时间复杂度: O ( n ) O(n) O(n) (先序遍历二叉树中所有节点 O ( n ) O(n) O(n),每次dfs时间复杂度为 O ( 1 ) O(1) O(1),所以最终时间复杂度为 O ( 1 ∗ n ) O(1 * n) O(1∗n))
- 空间复杂度: O ( n ) O(n) O(n) (树退化为链表时,即递归深度为 O ( n ) O(n) O(n))
剑指 Offer 36. 二叉搜索树与双向链表
题目描述
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。
DFS + pre 指针
⭐️
参考
DFS 思路 🤔
- 使用前驱指针
pre
,记录中序序列上一个遍历的节点 - 由于
BST
中序遍历结果是升序的,且题目要求转换后的链表“升序”,所以这里dfs
采用 “中序遍历”- 中序遍历左子树
- 根逻辑处理
- 若
pre == null
,则说明找到二叉树的最左下节点,即转换后链表的头节点,则head = root;
; - 若
pre != null
,则说明当前节点是其前驱节点pre
的后继节点,则pre.right = root;
- 更新当前节点的前驱指针,即
root.left = pre;
- 注意⚠️:这里和【线索二叉树】不太一样,这里不用判断
root.left
不为空
- 注意⚠️:这里和【线索二叉树】不太一样,这里不用判断
- 更新前驱节点
pre
- 若
- 中序遍历右子树
dfs
结束后,此时链表中 left
、right
已经更新完事了,但是题目要求的是 “循环链表”,所以还需要将链表的 “首尾相连”,构成 “循环链表”:
head.left = pre;
pre.right = head;
/*
// Definition for a Node.
class Node {
public int val;
public Node left;
public Node right;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val,Node _left,Node _right) {
val = _val;
left = _left;
right = _right;
}
};
*/
class Solution {
// 前驱指针(记录中序序列上一个遍历的节点)
Node pre = null;
Node head = null;
public Node treeToDoublyList(Node root) {
if (root == null) {
return null;
}
dfs(root);
// 首尾相连,构成循环链表
head.left = pre;
pre.right = head;
return head;
}
void dfs(Node root) {
if (root == null) return;
// 中序
dfs(root.left);
// 根
root.left = pre; // 和【线索二叉树】不太一样,这里不用判断 root.left 不为空
if (pre != null) {
/** 中序-线索化的逻辑 */
// if (root.left == null) { // 当前节点左指针为空时,
// root.left = pre;
// }
// if (pre.right == null) {
// pre.right = root;
// }
pre.right = root;
} else { // 最左下,即头节点
head = root;
}
pre = root; // 更新前驱
dfs(root.right);
}
}
- 时间复杂度: O ( N ) O(N) O(N) (中序遍历需要访问所有节点)
- 空间复杂度: O ( N ) O(N) O(N) (最差情况下,即树退化为链表时,递归深度达到 N,使用 O(N) 栈空间)
复杂度分析:K佬
剑指 Offer 54. 二叉搜索树的第 k 大节点
题目描述
给定一棵二叉搜索树,请找出其中第k大的节点。
限制:
- 1 ≤ k ≤ 二叉搜索树元素个数
DFS + 排序
思路
- 一种通用的做法是,即如果是普通的二叉树时,则可以通过一遍遍历(可以采用任意的遍历方式)将二叉树中所有节点都保存到
res
中; - 等二叉树中所有节点收集完毕时,对
res
降序排序,然后获取其第k
个元素,即为所求。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
List<Integer> res = new ArrayList<>();
public int kthLargest(TreeNode root, int k) {
if (root == null) return 0;
dfs(root);
// 降序排序
Collections.sort(res, (o1, o2) -> (o2 - o1));
System.out.println(res);
return res.get(k - 1);
}
void dfs(TreeNode root) {
if (root == null) return;
// 此时遍历方式可以任意,这里以“前序”为例
res.add(root.val);
dfs(root.left);
dfs(root.right);
}
}
- 时间复杂度: O ( n ) O(n) O(n) (**先序**遍历二叉树中所有节点为 O ( n ) O(n) O(n),每次dfs时间复杂度为 O ( 1 ) O(1) O(1),所以最终时间复杂度为 O ( 1 ∗ n ) O(1 * n) O(1∗n))
- 空间复杂度:
O
(
n
)
O(n)
O(n) (树退化为链表时,即所有节点都挂在
left
上,此时递归深度为 O ( n ) O(n) O(n))
DFS 反-中序
⭐️
思路:
- BST 中序遍历结果是 升序的,本题要找 降序排序的第
k
k
k 个元素,所以可以 “
反-中序
”遍历 - 即,采用 “
右根左
” 的顺序遍历 BST,取第 k k k 个元素,即可。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
int res = 0;
int index = 0;
public int kthLargest(TreeNode root, int k) {
if (root == null) return 0;
dfs(root, k);
return res;
}
boolean dfs(TreeNode root, int k) {
if (root == null) return false;
// 右左根遍历 --> 保证BST降序
if (dfs(root.right, k)) { // 找到第k大元素,立即返回
return true;
}
if (++index == k) {
res = root.val;
return true;
}
if (dfs(root.left, k)) { // 找到第k大元素,立即返回
return true;
}
return false;
}
}
- 时间复杂度:
O
(
n
)
O(n)
O(n) (最坏情况,即所有节点都挂在
right
上,且 k = = 1 k == 1 k==1时,则需要 “中序” 遍历所有节点 O ( n ) O(n) O(n),每次dfs时间复杂度为 O ( 1 ) O(1) O(1),所以最终时间复杂度为 O ( 1 ∗ n ) O(1 * n) O(1∗n)) - 空间复杂度:
O
(
n
)
O(n)
O(n) (树退化为链表时,即所有节点都挂在
right
上,此时递归深度为 O ( n ) O(n) O(n))