其实只有维护全局变量,引用变量如列表,链表时才需要显式回溯。其他时候都不需要显式回溯。
513.找树左下角的值
class Solution {
public int findBottomLeftValue(TreeNode root) {
Queue<TreeNode> q = new LinkedList<>();
q.add(root);
int num = 0;
while(!q.isEmpty()){
int size = q.size();
num = q.peek().val;
while(size-- > 0){
TreeNode node = q.poll();
if(node.left != null) q.add(node.left);
if(node.right != null) q.add(node.right);
}
}
return num;
}
}
迭代法自己AC了,每一层取数的开始把第一个数拿出来,到最后一层就是最后一层最左边的。
TreeNode node = root;
Queue <TreeNode> q = new ArrayDeque<>();
q.add(root);
while(!q.isEmpty()){
node = q.poll();
if(node.right != null) q.add(node.right);
if(node.left != null) q.add(node.left);
}
return node.val;
茶大人先右再左的思路太牛了,每次看茶大人的代码都会有一种,优雅永不过时的感觉,太雅了。
遍历法没想到思路,看了卡哥的思路。
DFS只要是先左就可前序中序都可,后序不行,后序是高度,先左的最大深度叶子结点记录一下,就是最下的一层左的值(因为先左所以是左下)。
class Solution {
private int Deep = -1;
private int value = 0;
public int findBottomLeftValue(TreeNode root) {
value = root.val;
findLeftValue(root,0);
return value;
}
private void findLeftValue (TreeNode root,int deep) {
if (root == null) return;
if (root.left == null && root.right == null) {
if (deep > Deep) {
value = root.val;
Deep = deep;
}
}
if (root.left != null) findLeftValue(root.left,deep + 1);
if (root.right != null) findLeftValue(root.right,deep + 1);
}
}
自己写的时候递归加回溯出现好多问题,感谢群友的帮助思路终于通了,三种显式递归加回溯,第一种++与--都在外边,第二种都写里边,第三种设置全局变量或者有引用传递类型的变量值
一:
//递归求解最大深度
void ans(TreeNode tr,int tmp){
if(tr==null) return;
tmp++;
maxnum = maxnum<tmp?tmp:maxnum;
ans(tr.left,tmp);
ans(tr.right,tmp);
tmp--;
}
二:
private void getDeepth(TreeNode root, int deepth){
if(root == null) return;
System.out.println(deepth);
if(deepth > Maxdeepth){
Maxdeepth = deepth;
res = root.val;
}
if(root.left != null){
deepth++;
getDeepth(root.left,deepth);
deepth--;
}
if(root.right != null){
deepth++;
getDeepth(root.right,deepth);
deepth--;
}
}
三:
class Solution {
int res = 0;
int Maxdeepth = -1;
int deepth;
public int findBottomLeftValue(TreeNode root) {
getDeepth(root);
return res;
}
private void getDeepth(TreeNode root){
if(root == null) return;
deepth++;
System.out.println(deepth);
if(deepth > Maxdeepth){
Maxdeepth = deepth;
res = root.val;
}
if(root.left != null){
getDeepth(root.left);
deepth--;
}
if(root.right != null){
getDeepth(root.right);
deepth--;
}
}
}
class Solution {
/**
* 递归法
*/
public List<String> binaryTreePaths(TreeNode root) {
List<String> res = new ArrayList<>();// 存最终的结果
if (root == null) {
return res;
}
List<Integer> paths = new ArrayList<>();// 作为结果中的路径
traversal(root, paths, res);
return res;
}
private void traversal(TreeNode root, List<Integer> paths, List<String> res) {
paths.add(root.val);// 前序遍历,中
// 遇到叶子结点
if (root.left == null && root.right == null) {
// 输出
StringBuilder sb = new StringBuilder();// StringBuilder用来拼接字符串,速度更快
for (int i = 0; i < paths.size() - 1; i++) {
sb.append(paths.get(i)).append("->");
}
sb.append(paths.get(paths.size() - 1));// 记录最后一个节点
res.add(sb.toString());// 收集一个路径
return;
}
// 递归和回溯是同时进行,所以要放在同一个花括号里
if (root.left != null) { // 左
traversal(root.left, paths, res);
paths.remove(paths.size() - 1);// 回溯
}
if (root.right != null) { // 右
traversal(root.right, paths, res);
paths.remove(paths.size() - 1);// 回溯
}
}
}
再来看看隐式 回溯,隐式回溯
第一种靠形参位置传参是做加一,在代码中没有进行参数操作,这样回溯回去的参数自然是上一个的。
第二种靠String这样唯一不可变的参数,保证每次回溯回去是是上一层的参数。看一下下边的代码找找感觉。
class Solution {
private int Deep = -1;
private int value = 0;
public int findBottomLeftValue(TreeNode root) {
value = root.val;
findLeftValue(root,0);
return value;
}
private void findLeftValue (TreeNode root,int deep) {
if (root == null) return;
if (root.left == null && root.right == null) {
if (deep > Deep) {
value = root.val;
Deep = deep;
}
}
if (root.left != null) findLeftValue(root.left,deep + 1);
if (root.right != null) findLeftValue(root.right,deep + 1);
}
}
class Solution {
List<String> result = new ArrayList<>();
public List<String> binaryTreePaths(TreeNode root) {
deal(root, "");
return result;
}
public void deal(TreeNode node, String s) {
if (node == null)
return;
if (node.left == null && node.right == null) {
result.add(new StringBuilder(s).append(node.val).toString());
return;
}
String tmp = new StringBuilder(s).append(node.val).append("->").toString();
deal(node.left, tmp);
deal(node.right, tmp);
}
}
public List<String> binaryTreePaths(TreeNode root) {
List<String> res = new ArrayList<>();
dfs(root, "", res);
return res;
}
private void dfs(TreeNode root, String path, List<String> res) {
//如果为空,直接返回
if (root == null)
return;
//如果是叶子节点,说明找到了一条路径,把它加入到res中
if (root.left == null && root.right == null) {
res.add(path + root.val);
return;
}
//如果不是叶子节点,在分别遍历他的左右子节点
dfs(root.left, path + root.val + "->", res);
dfs(root.right, path + root.val + "->", res);
}
class Solution {
public List<String> binaryTreePaths(TreeNode root) {
List<String> res = new ArrayList<>();
backTrack(root, new StringBuilder(), res);
return res;
}
void backTrack(TreeNode root, StringBuilder path, List<String> res) {
if (root == null) return;
// 记录进入这一层递归时的路径长度
int len = path.length();
path.append(root.val);
if (root.left == null && root.right == null) {
// 叶子节点,添加路径到结果集
res.add(path.toString());
} else {
// 非叶子节点,继续递归
path.append("->");
backTrack(root.left, path, res);
backTrack(root.right, path, res);
}
// 回溯,撤销到这一层递归开始时的状态
path.setLength(len);
}
112. 路径总和
此题引发了回溯问题的大思考,卡哥讲的回溯或者我理解的可能陷入了误区。
其实只有维护全局变量,引用变量如列表,链表时才需要显式回溯。其他时候都不需要显式回溯。
class Solution {
private boolean traversal(TreeNode cur, int count) {
// 检查当前节点是否为 null
if (cur == null) return false;
// 减去当前节点值
count -= cur.val;
// 检查是否为叶子节点
if (cur.left == null && cur.right == null) {
return count == 0; // 如果计数为0,返回true
}
// 递归处理左子树
if (traversal(cur.left, count)) return true;
// 递归处理右子树
if (traversal(cur.right, count)) return true;
return false; // 没有找到路径
}
public boolean hasPathSum(TreeNode root, int sum) {
if (root == null) return false;
return traversal(root, sum); // 传递目标和
}
}
隐式回溯的概念
-
递归调用的上下文:
- 每次调用
hasPathSum
时,函数都有自己的局部变量,包括targetSum
和root
。这些局部变量在每次递归中是独立的,不会互相影响。 - 当一个递归调用完成并返回到上层调用时,之前的状态(如
targetSum
的值)自然恢复到上一个调用的状态。
- 每次调用
-
状态管理:
- 在每次递归中,
targetSum
是通过参数传递的,你在每次调用时减去当前节点的值。这不会影响其他调用,因为每个调用都有自己的targetSum
值。 - 如果你在某个路径找到了符合条件的结果,函数会返回
true
,而上层的调用会根据这个结果决定是否继续执行。
- 在每次递归中,
class Solution {
int maxnum = 0;
public int maxDepth(TreeNode root) {
ans(root, 0);
return maxnum;
}
void ans(TreeNode tr, int tmp) {
if (tr == null) return;
tmp++; // 进入节点时增加深度
maxnum = Math.max(maxnum, tmp); // 更新最大深度
ans(tr.left, tmp); // 递归左子树
ans(tr.right, tmp); // 递归右子树
// tmp--; // 这个操作可以去掉
}
}
tmp
是独立的局部变量,每次递归调用都有自己的副本。tmp--
是不必要的,因为每个调用结束后,tmp
的值会自动恢复到上层调用的状态。- 直接通过递归调用和局部变量的独立性来管理状态,可以使代码更简洁。
106. 从中序与后序遍历序列构造二叉树
class Solution {
HashMap<Integer,Integer> Indexmap = new HashMap<>();
int[] post;
public TreeNode buildTree(int[] inorder, int[] postorder) {
for(int i = 0; i < inorder.length; i++){
Indexmap.put(inorder[i],i);
}
post = postorder;
TreeNode root = BuildTree(0, inorder.length - 1, 0, post.length -1);
return root;
}
private TreeNode BuildTree(int iStart, int iEnd, int pStart, int pEnd){
if(iStart > iEnd || pStart > pEnd) return null;
int val = post[pEnd];
int Index = Indexmap.get(val);
TreeNode node = new TreeNode(val);
node.left = BuildTree(iStart, Index - 1, pStart, Index - iStart + pStart - 1);
node.right = BuildTree(Index + 1, iEnd, Index - iStart + pStart, pEnd - 1);
return node;
}
}
根据中序遍历和后续遍历的特性我们进行树的还原过程分析
首先在后序遍历序列中找到根节点(最后一个元素)
根据根节点在中序遍历序列中找到根节点的位置
根据根节点的位置将中序遍历序列分为左子树和右子树
根据根节点的位置确定左子树和右子树在中序数组和后续数组中的左右边界位置
递归构造左子树和右子树
返回根节点结束
作者:房建斌学算法