一、算法常识
ASCII码
1-9 : 49 - 57; A-Z : 65 - 90; a-z : 97 - 122
小写 = 大写 + 32; 大写 = 小写 - 32
Integer最大/最小值
Integer.MAX_VALUE = 2147483647 Integer.MIN_VALUE = -2147483648
位运算符
& 按位与 有0则0,全1才1
| 按位或 有1则1,全0才0
^ 按位异或 相同为0,不同为1
~ 按位取反 全部位0变1、1变0
<< 左移 左移一位,等价于乘以2
>> 右移(带符号) 右移一位,等价于除以2向下取整
>>> 无符号右移(Java 特有) 无证正负,左边一律补零
二、算法类型思路
1. 动态规划
(1)定义dp数组,明确数组下标的含义
(2)明确每个元素的各种状态,推出递推公式
(3)dp数组如何初始化,如何遍历顺序
在这个基础上可以通过维护一些参数取代dp数组,降低空间复杂度
2. 贪心算法
核心:每一步都选当前最优,局部最优最终得到全局最优
(1)设计贪心策略(每一步怎么选最优)
(2)局部最优累加
(3)验证是否可行
3. 回溯算法
步骤:递归+循环,可通过剪枝优化
三、常用数据结构
1. 数组(Array)
数组是最基本的数据结构,大部分题目都会涉及。
常用方法如下:
int[] arr = {3, 1, 4, 1, 5};
// 填充
Arrays.fill(arr, 0); // 全部赋值 0
// 拷贝
int[] copy = Arrays.copyOf(arr, arr.length);
// 二分查找(需要升序数组, 不存在就返回负数)
int idx = Arrays.binarySearch(arr, 4);
一维数组排序:
// 整型排序
int[] nums = {3, 1, 2};
Arrays.sort(nums); // 升序
// 局部排序
Arrays.sort(arr, 0, 3);
// 字符排序
char[] chars = {'r', 'a', 'b'};
Arrays.sort(chars); // 升序
// 降序排序(对象类型)
Integer[] nums2 = {3, 1, 2};
Arrays.sort(nums2, (a, b) -> b - a);
二维数组排序:
// 按第 0 列升序
Arrays.sort(arr, Comparator.comparingInt(a -> a[0]));
// 按第 0 列降序
Arrays.sort(arr, (a, b) -> Integer.compare(b[0], a[0]));
2. 字符串(String / StringBuilder)
算法中字符串处理也很多,尤其是子串、回文、哈希等。
常用方法如下:
String s = "hello";
// 转数组
char[] chars = s.toCharArray();
// 截取
String sub = s.substring(1, 3); // "el"
// 查找
s.indexOf("ll"); // 2
s.contains("he"); // true
// 替换
s.replace("l", "x"); // hexxo
// 拼接(效率高用 StringBuilder)
StringBuilder sb = new StringBuilder();
sb.append("a").append("b");
sb.toString();
3. 集合框架(Collections)
(1)List
List<Integer> list = new ArrayList<>();
list.add(1); list.add(2);
list.get(0); // 1
list.remove(1); // 按索引删
list.contains(2); // false
Collections.sort(list); // 排序
(2)Set
-
特点:不重复,适合去重、查找是否存在
Set<Integer> set = new HashSet<>();
set.add(1); set.add(2); set.add(2); // 只会保留一个 2
set.contains(1); // true
set.remove(1); // 删除
(3)Map
-
键值对存储,查找效率高
Map<Character, Integer> map = new HashMap<>();
map.put('a', 1);
map.get('a'); // 1
map.containsKey('b'); // false
map.remove('a');
4. 栈 & 队列
算法题常考栈和队列(括号匹配、BFS/DFS、单调栈、滑动窗口)。
// 栈
Stack<Integer> stack = new Stack<>();
stack.push(1);
stack.pop();
stack.peek(); // 栈顶
// 队列
Queue<Integer> queue = new LinkedList<>();
queue.offer(1);
queue.poll(); // 出队
queue.peek(); // 队首
// 双端队列(单调队列用得多)
Deque<Integer> deque = new LinkedList<>();
deque.offerFirst(1);
deque.offerLast(2);
deque.pollFirst();
deque.pollLast();
(1) 优先队列(堆)
-
优先队列:出队顺序由元素优先级(通常用最小/最大堆实现)决定,而不是按入队顺序。
-
Java 提供
PriorityQueue<E>,底层是 最小堆(默认升序)。 -
常用于 堆排序、TopK 问题、Dijkstra 最短路、合并 K 个有序链表 等。
PriorityQueue<Integer> pq = new PriorityQueue<>(); // 默认小根堆
pq.offer(3);
pq.offer(1);
pq.offer(2);
pq.poll(); // 1// 大根堆
PriorityQueue<Integer> maxHeap = new PriorityQueue<>((a, b) -> b - a);
✅ 时间复杂度
-
插入:O(log n) (堆调整)
-
删除(poll):O(log n)
-
获取堆顶(peek):O(1)
-
TopK 问题中,每次操作堆大小为 K,复杂度 O(n log k)。
(2) 双端队列
-
双端队列(Deque,Double Ended Queue):队头和队尾都可以插入、删除。
-
Java 提供接口
Deque<E>,常用实现类有:ArrayDeque(推荐,效率高)、LinkedList。Deque<Integer> deque = new ArrayDeque<>();
deque.addFirst(1); // 队头加
deque.addLast(2); // 队尾加
deque.removeFirst(); // 队头删
deque.removeLast(); // 队尾删
✅ 时间复杂度
-
插入 / 删除:O(1)
-
取队头/队尾元素:O(1)
-
在 滑动窗口最大值 算法中,每个元素进出队列最多一次,总复杂度 O(n)。
四、常用方法
字符串
判断回文字符串
双指针法
public static boolean isPalindrome(String s) {
if (s == null) return false;
int left = 0, right = s.length() - 1;
while (left < right) {
if (s.charAt(left) != s.charAt(right)) {
return false;
}
left++;
right--;
}
return true;
}
🟢 特点:时间复杂度:O(n) 空间复杂度:O(1)
dp数组
boolean[][] dp = new boolean[n][n];
for (int right = 0; right < n; right++) {
for (int left = 0; left <= right; left++) {
if (s.charAt(left) == s.charAt(right) &&
(right - left <= 2 || dp[left + 1][right - 1])) {
dp[left][right] = true;
}
}
}
要是需要多次判断,使用dp数组预处理,降低整体时间复杂度
数组
数组中两数转换
private void swap(int[] nums, int a, int b) {
int ans = nums[a];
nums[a] = nums[b];
nums[b] = ans;
}
数组反转
private void reverse(int[] nums, int start, int end) {
while (start < end) {
// 交换 nums[start] 和 nums[end]
int temp = nums[start];
nums[start] = nums[end];
nums[end] = temp;
// 移动指针
start++;
end--;
}
矩阵数组基于主对角线反转
就是把元素 (i, j) 和 (j, i) 交换
public static void reverseMainDiagonal(int[][] matrix) {
int n = matrix.length;
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
int temp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = temp;
}
}
}
矩阵数组基于副对角线反转
副对角线下标特点:i + j = n - 1,要交换 (i, j) 和 (n-1-j, n-1-i)。
public static void reverseSecondaryDiagonal(int[][] matrix) {
int n = matrix.length;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n - i; j++) {
int temp = matrix[i][j];
matrix[i][j] = matrix[n - 1 - j][n - 1 - i];
matrix[n - 1 - j][n - 1 - i] = temp;
}
}
}
原地哈希法
将数组中的元素和数组索引按照某种规则一一对应。比如将数组中的元素 i 存储到nums[i - 1]中,空间复杂度为常数级别,可解决缺失的第一个正数问题
public int method(int[] nums) {
int n = nums.length;
// Step 1: 原地哈希排序,把每个数放到正确的位置 nums[i] -> nums[nums[i] - 1]
for (int i = 0; i < n; i++) {
while (nums[i] > 0 && nums[i] <= n && nums[i] != nums[nums[i] - 1]) {
swap(nums, i, nums[i] - 1);
}
}
}
二分查找算法
public int binarySearch(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2; // 避免溢出
if (nums[mid] == target) {
return mid; // 找到目标
} else if (nums[mid] < target) {
left = mid + 1; // 目标在右边
} else {
right = mid - 1; // 目标在左边
}
}
return -1; // 未找到
}
链表
反转链表
定义三个指针,每次迭代将 curr.next 指向 prev,然后三个指针往后移动,直到 curr 为 null。

public ListNode reverseList(ListNode head) {
// 初始化前一个节点为 null
ListNode prev = null;
// 当前节点从 head 开始
ListNode curr = head;
// 遍历链表,直到 curr 为 null
while (curr != null) {
// 保存当前节点的下一个节点
ListNode next = curr.next;
// 反转当前节点的指针指向前一个节点
curr.next = prev;
// 向后移动 prev 和 curr 指针
prev = curr;
curr = next;
}
// 最终 prev 指向反转后的头节点
return prev;
}
找链表中点
使用快慢指针
private ListNode findCenter(ListNode head) {
if(head == null || head.next.next == null) {
return null;
}
ListNode fast = head;
ListNode slow = head;
while(fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}
return slow;
}
合并两个有序链表
private ListNode merge(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode(0); // 虚拟头节点
ListNode tail = dummy;
while (l1 != null && l2 != null) {
if (l1.val < l2.val) {
tail.next = l1; // 添加较小节点
l1 = l1.next;
} else {
tail.next = l2;
l2 = l2.next;
}
cur = cur.next; // 移动指针
}
// 接上剩余部分
tail.next = (l1 != null) ? l1 : l2;
return dummy.next;
}
链表排序
链表排序适合使用 归并排序(Merge Sort),思路是分而治之
步骤如下:
-
使用快慢指针找到链表的中点;
-
递归地将左右链表进行归并排序;
-
合并两个有序链表。

public ListNode sortList(ListNode head) {
// 递归结束条件:空链表或只有一个节点
if (head == null || head.next == null) {
return head;
}
// 快慢指针找中点
ListNode slow = head, fast = head, prev = null;
while (fast != null && fast.next != null) {
prev = slow; // 记录 slow 前一个节点
slow = slow.next; // slow 每次走一步
fast = fast.next.next; // fast 每次走两步
}
prev.next = null; // 断开链表,分为左右两部分
// 对左右两部分递归排序
ListNode l1 = sortList(head);
ListNode l2 = sortList(slow);
// 合并两个有序链表
return merge(l1, l2);
}
// 合并两个有序链表
private ListNode merge(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode(0); // 虚拟头节点
ListNode tail = dummy;
while (l1 != null && l2 != null) {
if (l1.val < l2.val) {
tail.next = l1; // 添加较小节点
l1 = l1.next;
} else {
tail.next = l2;
l2 = l2.next;
}
cur = cur.next; // 移动指针
}
// 接上剩余部分
tail.next = (l1 != null) ? l1 : l2;
return dummy.next;
}
二叉树
中序遍历
中序遍历左中右
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
inorder(root, result); // 从根节点开始中序遍历
return result;
}
// 递归方法:中序遍历
private void inorder(TreeNode node, List<Integer> result) {
if (node == null) {
return; // 如果当前节点为空,直接返回
}
inorder(node.left, result); // 递归遍历左子树
result.add(node.val); // 访问当前节点(根节点)
inorder(node.right, result); // 递归遍历右子树
}
树的最大深度/高度
最大深度 = max(左子树深度, 右子树深度) + 1
public int maxDepth(TreeNode root) {
// 如果当前节点为空,深度为0
if (root == null) {
return 0;
}
// 递归获取左子树的最大深度
int leftDepth = maxDepth(root.left);
// 递归获取右子树的最大深度
int rightDepth = maxDepth(root.right);
// 当前树的最大深度 = 左右子树最大深度的较大者 + 1(加上当前节点)
return Math.max(leftDepth, rightDepth) + 1;
}
二叉树DFS
二叉树的层序遍历用队列实现
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
if (root == null) return res;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int size = queue.size(); // 当前层节点数量
List<Integer> level = new ArrayList<>();
for (int i = 0; i < size; i++) {
TreeNode node = queue.poll();
level.add(node.val);
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
res.add(level);
}
return res;
}
拓扑排序
拓扑排序(Topological Sorting) 是针对 有向无环图(DAG, Directed Acyclic Graph) 的一种 线性排序。
它要求:
若图中存在一条边
u → v,则在排序结果中
u一定出现在v之前。
拓扑排序常用 两种算法:
| 方法 | 思想 | 关键数据结构 |
|---|---|---|
| 1️⃣ Kahn 算法(BFS) | 每次取入度为 0 的节点 | 队列(Queue) |
| 2️⃣ DFS 递归算法 | 深度遍历 + 栈逆序输出 | 栈(Stack) |
🧩 Kahn 算法(基于入度的 BFS)
✅ 思想步骤:
-
统计每个节点的 入度。
-
将所有 入度为 0 的节点加入队列(表示没有前置依赖)。
-
不断出队节点,并将其邻接点的入度减 1。
-
如果某个节点入度变为 0,则入队。
-
当队列为空时,若输出的节点数等于图中节点数 → 无环;否则有环。
💻 代码示例(Java)
public List<Integer> topologicalSort(int numCourses, int[][] prerequisites) {
List<List<Integer>> graph = new ArrayList<>();
for (int i = 0; i < numCourses; i++) graph.add(new ArrayList<>());
int[] indegree = new int[numCourses];
for (int[] pre : prerequisites) {
int a = pre[0], b = pre[1];
graph.get(b).add(a);
indegree[a]++;
}
Queue<Integer> queue = new LinkedList<>();
for (int i = 0; i < numCourses; i++) {
if (indegree[i] == 0) queue.offer(i);
}
List<Integer> res = new ArrayList<>();
while (!queue.isEmpty()) {
int cur = queue.poll();
res.add(cur);
for (int next : graph.get(cur)) {
indegree[next]--;
if (indegree[next] == 0) queue.offer(next);
}
}
return res.size() == numCourses ? res : new ArrayList<>();
}
🧮 DFS 实现(递归检测环 + 栈保存顺序)
✅ 思想步骤:
-
对每个未访问节点执行 DFS。
-
递归过程中:
-
标记当前节点为"访问中"。
-
对所有相邻节点递归访问。
-
如果遇到"访问中"的节点 → 存在环。
-
-
访问完一个节点后,将其压入栈。
-
最后栈中元素逆序即为拓扑序。
💻 代码示例(Java)
public List<Integer> topologicalSortDFS(int numCourses, int[][] prerequisites) {
List<List<Integer>> graph = new ArrayList<>();
for (int i = 0; i < numCourses; i++) graph.add(new ArrayList<>());
for (int[] pre : prerequisites) graph.get(pre[1]).add(pre[0]);
int[] visited = new int[numCourses]; // 0=未访问,1=访问中,2=已完成
Stack<Integer> stack = new Stack<>();
for (int i = 0; i < numCourses; i++) {
if (!dfs(graph, visited, stack, i)) return new ArrayList<>(); // 有环
}
List<Integer> res = new ArrayList<>();
while (!stack.isEmpty()) res.add(stack.pop());
return res;
}
private boolean dfs(List<List<Integer>> graph, int[] visited, Stack<Integer> stack, int node) {
if (visited[node] == 1) return false; // 环
if (visited[node] == 2) return true;
visited[node] = 1;
for (int next : graph.get(node)) {
if (!dfs(graph, visited, stack, next)) return false;
}
visited[node] = 2;
stack.push(node);
return true;
}
📈 复杂度分析
| 项目 | 复杂度 |
|---|---|
| 时间复杂度 | O(V + E),遍历所有节点和边 |
| 空间复杂度 | O(V + E),用于图结构与辅助队列/栈 |
排序
🧮 一、冒泡排序(Bubble Sort)
💡 思想:
两层for循环,内层是逐个比较相邻两个元素,如果前者大于后者则交换,外层每次遍历都把当前最大的数迁移到末尾。优化点:如果内层都没有交换则证明原数组是升序的,时间复杂度从O(n^2)降为O(n)
✅ Java实现:
public static void bubbleSort(int[] arr) {
int n = arr.length;
for (int i = 0; i < n - 1; i++) {
boolean swapped = false;
for (int j = 0; j < n - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
swapped = true;
}
}
if (!swapped) break; // 优化:如果一轮无交换则提前结束
}
}
📈 复杂度:
| 最好 | 平均 | 最坏 | 空间复杂度 | 稳定性 |
|---|---|---|---|---|
| O(n) | O(n²) | O(n²) | O(1) | ✅ 稳定 |
🪄 二、选择排序(Selection Sort)
💡 思想:
每轮从未排序部分选出最小元素,与未排序部分头元素交换位置
✅ Java实现:
public static void selectionSort(int[] arr) {
int n = arr.length;
for (int i = 0; i < n - 1; i++) {
int minIndex = i;
for (int j = i + 1; j < n; j++) {
if (arr[j] < arr[minIndex]) minIndex = j;
}
int temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
}
📈 复杂度:
| 最好 | 平均 | 最坏 | 空间复杂度 | 稳定性 |
|---|---|---|---|---|
| O(n²) | O(n²) | O(n²) | O(1) | ❌ 不稳定 |
⚙️ 三、插入排序(Insertion Sort)
💡 思想:
将数组分为"已排序"和"未排序"部分,每次取出未排序的第一个元素,插入到前面合适的位置。
public static void insertionSort(int[] arr) {
for (int i = 1; i < arr.length; i++) {
int key = arr[i];
int j = i - 1;
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
}
📈 复杂度:
| 最好 | 平均 | 最坏 | 空间复杂度 | 稳定性 |
|---|---|---|---|---|
| O(n) | O(n²) | O(n²) | O(1) | ✅ 稳定 |
⚡ 四、快速排序(Quick Sort)
💡 思想:
选择一个"基准"(pivot),将数组分为两部分:比pivot小的在左,比pivot大的在右。递归排序两部分。
✅ Java实现:
public static void quickSort(int[] arr, int left, int right) {
if (left >= right) return;
int pivot = arr[left];
int i = left, j = right;
while (i < j) {
while (i < j && arr[j] >= pivot) j--;
while (i < j && arr[i] <= pivot) i++;
if (i < j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
arr[left] = arr[i];
arr[i] = pivot;
quickSort(arr, left, i - 1);
quickSort(arr, i + 1, right);
}
📈 复杂度:
| 最好 | 平均 | 最坏 | 空间复杂度 | 稳定性 |
|---|---|---|---|---|
| O(n log n) | O(n log n) | O(n²) | O(log n) | ❌ 不稳定 |
🧩 五、归并排序(Merge Sort)
💡 思想:
分治法:将数组分成两半,分别排序,再合并两个有序数组。
✅ Java实现:
public static void mergeSort(int[] arr, int left, int right) {
if (left >= right) return;
int mid = (left + right) / 2;
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
merge(arr, left, mid, right);
}
private static void merge(int[] arr, int left, int mid, int right) {
int[] temp = new int[right - left + 1];
int i = left, j = mid + 1, k = 0;
while (i <= mid && j <= right) {
temp[k++] = (arr[i] <= arr[j]) ? arr[i++] : arr[j++];
}
while (i <= mid) temp[k++] = arr[i++];
while (j <= right) temp[k++] = arr[j++];
System.arraycopy(temp, 0, arr, left, temp.length);
}
📈 复杂度:
| 最好 | 平均 | 最坏 | 空间复杂度 | 稳定性 |
|---|---|---|---|---|
| O(n log n) | O(n log n) | O(n log n) | O(n) | ✅ 稳定 |
🏗️ 六、堆排序(Heap Sort)
💡 思想:
利用大顶堆(或小顶堆)特性,每次取堆顶元素(最大/最小),调整堆。
✅ Java实现:
public static void heapSort(int[] arr) {
int n = arr.length;
// 建堆
for (int i = n / 2 - 1; i >= 0; i--) heapify(arr, n, i);
// 逐个取出堆顶元素
for (int i = n - 1; i > 0; i--) {
int temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;
heapify(arr, i, 0);
}
}
private static void heapify(int[] arr, int n, int i) {
int largest = i;
int left = 2 * i + 1, right = 2 * i + 2;
if (left < n && arr[left] > arr[largest]) largest = left;
if (right < n && arr[right] > arr[largest]) largest = right;
if (largest != i) {
int temp = arr[i];
arr[i] = arr[largest];
arr[largest] = temp;
heapify(arr, n, largest);
}
}
📈 复杂度:
| 最好 | 平均 | 最坏 | 空间复杂度 | 稳定性 |
|---|---|---|---|---|
| O(n log n) | O(n log n) | O(n log n) | O(1) | ❌ 不稳定 |
排序的 稳定性 指的是:
当有多个相同关键字的元素时,排序后它们的相对顺序是否保持不变。
✅ 举个例子
假设有一组学生成绩表,按"成绩"排序:
| 姓名 | 成绩 |
|---|---|
| 小明 | 90 |
| 小红 | 80 |
| 小刚 | 90 |
我们要按成绩升序排列。
-
如果排序后还是:
小红(80), 小明(90), 小刚(90)------✅ 稳定排序(小明和小刚原来顺序没变)
-
如果排序后成了:
小红(80), 小刚(90), 小明(90)------❌ 不稳定排序(两个90的顺序被打乱)