leecodecode【层序遍历】【2026.6.3打卡-java版本】
2026/6/4 1:03:15 网站建设 项目流程

二叉树的层序遍历

要点:队列

/** * 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 List<List<Integer>> levelOrder(TreeNode root) { //队列 if(root == null){ return new ArrayList<>(); } Deque<TreeNode> deque = new ArrayDeque<>(); List<List<Integer>> anss = new ArrayList<>(); deque.offer(root); while(!deque.isEmpty()){ int size = deque.size(); List<Integer> ans = new ArrayList<>(); for(int i = 0; i < size; i++){ TreeNode temp = deque.poll(); ans.add(temp.val); if(temp.left != null){ deque.offer(temp.left); } if(temp.right != null){ deque.offer(temp.right); } } anss.add(ans); } return anss; } }

二叉树的锯齿形层序遍历

要点:anss的个数,偶数则反转collections.reverse

/** * 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 List<List<Integer>> zigzagLevelOrder(TreeNode root) { if(root == null){ return new ArrayList<>(); } Deque<TreeNode> deque = new ArrayDeque<>(); List<List<Integer>> anss = new ArrayList<>(); //boolean isOuShu = false; deque.offer(root); while(!deque.isEmpty()){ int size = deque.size(); List<Integer> ans = new ArrayList<>(); for(int i = 0; i < size; i++){ TreeNode temp = deque.poll(); ans.add(temp.val); if(temp.left != null){ deque.offer(temp.left); } if(temp.right != null){ deque.offer(temp.right); } } if(anss.size() % 2 > 0){ Collections.reverse(ans); } anss.add(ans); } return anss; } }

找树左下角的值

要点:先right再left,最后就是了

/** * 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 int findBottomLeftValue(TreeNode root) { //队列 Deque<TreeNode> deque = new ArrayDeque<>(); deque.offer(root); TreeNode temp =root; while(!deque.isEmpty()){ temp = deque.poll(); if(temp.right != null){ deque.offer(temp.right); } if(temp.left != null){ deque.offer(temp.left); } } return temp.val; } }

随机知识【redis2】

二、缓存三剑客(必问,最高频)

什么是缓存穿透、缓存击穿、缓存雪崩?怎么解决?

面试官为什么这么问?
这几乎是 Redis 面试的必考题,问烂了仍然要问,因为它直接对应线上故障。我要听你非常清晰地用一句话定义每个问题,然后给出成熟方案,不要含糊。

希望听到怎样的回答:

  • 缓存穿透:查询一个根本不存在的数据,缓存和 DB 都没有,大量请求直接打 DB。
    • 解决:①布隆过滤器,先判断 key 是否存在;② 对查询结果为空的情况也缓存,设置短的过期时间(空值缓存)。
  • 缓存击穿:一个热点 key 过期的瞬间,大量请求同时查 DB。
    • 解决:①互斥锁:第一个请求加个锁去查 DB 并写回缓存,其他请求等待;②逻辑过期:value 里带过期时间,发现过期就异步重建缓存,旧值仍可使用。
  • 缓存雪崩大量 key 在同一时间过期,或 Redis 宕机,请求全部打向 DB。
    • 解决:① 过期时间加随机值,打散过期时间;② Redis 集群保证高可用;③ 本地缓存或限流做兜底。
  • 结合项目:“我们的题库缓存会设置随机过期时间,避免同一时刻大量题目缓存同时失效。”

候选人
好的,这三个问题是 Redis 缓存使用中最经典的三个风险场景,它们都是描述缓存失效导致大量请求直接打到数据库的情况,但诱因不同,解决方案也不同。我分别说明。

第一,缓存穿透。

缓存穿透是指查询一个数据库中根本不存在的数据。因为缓存里也不会有这条数据,所以每次请求都会穿过缓存直接打到数据库上。如果有人恶意用不存在的 ID 大量请求,数据库压力会瞬间飙升。

解决方案主要有两种:

一是布隆过滤器。它是一个概率型数据结构,特点是:它说某个 key 不存在,那就一定不存在;它说存在,可能实际存在也可能不存在(有一定误判率)。我们把所有可能存在的 key 都预先加载到布隆过滤器里,查询前先问布隆过滤器,如果返回不存在,直接拦截返回空,不用查缓存更不用查数据库。

二是缓存空值。如果查数据库发现数据确实不存在,也把这个空结果缓存起来,设置一个短一点的过期时间(比如 5 分钟)。下次同样的请求来了,直接从缓存拿到 null,不会再穿透到数据库。但要防止大量不存在的 key 撑爆 Redis 内存,所以需要设置较短的 TTL。

我项目里对高频查询的接口都加了空值缓存,同时记录了异常查询频率,如果短时间出现大量不存在的 ID 请求,会触发告警人工排查是否被恶意扫描。

第二,缓存击穿。

缓存击穿是指一个热点 key 在过期的瞬间,大量并发请求同时打到了数据库。热点 key 比如秒杀商品的库存、爆款文章的详情,访问量极高,过期时一起重建缓存会把数据库压垮。

解决方案也有两种思路:

一是互斥锁。当缓存失效后,不是所有线程都去查数据库重建缓存,而是让它们竞争一把分布式锁。拿到锁的线程去查数据库并写回缓存,其他线程等待(或自旋重试),缓存写好后它们直接从缓存拿。这个方案简单可靠,缺点是重建期间请求会阻塞。

二是逻辑过期。把热点 key 设置成永不过期,但在 value 里附加一个逻辑过期时间字段。每次读取时都检查这个字段是否过期:如果没有过期,直接返回缓存数据;如果逻辑时间已过期,把旧数据先返回给客户端(不等待),然后开异步线程去获取互斥锁重建缓存。这避免了请求阻塞,用户感知不到延迟,但可能在缓存重建完成前的短暂窗口内读到旧数据。

项目里的面试题库缓存就用互斥锁防击穿——热门题目缓存过期时,只有一个线程去查数据库重建,其余线程等待几十毫秒后拿到新缓存。

第三,缓存雪崩。

缓存雪崩是指大量 key 在同一时间过期,或者 Redis 服务直接宕机,导致海量请求直接打到数据库,可能瞬间拖垮整个系统。

解决从三个层面下手:

一,过期时间打散。给不同 key 的过期时间加上一个随机值(比如 1 到 3 分钟),避免集体同时失效。这是最简单有效的手段。

二,Redis 高可用。采用主从加哨兵或者 Redis Cluster 集群部署,单个节点故障时能自动切换,减少 Redis 不可用的时间窗口。

三,多级缓存和限流兜底。在应用层加一层本地缓存(比如 Caffeine),Redis 挂了还能用本地缓存扛一会;同时在网关或服务层加限流,保证数据库不会被瞬间流量打死。

项目里我们的缓存 key 过期时间都加了随机偏移量;同时 Redis 采用主从 + 哨兵架构保证高可用。对于核心接口,还有本地缓存的兜底方案,万一 Redis 全部宕机,本地缓存和限流可以保证数据库不被打崩。


一句话总结:穿透是查“不存在”的数据,用布隆过滤器和空值缓存防;击穿是热点 key 失效瞬间的并发冲击,用互斥锁或逻辑过期防;雪崩是大面积 key 同时失效或 Redis 宕机,用过期时间打散、高可用集群、多级缓存和限流来防。三种问题的本质都是在缓存层和数据库层之间建立缓冲机制,保护后端的稳定性。

碎碎念:后续会更新每天学习的八股和算法 题,开始准备秋招的第24天。努力连续更新100天!以后每天就按,秋招项目【java+agent】,科研,必做项目,算法,八股,锻炼身体来总结。

总结:科研要快搞,不要三心二意,实在不行就听纯音乐

1.算法要系统过一遍【灵神】13/27【早上】大概1h

2.秋招项目,【java】开始实际看业务,2.9/10;无

【agent】还在学,helloagent总结;2h,还是差太多了,决定后面看learncc。

3.科研要跑一下,0.5h,新建文件夹

4.检测项目也得总结文档,3h

5.训练项目看看先选择好,1h

6.背八股,

7.锻炼身体,无

反思:今天干杂活3h,趁现在没事情的话,赶快学习,

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询