实现 Trie (前缀树)
要点:26叉树
java
class Trie {
//26叉树
private static class Node{
Node[] son = new Node[26];
boolean end = false;
}
private final Node root = new Node();
public Trie() {
}
public void insert(String word) {
Node cur = root;
for(char c : word.toCharArray()){
int index = c - 'a';
if(cur.son[index] == null){
cur.son[index] = new Node();
}
cur = cur.son[index];
}
cur.end = true;
}
public boolean search(String word) {
return find(word) == 2;
}
public boolean startsWith(String prefix) {
return find(prefix) != 0;
}
public int find(String word){
Node cur =root;
for(char c : word.toCharArray()){
int index = c - 'a';
if(cur.son[index] == null){
return 0;
}
cur = cur.son[index];
}
return cur.end? 2:1;
}
}
/**
* Your Trie object will be instantiated and called as such:
* Trie obj = new Trie();
* obj.insert(word);
* boolean param_2 = obj.search(word);
* boolean param_3 = obj.startsWith(prefix);
*/
添加与搜索单词 - 数据结构设计
要点:同上
java
class WordDictionary {
//26叉树
private static class Node{
Node[] son = new Node[26];
boolean end = false;
}
private final Node root;
public WordDictionary() {
root = new Node();
}
public void addWord(String word) {
Node cur = root;
for(char c : word.toCharArray()){
int index = c - 'a';
if(cur.son[index] == null){
cur.son[index] = new Node();
}
cur = cur.son[index];
}
cur.end = true;
}
public boolean search(String word) {
return dfs(root, word, 0);
}
private boolean dfs(Node cur, String word, int index){
if(index == word.length()){
return cur.end;
}
char ch = word.charAt(index);
if(ch == '.'){
for(Node child : cur.son){
if(child != null && dfs(child, word, index +1)){
return true;
}
}
return false;
}else{
int idx = ch - 'a';
if(cur.son[idx] == null){
return false;
}
return dfs(cur.son[idx], word, index+1);
}
}
}
/**
* Your WordDictionary object will be instantiated and called as such:
* WordDictionary obj = new WordDictionary();
* obj.addWord(word);
* boolean param_2 = obj.search(word);
*/
组合总和
要点:回溯
java
class Solution {
List<List<Integer>> anss = new ArrayList<>();
//如何去重
public List<List<Integer>> combinationSum(int[] candidates, int target) {
//bfs回溯
List<Integer> ans = new ArrayList<>();
int n = candidates.length;
boolean[] used = new boolean[n];
dfs(ans, candidates, used,0,target);
return anss;
}
public void dfs(List<Integer> ans, int[] candidates, boolean[] used, int index, int target){
if(target == 0){
List<Integer> temp = new ArrayList<>(ans);
Collections.sort(temp);
anss.add(temp);
return;
}
if(target <0){
return;
}
for(int i = index; i < candidates.length; i++){
ans.add(candidates[i]);
dfs(ans,candidates,used,i,target-candidates[i]);
ans.removeLast();
}
}
}
N 皇后 II
要点:
java
class Solution {
private int ans;
public int totalNQueens(int n) {
boolean[] col = new boolean[n];
boolean[] diag1 = new boolean[n * 2 - 1];
boolean[] diag2 = new boolean[n * 2 - 1];
dfs(0, col, diag1, diag2);
return ans;
}
private void dfs(int r, boolean[] col, boolean[] diag1, boolean[] diag2) {
int n = col.length;
if (r == n) {
ans++; // 找到一个合法方案
return;
}
for (int c = 0; c < n; c++) {
int rc = r - c + n - 1;
if (!col[c] && !diag1[r + c] && !diag2[rc]) {
col[c] = diag1[r + c] = diag2[rc] = true;
dfs(r + 1, col, diag1, diag2);
col[c] = diag1[r + c] = diag2[rc] = false; // 恢复现场
}
}
}
}
单词搜索 II
java
class Solution {
// 1. 定义 Trie 节点
private static class Node {
Node[] children = new Node[26];
String word = null; // 直接存储完整单词,方便添加结果
}
// 2. 构建 Trie
private Node buildTrie(String[] words) {
Node root = new Node();
for (String w : words) {
Node cur = root;
for (char c : w.toCharArray()) {
int idx = c - 'a';
if (cur.children[idx] == null) {
cur.children[idx] = new Node();
}
cur = cur.children[idx];
}
cur.word = w; // 单词结束处存储完整单词
}
return root;
}
// 3. 主函数
public List<String> findWords(char[][] board, String[] words) {
List<String> result = new ArrayList<>();
Node root = buildTrie(words);
int m = board.length, n = board[0].length;
// 从每个格子开始 DFS
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
dfs(board, i, j, root, result);
}
}
return result;
}
// 4. DFS 回溯
private void dfs(char[][] board, int i, int j, Node node, List<String> result) {
char c = board[i][j];
// 如果当前字符不是 node 的孩子,直接返回(剪枝)
int idx = c - 'a';
if (node.children[idx] == null) {
return;
}
node = node.children[idx]; // 沿着 Trie 往下走
// 如果当前节点是某个单词的结尾,加入结果
if (node.word != null) {
result.add(node.word);
node.word = null; // 防止重复添加同一个单词
}
// 标记当前格子已访问(用特殊字符)
board[i][j] = '#';
// 四个方向搜索
int[] dx = {-1, 1, 0, 0};
int[] dy = {0, 0, -1, 1};
for (int d = 0; d < 4; d++) {
int ni = i + dx[d];
int nj = j + dy[d];
if (ni >= 0 && ni < board.length && nj >= 0 && nj < board[0].length && board[ni][nj] != '#') {
dfs(board, ni, nj, node, result);
}
}
// 回溯,恢复当前格子字符
board[i][j] = c;
}
}
随机知识
一、集合框架概览(开篇必考)
1. 请说说Java集合的整体框架结构?
-
两大体系 :Java集合主要分为
Collection(存储单一元素)和Map(存储键值对)两大接口体系。 -
Collection子接口 :
List(有序、可重复)、Set(无序、不可重复)、Queue(队列)。 -
Map实现类 :
HashMap、TreeMap、LinkedHashMap、Hashtable、ConcurrentHashMap等。 -
核心区别 :
List重顺序,Set重唯一,Map重键值查找。
2. Collection和Collections有什么区别?
-
Collection是一个接口,是集合框架的根接口,定义了集合的基本操作方法。 -
Collections是一个工具类 ,提供了大量静态方法来操作或返回集合,如sort()、synchronizedList()等。
二、List接口(高频:ArrayList vs LinkedList)
3. ArrayList和LinkedList的区别?(几乎必考)
-
底层数据结构 :
ArrayList基于动态数组 ;LinkedList基于双向链表。 -
随机访问性能 :
ArrayList支持快速随机访问,时间复杂度O(1) ;LinkedList需遍历,时间复杂度O(n)。 -
插入/删除性能 :
ArrayList在中间位置插入删除需移动元素,效率低(O(n));LinkedList只需修改节点引用,效率高(O(1))。 -
内存占用 :
ArrayList内存更紧凑;LinkedList每个节点需额外存储两个指针,内存开销更大。 -
适用场景 :读多写少、随机访问用
ArrayList;写多读少、频繁中间插入删除用LinkedList。 -
线程安全 :两者都是线程不安全的。
4. ArrayList的扩容机制是怎样的?
-
JDK 8中采用懒加载 :
new ArrayList()时初始为空数组,第一次add时才初始化为容量10。 -
当元素数量超过当前容量时触发扩容,新容量约为原来的1.5倍 (通过
Arrays.copyOf()复制数组)。
5. Vector和ArrayList的区别?为什么不推荐用Vector?
-
Vector是线程安全的(方法用synchronized修饰),但采用粗粒度锁导致性能较差。 -
ArrayList线程不安全但性能更好。 -
现代替代方案:单线程用
ArrayList;需要线程安全可用CopyOnWriteArrayList或Collections.synchronizedList()。
三、Set接口(去重与排序)
6. HashSet、LinkedHashSet、TreeSet的区别?
-
HashSet:基于HashMap实现,无序 ,允许一个null,性能最高。 -
LinkedHashSet:继承HashSet,内部用LinkedHashMap,保持插入顺序。 -
TreeSet:基于红黑树 实现,自动排序 (自然排序或定制排序),不允许null。 -
共同点:元素都不重复。
7. HashSet如何保证元素不重复?
-
依赖元素的
hashCode()和equals()方法。 -
先计算哈希值定位桶;若该位置无元素则直接存入;若有元素则用
equals()比较,false则以链表/红黑树形式存储。 -
重要原则 :重写
equals()必须重写hashCode(),否则可能破坏唯一性。
四、Map接口(重中之重:HashMap)
8. HashMap的底层数据结构是什么?JDK 7和JDK 8有何区别?
-
JDK 7 :数组 + 链表(拉链法解决哈希冲突)。
-
JDK 8 :数组 + 链表 + 红黑树 。当链表长度 > 8 且数组容量 > 64时,链表转为红黑树,优化查询效率。
9. HashMap、Hashtable、TreeMap、LinkedHashMap的区别?
-
线程安全 :
Hashtable线程安全(方法加锁),其余均非线程安全。 -
null键值 :
HashMap允许一个null键和多个null值;Hashtable不允许null。 -
顺序 :
TreeMap按键排序;LinkedHashMap保持插入或访问顺序;HashMap无序。 -
底层 :
HashMap数组+链表+红黑树;Hashtable类似但线程安全;TreeMap红黑树;LinkedHashMap继承HashMap+ 双向链表。
10. HashMap的工作原理(put/get流程)?
-
put:计算key的哈希值 → 定位数组索引 → 若为空则创建节点;若存在则遍历链表/红黑树,找到相同key则替换value,否则插入新节点。
-
get:计算key哈希值定位索引 → 遍历链表/红黑树查找匹配key → 返回对应value。
-
扩容时机:元素数量 > 容量 × 加载因子(默认0.75)时触发扩容。
五、其他重要概念
11. Iterator和ListIterator的区别?
-
Iterator可遍历List和Set,支持单向遍历及remove()。 -
ListIterator仅用于List,支持双向遍历,并可添加、修改元素。
12. fail-fast(快速失败)和fail-safe(安全失败)机制?
-
fail-fast :遍历时若集合结构被修改(非迭代器自身
remove),立即抛出ConcurrentModificationException。 -
fail-safe :遍历时操作的是集合的副本,允许并发修改(如
CopyOnWriteArrayList)。
13. 数组和集合的区别?
-
数组长度固定 ;集合长度动态可变。
-
数组可存基本类型 和引用类型;集合只能存引用类型(基本类型需包装类)。
-
数组元素类型必须相同;集合可存不同类型对象。
六、并发集合(进阶必问)
14. ConcurrentHashMap的实现原理?
-
JDK 8采用数组 + 链表 + 红黑树 ,与
HashMap结构类似。 -
通过CAS + synchronized 实现线程安全,锁粒度更细(仅锁住链表/红黑树的头节点),并发性能远优于
Hashtable。
15. ArrayList如何变成线程安全?
-
Collections.synchronizedList(list):全局锁包装,性能一般。 -
CopyOnWriteArrayList:写时复制,读操作无锁,适合读多写少场景。 -
Vector:古老实现,全方法加锁,基本不推荐。
面试准备建议
面试官真正想看的,是你有没有思考过"为什么这么设计",以及能不能结合业务做优化。
建议重点掌握:
-
底层数据结构(数组、链表、红黑树)
-
核心方法的时间复杂度
-
线程安全问题及解决方案
-
JDK版本差异(特别是HashMap在7和8中的变化)
以上覆盖了Java集合面试中80%以上的高频考点。
碎碎念:后续会更新每天学习的八股和算法 题,开始准备秋招的第54天。努力连续更新100天!以后每天就按,秋招项目【java +agent】,科研,必做项目,算法,八股,锻炼身体来总结。
总结:慢慢恢复状态吧
1.算法面试150 104/150 2h
2.秋招项目,【java 项目】,
【agent 项目 】,
3.科研要跑一下,
4.实习;6h
6.背八股,1h
7.锻炼身体,
杂活好多,少玩手机