给定一个二叉树的 根节点 root
,请找出该二叉树的 最底层 最左边 节点的值。
假设二叉树中至少有一个节点。
递归:
class Solution {
int curVal = 0;
int curHeight = 0;
public int findBottomLeftValue(TreeNode root) {
dfs(root, 0);
return curVal;
}
public void dfs(TreeNode root, int height) {
if (root == null) {
return;
}
height++;
dfs(root.left, height);
dfs(root.right, height);
if (root.left == null && root.right == null) {
if (height > curHeight) {
curHeight = height;
curVal = root.val;
}
}
}
}
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);//deep+1也是fn和f(n+1)的关系之一
if (root.right != null) findLeftValue(root.right,deep + 1);
}
}
1.第一个是后序遍历第二个是前序遍历。
2.第一个根节点高度是1,而第二个是0.
3.引入 height
(或在第二个代码中称为 deep
)变量的目的是跟踪当前节点在树中的深度或层级。这对于确定哪些节点位于最底层是必要的,因为我们的目标是找到最底层的最左侧节点的值。
以下是使用 height
或 deep
变量的主要原因:
-
区分节点的深度:在递归遍历树时,我们需要知道每个节点所处的深度。这样,当我们遇到一个叶子节点时,我们可以检查它是否位于比我们之前见过的任何叶子节点更深的位置。
-
更新最左侧值的条件:只有当当前叶子节点的深度超过之前记录的最大深度时,我们才更新存储的最左侧值。为此,我们需要有一个变量来跟踪当前节点的深度,并将其与已知的最大深度进行比较。
-
递归时的深度跟踪:在递归调用的过程中,我们需要为子节点更新深度值。例如,如果一个节点的深度是
d
,那么它的子节点的深度就是d+1
。
如果我们不使用 height
或 deep
这样的变量,我们就无法确定每个节点的深度,从而无法确定哪个叶子节点位于最底层。因此,这个变量在解决这个问题时起着关键作用。
迭代:
class Solution {
public int findBottomLeftValue(TreeNode root) {
int ret = 0;
Queue<TreeNode> queue = new ArrayDeque<TreeNode>();
queue.offer(root);
while (!queue.isEmpty()) {
TreeNode p = queue.poll();
if (p.right != null) {
queue.offer(p.right);
}
if (p.left != null) {
queue.offer(p.left);
}
ret = p.val;
}
return ret;
}
}
迭代就可以看出来,层序遍历也不是非要int n = que.size();不可,和深度遍历的更大区别就在于一个是栈一个是队列。需要int和内层循环的必然在内层循环进行了一些操作。
给你二叉树的根节点 root
和一个表示目标和的整数 targetSum
。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum
。如果存在,返回 true
;否则,返回 false
。
叶子节点 是指没有子节点的节点。
有一句话慢慢体会
if (cur->left) { // 左 (空节点不遍历)
// 遇到叶子节点返回true,则直接返回true
if (traversal(cur->left, count - cur->left->val)) return true; // 注意这里有回溯的逻辑
}
if (cur->right) { // 右 (空节点不遍历)
// 遇到叶子节点返回true,则直接返回true
if (traversal(cur->right, count - cur->right->val)) return true; // 注意这里有回溯的逻辑
}
return false;
if (cur->left) { // 左
count -= cur->left->val; // 递归,处理节点;
if (traversal(cur->left, count)) return true;
count += cur->left->val; // 回溯,撤销处理结果
}
if (cur->right) { // 右
count -= cur->right->val;
if (traversal(cur->right, count)) return true;
count += cur->right->val;
}
return false;
以上代码中是包含着回溯的,没有回溯,如何后撤重新找另一条路径呢。
回溯隐藏在traversal(cur->left, count - cur->left->val)
这里, 因为把count - cur->left->val
直接作为参数传进去,函数结束,count的数值没有改变。
class solution {
public boolean haspathsum(treenode root, int targetsum) {
if (root == null) return false; // 为空退出
// 叶子节点判断是否符合
if (root.left == null && root.right == null) return root.val == targetsum;
// 求两侧分支的路径和
return haspathsum(root.left, targetsum - root.val) || haspathsum(root.right, targetsum - root.val);
}
}
class solution {
public boolean haspathsum(treenode root, int targetsum) {
if (root == null) {
return false;
}
targetsum -= root.val;
// 叶子结点
if (root.left == null && root.right == null) {
return targetsum == 0;
}
if (root.left != null) {
boolean left = haspathsum(root.left, targetsum);
if (left) { // 已经找到
return true;
}
}
if (root.right != null) {
boolean right = haspathsum(root.right, targetsum);
if (right) { // 已经找到
return true;
}
}
return false;
}
}
第一个代码其实就是把第二个代码后面的部分合并了一下。
使用逻辑“或”操作符 ||
是因为我们只需要左子树或右子树中的任何一个满足条件就可以。如果左子树返回 true
,那么整个表达式立即返回 true
,不再检查右子树(这是 ||
操作符的短路特性)。如果左子树返回 false
,那么右子树的结果决定整个表达式的返回值。
这样,递归地检查每个节点,直到找到满足条件的路径或遍历完所有的节点为止。
class Solution {
public boolean hasPathSum(TreeNode root, int targetSum) {
if (root == null) {
return false;
}
Deque<TreeNode> stack = new ArrayDeque<>();
Deque<Integer> stack1 = new ArrayDeque<>();
stack.push(root);
stack1.push(root.val);
while (!stack.isEmpty()) {
TreeNode pop = stack.pop();
int sum = stack1.pop();
if (pop.left == null && pop.right == null && sum == targetSum) {
return true;
}
if (pop.left != null) {
stack.push(pop.left);
stack1.push(sum+pop.left.val);
}
if(pop.right!=null){
stack.push(pop.right);
stack1.push(sum+pop.right.val);
}
}
return false;
}
}
给你二叉树的根节点 root
和一个整数目标和 targetSum
,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。
叶子节点 是指没有子节点的节点。
class Solution {
List<List<Integer>> ret = new LinkedList<List<Integer>>();
Deque<Integer> path = new LinkedList<Integer>();
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
dfs(root, targetSum);
return ret;
}
public void dfs(TreeNode root, int targetSum) {
if (root == null) {
return;
}
path.offerLast(root.val);
targetSum -= root.val;
if (root.left == null && root.right == null && targetSum == 0) {
ret.add(new LinkedList<Integer>(path));
}
dfs(root.left, targetSum);
dfs(root.right, targetSum);
path.pollLast();
}
}
class Solution {
List<List<Integer>> ret = new LinkedList<List<Integer>>();
Map<TreeNode, TreeNode> map = new HashMap<TreeNode, TreeNode>();
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
if (root == null) {
return ret;
}
Queue<TreeNode> queueNode = new LinkedList<TreeNode>();
Queue<Integer> queueSum = new LinkedList<Integer>();
queueNode.offer(root);
queueSum.offer(0);
while (!queueNode.isEmpty()) {
TreeNode node = queueNode.poll();
int rec = queueSum.poll() + node.val;
if (node.left == null && node.right == null) {
if (rec == targetSum) {
getPath(node);
}
} else {
if (node.left != null) {
map.put(node.left, node);
queueNode.offer(node.left);
queueSum.offer(rec);
}
if (node.right != null) {
map.put(node.right, node);
queueNode.offer(node.right);
queueSum.offer(rec);
}
}
}
return ret;
}
public void getPath(TreeNode node) {
List<Integer> temp = new LinkedList<Integer>();
while (node != null) {
temp.add(node.val);
node = map.get(node);
}
Collections.reverse(temp);
ret.add(new LinkedList<Integer>(temp));
}
}
class Solution {
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
List<List<Integer>> result = new ArrayList<>();
Stack<TreeNode> nodeStack = new Stack<>();
Stack<Integer> sumStack = new Stack<>();
Stack<ArrayList<Integer>> pathStack = new Stack<>();
if(root == null)
return result;
nodeStack.add(root);
sumStack.add(root.val);
pathStack.add(new ArrayList<>());
while(!nodeStack.isEmpty()){
TreeNode currNode = nodeStack.peek();
int currSum = sumStack.pop();
ArrayList<Integer> currPath = pathStack.pop();
if(currNode != null){
nodeStack.pop();
nodeStack.add(currNode);
nodeStack.add(null);
sumStack.add(currSum);
currPath.add(currNode.val);
pathStack.add(new ArrayList(currPath));
if(currNode.right != null){
nodeStack.add(currNode.right);
sumStack.add(currSum + currNode.right.val);
pathStack.add(new ArrayList(currPath));
}
if(currNode.left != null){
nodeStack.add(currNode.left);
sumStack.add(currSum + currNode.left.val);
pathStack.add(new ArrayList(currPath));
}
}else{
nodeStack.pop();
TreeNode temp = nodeStack.pop();
if(temp.left == null && temp.right == null && currSum == targetSum)
result.add(new ArrayList(currPath));
}
}
return result;
}
}
给定两个整数数组 preorder
和 inorder
,其中 preorder
是二叉树的先序遍历, inorder
是同一棵树的中序遍历,请构造二叉树并返回其根节点。
public TreeNode buildTree(int[] preOrder, int[] inOrder) {
if (preOrder.length == 0) {
return null;
}
// 获取根节点值
int rootValue = preOrder[0];
TreeNode root = new TreeNode(rootValue);
// 区分左右子树
for (int i = 0; i < inOrder.length; i++) {
if (inOrder[i] == rootValue) {
// 0 ~ i-1 左子树
// i+1 ~ inOrder.length -1 右子树
int[] inLeft = Arrays.copyOfRange(inOrder, 0, i); // [4,2]
int[] inRight = Arrays.copyOfRange(inOrder, i + 1, inOrder.length); // []
int[] preLeft = Arrays.copyOfRange(preOrder, 1, i + 1); // [2,4]
int[] preRight = Arrays.copyOfRange(preOrder, i + 1, inOrder.length);
root.left = buildTree(preLeft, inLeft); // 2
root.right = buildTree(preRight, inRight); // 3
break;
}
}
return root;
}
class Solution {
Map<Integer, Integer> map;
public TreeNode buildTree(int[] preorder, int[] inorder) {
map = new HashMap<>();
for (int i = 0; i < inorder.length; i++) { // 用map保存中序序列的数值对应位置
map.put(inorder[i], i);
}
return findNode(preorder, 0, preorder.length, inorder, 0, inorder.length); // 前闭后开
}
public TreeNode findNode(int[] preorder, int preBegin, int preEnd, int[] inorder, int inBegin, int inEnd) {
// 参数里的范围都是前闭后开
if (preBegin >= preEnd || inBegin >= inEnd) { // 不满足左闭右开,说明没有元素,返回空树
return null;
}
int rootIndex = map.get(preorder[preBegin]); // 找到前序遍历的第一个元素在中序遍历中的位置
TreeNode root = new TreeNode(inorder[rootIndex]); // 构造结点
int lenOfLeft = rootIndex - inBegin; // 保存中序左子树个数,用来确定前序数列的个数(这个很容易想,因为两个坐标相减就是包含两个数的个数减1,因为减去前一个坐标是包含两个数总共有几个数)
root.left = findNode(preorder, preBegin + 1, preBegin + lenOfLeft + 1,
inorder, inBegin, rootIndex);
root.right = findNode(preorder, preBegin + lenOfLeft + 1, preEnd,
inorder, rootIndex + 1, inEnd);
return root;
}
}
106
class Solution {
Map<Integer, Integer> map = new HashMap<>();
public TreeNode buildTree(int[] inorder, int[] postorder) {
for (int i = 0; i < inorder.length; i++) {
map.put(inorder[i], i);
}
return findNode(postorder, 0, postorder.length, inorder, 0, inorder.length);
}
public TreeNode findNode(int[] postOrder, int postBegin, int postEnd, int[] inorder, int inBegin, int inEnd) {
if (inBegin >= inEnd || postBegin >= postEnd) { // 不满足左闭右开,说明没有元素,返回空树
return null;
}
int rootIndex = map.get(postOrder[postEnd - 1]);
TreeNode root = new TreeNode(inorder[rootIndex]);
int lenOfLeft = rootIndex - inBegin;
root.left = findNode(postOrder, postBegin, postBegin + lenOfLeft, inorder, inBegin, rootIndex);
root.right = findNode(postOrder, postBegin + lenOfLeft, postEnd - 1, inorder, rootIndex + 1, inEnd);
return root;
}
}
变量能不能被别的方法用和它所在的方法是不是public定义的无关对吗
ChatGPT
是的,你说得对。一个变量是否能被其他方法使用与它所在的方法是否被声明为 public
是没有关系的。让我们更详细地分析这个问题:
-
局部变量:局部变量是在方法、构造器或块中声明的变量。它们的作用域只限于声明它们的代码块。一旦超出这个代码块,这个变量就会失效,不能被其他方法访问。局部变量的生命周期仅限于方法的执行时间。方法是否是
public
对此没有影响。 -
成员变量:成员变量(也称为实例变量)是在类的内部但在方法、构造器或任何块之外声明的。它们的访问权限由访问修饰符(如
public
、private
、protected
或默认)决定。其他方法可以访问成员变量,只要这些方法的作用域允许它们这么做。
例如,如果一个成员变量被声明为 private
,那么只有同一类中的方法可以访问它。如果它被声明为 public
,任何其他类都可以访问它,前提是它们有这个类的实例。
总结:方法是否是 public
只决定了其他类是否可以调用这个方法。它与方法内部的局部变量的可见性或类的成员变量的可见性无关。