一、整数
常解决问题
-
位运算,快速幂思想,快速除法
-
小技巧:根据 i & (i-1) 计算 i 的二进制形式中的 1 的个数(面试题 3:前 n 个数字二进制数字中 1 的个数)
-
小技巧:累加二进制数位求余(面试题 4:只出现一次的数字)
-
位运算也许可以对算法进行优化!
二、数组
ArraysList 常用方法;
add();
remove();
get();
set();
size();
isEmpty();
contains();
clear();
LinkedList 常用方法;
add();
remove();
get();
set();
size();
isEmpty();
contains();
clear();
removeLast();
常解决问题
-
双指针 + 哈希表
-
双指针 -> 滑动窗口
-
前缀和 + 哈希表(前缀和中哈希表的初始化)
-
去重思想
三、字符串
// String 中的常用方法
charAt(); // 指定下标的字符
compareTo(); // 按照字典序比较两个字符串
equals(); // 判断两个字符串的长度和内容是否相同
indexOf(); // 返回某个字符或字符串首次出现的问题
lastIndexOf(); // 返回某个字符或字符串最后一次出现的问题
length(); // 返回字符串的长度
split(); // 将字符串按照指定的分隔符进行分割,返回数组类型
substring(); // 根据下表截取子字符串
toLowerCase(); // 所有字符改为小写
toUpperCase(); // 所有字符改为大写
可修改的字符串类,StringBuffer(线程安全,性能差)、StringBuilder(线程不安全,性能高)
// 增:append(xxx)
// 删:delete(int start,int end)
// 改:setCharAt(int n ,char ch) / replace(int start, int end, String str)
// 查:charAt(int n )
// 插:insert(int offset, xxx)
// 长度:length();
// 遍历:for() + charAt() / toString()
常解决问题
- 双指针 + 哈希表(比如使用哈希表统计 两个指针之间出现的字符个数等)
四、链表
哨兵节点思想 -> 简化创建和删除链表节点的逻辑,位于链表头部,无实际意义
注意点:注意改动节点,节点之间指针指向丢失或者错误的问题,要正确修改节点
常解决问题
- 双指针(快慢指针)(面试题 21:链表中环的入口节点)
- 反转链表(前一个节点,当前节点,下一个节点)
- 最佳练习:面试题 26:重排链表
- 最佳练习:面试题 25:链表中的数字相加
- 双向链表
- 循环链表
五、哈希表
// HashSet 常用方法;
add();
contains();
remove();
size();
// HashMap 常用方法;
containsKey();
get();
getOrDefault();
put();
putIfAbsent();
remove();
replace();
size();
// HashMap 遍历;
// 1. 通过 map.keySet();
// 2. 通过 map.values();
// 3. 推荐方式 map.entrySet(), Map.Entry<>
// for (Map.Entry<String, String> entry : map.entrySet()) {
// System.out.println("key is "+entry.getKey());
// System.out.println("value is "+ entry.getValue());
// }
常解决问题
- LRU (最近最少使用)算法:双向链表 + 哈希表
- 各类问题
- 映射思想,个人理解以为映射思想也是“加一层”思想的另一种体现,某位计算机先人曾说,计算机界的问题都可以通过加一层解决
六、栈
Stack<Object> stack = new Stack<>();
- push()、pop()、peek()
常解决问题
- 如果一个数据集合的添加、删除满足后入先出的特点,通常使用栈
- 输入的数据,暂时不用处理,可以使用栈
- 单调栈问题(高频必会)
七、队列
Queue<TreeNode> queue = new LinkedList<>();
-
offer()、poll()、peek()(遇空,返回 null)
-
add()、remove()、element()(遇空,抛异常)
常解决问题
-
具有先进先出特性
-
二叉树的广度优先搜索(不同层,考虑两个队列)
-
二叉树,提到层的概念用广搜,提到路径的概念用深搜
-
通常,使用队列解决广搜的距离问题时,可以使用两个队列!
八、树
二叉树的深度优先搜索(递归与递推代码要烂熟于心),一定要理解递归的思想
- 中序遍历(左根右):解决二叉搜索树(增删查都为 O(logn) 复杂度)的问题
- 前序遍历(根左右):路径问题或其他
- 后序遍历(左右根):路径问题或其他
红黑树 TreeSet、TreeMap
-
可以解决 动态地在一个排序的数据集合中添加元素,或者需要根据数据的大小查找问题。
-
// TreeSet 常用方法; ceiling(); // 大于等于给定值的键 floor(); // 小于等于给定值的键 higher(); // 大于给定值的键 lower(); // 小于给定值的键
-
// TreeMap 常用方法; ceilingEntry/ceilingKey // 大于等于给定值的最小映射 / 键 floorEntry/floorKey // 小于等于给定值的最小映射 / 键 higherEntry/higherKey // 大于给定值的最小映射 / 键 lowerEntry/lowerKey // 小于给定值的最小映射 / 键
九、堆
堆分为最大堆和最小堆,通常使用完全二叉树实现。底层数据结构可以用数组实现,通常只操作(查询、删除)位于顶部的元素,添加与删除的复杂度为 O(logn)。
Java 提供了 PriorityQueue 类,该类实现了接口 Queue。默认情况下是最小堆,需要使用 Comparator 改变排序规则。第一个元素减第二个元素是升序,第二个减第一个元素是降序。(通过 Lamda 表达式实现)
Queue<Map.Entry<Integer, Integer>> minheap = new PriorityQueue<>((e1, e2)->e1.getValue()-e2.getValue());
常解决问题
- 求出一个动态数据集合中的最大值或最小值,第 k 个最大/小值的问题。
- 求出一个数据流中的最大值或最小值等
十、前缀树
前缀树主要用来解决与字符串查找相关的问题,又称为查找树,可以用来根据前缀查找等,时间复杂度 O(k) 。
典型应用如:过滤敏感词
前缀树示例代码
class Trie {
// 创建节点的数据结构类,节点类
static class TrieNode {
TrieNode[] children;
boolean isWord;
public TrieNode() {
children = new TrieNode[26];
}
}
private TrieNode root;
/** Initialize your data structure here. */
public Trie() {
root = new TrieNode();
}
/** Inserts a word into the trie. */
public void insert(String word) {
TrieNode node = root;
for (char ch : word.toCharArray()) {
if (node.children[ch - 'a'] == null) {
node.children[ch - 'a'] = new TrieNode();
}
node = node.children[ch - 'a'];
}
node.isWord = true;
}
/** Returns if the word is in the trie. */
public boolean search(String word) {
TrieNode node = root;
for (char ch : word.toCharArray()) {
if (node.children[ch - 'a'] == null) {
return false;
}
node = node.children[ch - 'a'];
}
return node.isWord;
}
/** Returns if there is any word in the trie that starts with the given prefix. */
public boolean startsWith(String prefix) {
TrieNode node = root;
for (char ch : prefix.toCharArray()) {
if (node.children[ch - 'a'] == null) {
return false;
}
node = node.children[ch - 'a'];
}
return true;
}
}
常解决问题
- 字符串查找
十一、二分查找
修改 left 或 right 的判断条件要仔细琢磨
常解决问题
- 排序数组中查找
- 数值范围内查找
- 通常作为一种比较优的算法,O(logn)
十二、排序
-
插入排序
-
选择排序
-
冒泡排序
-
堆排序(通过最大堆或者最小堆实现,O(nlogn))
-
计数排序(数值范围远小于数据量的情况下,效率极高,如员工年龄排序;统计数组中每个数出现的次数)
-
快速排序(分治法,随机选择一个元素作为中间值,然后对数据进行分区,平均 O(nlogn),最坏 O(n2))
-
归并排序(分治法,不论什么情况都是 O(nlogn))
常解决问题
- 合并排序链表等(通常都可以用堆排序解决,但是就怕面试官让手写快排,归并等)
十三、回溯
暴力的高级版,非常适合解决由多个步骤组成的问题,并且每个步骤都有多个选项。
回溯法解决问题的过程可以形象地用一个树形结构表示,求解问题的每个步骤可以看做树的一个节点。
在采用回溯法解决问题时如果达到树形结构的叶节点,就找到了问题的一个解。如果希望找到更多的解,那么还可以回溯到它的父节点再尝试父节点的其他选项。故,采用回溯的方法解题的过程实际上是在树形结构中从根节点开始进行深度优先遍历。通常,用递归代码实现。
如果前往某个状态时对问题的解的状态进行了修改,那么在回溯到它的父节点时要清楚响应的修改。
通常可以通过剪枝提高效率。
常解决问题
- 通常解决组合与排列相关的问题!
- 问题的解需要多个步骤,每个步骤又有若干个选择!
- 每个选择,一般两种情况,选择或者不选择!
- 组合(跳过,不跳过并回溯)
- 排列(交换,搜索下一个位置,回溯交换)
十四、动态规划
通常回溯能解决的问题,也可以用动态规划解决
动态规划具有最优子结构!多个选择!通常求解的个数!最优值等!
想不来就手写表格,演算!
常解决问题
- 单序列问题
- 线性一维 dp:dp 一次
- 环一维 dp:dp 两次,1 -> n-2,2 -> n-1
- 字符串 dp:很难,直接摆烂
- 双序列问题
- 通常是字符串的 相等 和 不相等 两种情况
- 矩阵路径
- 最简单的 dp 类型了
- 背包问题
- 通常要对问题进行转化,常见有如下类型:
- 0-1背包:物品只能选一个,只有选或者不选
- 多重背包:物品有多个,但有限
- 完全背包:物品不限
- 通常要对问题进行转化,常见有如下类型:
十五、图
通常有 邻接表 和 邻接矩阵 两种表示方式
- 图的搜索
- 广搜
- 深搜
- 拓朴排序:通常使用广搜实现,统计每个点的入度,每次删除入度为 0 的点并加入拓朴序列,更新剩余节点的入度,直至没有入度为 0 的节点。通常也用来检测有向图中是否有环!
- 图的最短路径:Dijkstra、Floyd、Bellman-Ford
- 最小生成树:Prim、Kruskal
- 并查集:初始化每个节点都是自己的根节点,路径优化的 find 方法,以及合并的 union 方法
常解决问题
- 二分图(着色问题)
- 通常需要我们对问题进行抽象建模,从实际问题到图的问题
- 距离问题通常用 广搜(两个队列)
- 路径问题通常用 深搜(递归实现)
- 拓朴排序问题,与任务顺序有关的问题,通常也用来检测有向图中是否有环!
- 并查集问题(问题可以分成若干子图,需要合并与查找等)