12. 路径总和
给你二叉树的根节点root和一个表示目标和的整数targetSum。判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和targetSum。如果存在,返回true;否则,返回false。
叶子节点是指没有子节点的节点。
/** * 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 { public boolean hasPathSum(TreeNode root, int targetSum) { return dfs(root,targetSum); } public boolean dfs(TreeNode root,int targetSum){ if(root == null){ return false; } //是从根节点到子节点,要判断等于0的点是不是子节点 if(root.val == targetSum && root.left == null && root.right ==null){ return true; } boolean left = dfs(root.left,targetSum - root.val); boolean right = dfs(root.right, targetSum - root.val); return left || right; } }轮流打印abc
package hot100; public class ThreadPrintingABC { public static class PrintABC{ private final int max; private int state = 0; PrintABC(int max){ this.max = max; } public void Print(String letter, int num){ for(int i = 0; i < max; i++){ synchronized (this){ while(state%3 != num){ try{ this.wait(); }catch(InterruptedException e){ e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + "->" + num); state++; notifyAll();; } } } } public static void main(String[] args) { PrintABC printABC = new PrintABC(5); Thread a = new Thread(() -> printABC.Print("A",0),"Thread-A"); Thread b = new Thread(() -> printABC.Print("B",1),"Thread-B"); Thread c = new Thread(() -> printABC.Print("C",2),"Thread-c"); a.start(); b.start(); c.start(); } }基于我们整个对话,我把所有核心知识点为你浓缩成一套“总结笔记”。看完这篇,面试问到这个题你就能对答如流了。
1. 核心结论:你的代码完全正确
你写的synchronized + wait/notifyAll方案是标准且满分的面试答案。唯一要改的是输出语句,把num换成letter,否则打印的是数字 0/1/2 而不是 A/B/C。
2. 终于弄懂的对象与内存(房子与遥控器)
对象 = 堆内存里的实体(房子):执行
new PrintABC(5)就是在堆里盖了一栋房子,里面放着max=5和state=0。引用 = 栈里的遥控器(变量):
printABC存储在栈中,指向堆里的房子。三个线程共享的前提:A、B、C 三个线程的 Lambda 表达式,捕获的都是同一个遥控器
printABC,所以它们操作的是同一栋房子。static的真相:你问的
public static class PrintABC叫静态嵌套类,意思是不持有外部类ThreadPrintingABC的遥控器(独立图纸),这跟“类变量”完全是两码事。state不需要加static。因为是三个线程操作同一个对象,所以state天然共享。如果加了static,虽然也能跑,但不利于以后创建多个独立打印机(会互相干扰)。
3. 线程到底是怎么按顺序跑的?(锁与等待池)
唯一锁对象:三个线程争抢的锁,是堆里那栋
printABC房子自带的一把“大门锁”(Monitor,存储在对象头里)。运行流程:
A 线程抢到锁,发现
state%3 == 0,打印 A,state++变成 1,notifyAll()唤醒大家,退出同步块。B 抢到锁,发现
state%3 == 1,打印 B,state++变 2,唤醒大家。C 抢到锁,发现
state%3 == 2,打印 C,state++变 3(即 0),唤醒大家。下一轮,A 再次抢到锁(或者 B 抢到但检查不通过,乖乖
wait让出),如此循环。
wait()的妙用:调用wait()时,线程会释放锁并进入“等待室”(WaitSet),把锁让给下一个线程;被唤醒后,它必须先重新抢到锁,才能从wait()处继续往下执行。
4. 两个写法的区别(面试官可能会追问)
| 你的写法(synchronized + notifyAll) | 我给的写法(ReentrantLock + Condition) |
|---|---|
| 唤醒所有线程(惊群效应),不满足条件的再次阻塞等待,CPU 稍有消耗但代码更简单。 | 精准唤醒下一个线程(如只唤 B),性能更高,但代码稍复杂。 |
面试回答技巧:如果面试官问缺点,你就说“如果用notifyAll,线程会全部醒来抢锁,造成不必要的上下文切换,但用while检查条件可以确保安全,在简单场景下完全够用。” | 如果面试官问优化,你就说“可以用Lock+Condition精准唤醒,避免惊群。” |
5. 终极避坑口诀(背下来,面试稳了)
用
while不用if:防止虚假唤醒,醒来后必须再检查一遍是否轮到我了。wait()必须释放锁:否则别的线程进不来,程序就死锁了。共享的根源是“同一个对象”,不是
static关键字。
sql
select name from Customer where referee_id IS NULL or referee_id != 2;碎碎念:后续会更新每天学习的八股和算法题,开始准备秋招的第81天。努力连续更新100天!
总结:加油吧