LeetCodehot100题解

利用前序和中序数组构造二叉树

思路: 构造一颗二叉树一定要用递归+hash查找

  1. 找根 :从 preorder 中按prel拿出一个节点作为根。

  2. 切分 :拿着这个根的值,去 inorder 中找到它的位置 index。

    • 计算左子树的节点个数 = index - in_left(中序左边界)。
  3. 递归 :根据上面算出的节点个数,把 preorder 和 inorder 切分成左右两部分,分别递归构造左子树和右子树。

  4. 优化(关键) :因为每次都要在 inorder 中找根的位置,如果每次都用 for 循环找,时间复杂度会退化到 O(n2)O(n2)。我们可以用 HashMap 提前把 inorder 里"值 -> 下标"的映射存起来,这样找根的时间就变成了 O(1)O(1)。

/**

* 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;

* }

* }

*/

复制代码
       @param preorder 先序数组 
  • @param preL 当前子树在先序数组中的左边界
  • @param preR 当前子树在先序数组中的右边界
  • @param inL 当前子树在中序数组中的左边界
  • @param inR 当前子树在中序数组中的右边界

class Solution {

private Map<Integer,Integer> indexmap=new HashMap<>();

private TreeNode build(int\[\] preorder,int prel,int prer,int inl,int inr){

if(prer<prel||inl>inr)return null;

int rootval=preorderprel;

TreeNode root=new TreeNode(rootval);

int indexin=indexmap.get(rootval);

int leftsize=indexin-inl;

root.left=build(preorder,prel+1,prel+leftsize,inl,indexin-1);

root.right=build(preorder,prel+leftsize+1,prer,indexin+1,inr);

return root;

}

public TreeNode buildTree(int\[\] preorder, int\[\] inorder) {

int n=preorder.length;

for(int i=0;i<n;i++){

indexmap.put(inorderi,i);

}

return build(preorder,0,n-1,0,n-1);

}

}

树上求路径和为指定数的路径条数

思路:树上前缀+Hsah

  1. 前缀和定义 :从根节点走到当前节点的路径和记为 curSum。那么,如果存在某个祖先节点,从根走到那个祖先节点的路径和是 oldSum。

  2. 魔法公式 :如果 curSum - oldSum = targetSum,这就意味着从那个祖先节点到当前节点的这段路径和,刚好等于 targetSum。

    • 变形一下:oldSum = curSum - targetSum。
  3. 哈希表的作用 :我们在向下遍历的过程中,用一个 HashMap 记录当前路径上(从根到当前节点)所有前缀和出现的次数。

  4. 回溯(恢复现场) :当我们处理完一个节点的左右子树,准备返回它的父节点时,必须把当前节点的 curSum 从哈希表里减掉。因为当前节点已经不在我们要探索的下一条路径上了,不能影响其他分支的计算。

细节:

1.为避免树是单链表形式且结点值很大时导致前缀和溢出出现负数,map里的key前缀和要使用long类型

2.初始化:前缀和要先初始化map{0L,1};否则会出现漏掉

3.cursum是当前这条路径从根节点到当前节点的前缀和,当改变一条路径时,必须回溯

/**

* 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 {

private int dfs(TreeNode node,long cursum,int targetSum,Map<Long,Integer> premap){

if(node==null)return 0;

cursum+=node.val;

int count=premap.getOrDefault(cursum-targetSum,0);

premap.put(cursum,premap.getOrDefault(cursum,0)+1);

count+=dfs(node.left,cursum,targetSum,premap);

count+=dfs(node.right,cursum,targetSum,premap);

premap.put(cursum,premap.get(cursum)-1);

return count;

}

public int pathSum(TreeNode root, int targetSum) {

Map<Long,Integer> premap=new HashMap<>();

premap.put(0L,1);

return dfs(root,0L,targetSum,premap);

}

}

1.bean是单例吗?安全吗?

Spring Bean 默认是单例的,但容器本身不管线程安全。实际项目里我们的 Service 和 Dao 都是无状态(bean内部无可变的成员变量就叫做无状态)的,只调方法不存数据,所以是安全的。如果确实需要保存状态,我会用 ThreadLocal

2.string、stringBuilder、StringBuffer区别

三者核心区别在可变性 和线程安全。

String StringBuilder StringBuffer
可变性 不可变(final char\[\]) 可变 可变
线程安全 安全(因为不可变) 不安全 安全(方法加 synchronized)
性能 拼接产生新对象,慢 最快 比 StringBuilder 慢
适用场景 少量字符串、常量 单线程大量拼接 多线程大量拼接
  • String 每次拼接(+、concat)都会创建新对象,循环里用 + 是性能杀手。

  • StringBuilder 是 JDK 1.5 新增的,去掉了 StringBuffer 的锁开销,是单线程拼接的首选。

话术:"日常拼接我用 StringBuilder,单线程没有锁开销。StringBuffer 基本不用了,只有明确需要在多线程间共享可变字符串时才考虑。

3.redis淘汰策略

Redis 提供 8 种淘汰策略

  • noeviction:不淘汰,内存满时写入报错(默认)。

  • allkeys-lru:从所有 key 中淘汰最近最少使用的。

  • allkeys-lfu:从所有 key 中淘汰最不经常使用的。

  • allkeys-random:从所有 key 中随机淘汰。

  • volatile-lru / volatile-lfu / volatile-random :只在设置了过期时间的 key 中,按 LRU/LFU/随机淘汰。

  • volatile-ttl:在设置了过期时间的 key 中,淘汰剩余存活时间最短的。

5.GC是啥,内存溢出和泄露是啥

GC(垃圾回收) :JVM 自动回收不再被引用的对象的机制。核心思想是可达性分析 ------从 GC Roots 出发,不可达的对象就是垃圾。JVM 分代回收:新生代用复制算法(对象朝生夕死,回收快),老年代用标记-整理(存活率高,回收慢)。

内存泄漏 vs 内存溢出:

  • 内存泄漏 :对象不再使用,但仍被引用 ,GC 无法回收。内存逐渐被"僵尸对象"蚕食,最终导致溢出。

  • 内存溢出(OOM) :JVM 堆内存真的不够用了,无法为新对象分配空间。泄漏是原因,溢出是结果。

6.Git reset、rebase、revert区别

简单来说,如果是我自己还没推的本地代码搞乱了,我用 reset 直接抹掉重来;如果是想把我的本地提交漂亮地接到主干后面,我用 rebase 变基;如果是线上的公共代码出问题了需要撤回,绝对只能用 revert,因为改历史(reset/rebase)只能动自己,动别人必须用 revert

rebase = Re(重新) + Base(基底/地基)。

想象你开发功能 A,是从主干 master 的节点 C1 开始建的。

但是在你开发期间,master 已经往前跑了,更新到了节点 C4。

此时,你的分支是基于旧的 C1 地基建的。

  • git merge(合并) :相当于在你的分支和 master 之间架一座桥,保留你的旧地基。结果是历史记录里有分叉,有个 Merge commit。

    复制代码
    C2 --- C3 (master)

    /

    C1 Merge (feature)

    \ /

    F1 --- F2

  • git rebase(变基) :相当于把你之前写的代码(提交)先"拔"出来,然后把你的新地基 换成 master 最新的 C4,最后把你拔出来的代码重新"插"到 C4 后面。

C1 --- C2 --- C3 --- F1' --- F2' (feature)

(master)

一句话理解 :把当前分支的"基底",变成另一个分支的最新状态。

子集

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

示例 1:

**输入:**nums = 1,2,3

输出:\[,1,2,1,2,3,1,3,2,3,1,2,3]

思路:递归深搜+回溯 脑子里面有一颗树,每一层是从数组中挑选数字

细节:i + 1 而不是start+1的起点控制,避免出现重复、深拷贝收集结果

class Solution {

private void backtrace(int\[\] nums,int start,List path,List<List> res){

res.add(new ArrayList<>(path));

for(int i=start;i<nums.length;i++){

path.add(numsi);

backtrace(nums,i+1,path,res);

path.remove(path.size()-1);

}

}

public List<List> subsets(int\[\] nums) {

List<List> res=new ArrayList<>();

List path=new ArrayList<>();

backtrace(nums,0,path,res);

return res;

}

}

组合总和

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。

candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。

对于给定的输入,保证和为 target 的不同组合数少于 150 个。

示例 1:

**输入:**candidates = 2,3,6,7, target = 7

输出: \[2,2,3,7]

解释:

2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。

7 也是一个候选, 7 = 7 。

仅有这两种组合。

思路: 利用dfs+回溯+res+path+start标记

class Solution {

private void dfs(int\[\] nums,int sum,int target,int start,List path,List<List> res){

if(sum==target){

res.add(new ArrayList<>(path)); 细节1:拷贝新array

return;

}

for(int i=start;i<nums.length;i++){ 细节2:引入start而不是直接从0开始保证了下一层递归只能从当前元素的右边继续挑,绝对不可能回头去挑左边的元素,避免出门 12 21 这种情况

sum+=numsi;

if(sum>target)break; 细节6:剪枝判断,避免栈溢出

path.add(numsi);

dfs(nums,sum,target,i,path,res); 细节3:i重复选,i+1不重复选

path.remove(path.size()-1); 细节4:回溯

sum-=numsi;

}

}

public List<List> combinationSum(int\[\] candidates, int target) {

List<List> res=new ArrayList<>();

List path=new ArrayList<>();

Arrays.sort(candidates); 细节5:先排序,为了剪枝

dfs(candidates,0,target,0,path,res);

return res;

}

}

相关推荐
SunnyDays10111 小时前
Java 操作 Word 脚注和尾注:添加、读取与删除
java·word
SunnyDays10111 小时前
Java 为 Word 添加页码:隐藏封面页码、从指定页开始编号、奇偶页页码设置等
java·word·页码
开源推荐官1 小时前
多门店 O2O 商城选型复盘:从开发视角对比主流开源系统
java·架构·开源·php
吴名氏.1 小时前
Java转AI Agent教程(第二周)超时重试与错误分类
java·人工智能·python·分类·ai agent
青少儿编程课堂1 小时前
2026蓝桥杯·全国青少年信息素养大赛·电子学会Python/C++考级:堆排序与优先队列解析
c++·python·算法·bfs·信息学竞赛
芒果作者1 小时前
我决定系统学习 Codex,并建立一套自己的知识库
学习
从琳开始9892 小时前
从Socket到UDP回显服务器:Java网络编程完整指南
java·网络·网络协议·udp·java-ee
2601_961146202 小时前
实验室等离子表面处理设备安全整改记录:通风、UV、接地三项
笔记·安全·uv
SZYSK2 小时前
OpenHarmony 上读卡、推理和双屏刷新抢 CPU:核验机与消费机的调度差
物联网·算法·harmonyos·1024程序员节