01.09 Java基础篇|算法与数据结构实战
导读
- 目标:掌握常用算法和数据结构,能够分析时间复杂度和空间复杂度,解决实际编程问题。
- 适用场景:算法面试、性能优化、代码重构、系统设计。
核心数据结构
1. 数组与链表
数组特点:
- 连续内存,随机访问 O(1)
- 插入删除 O(n)
- 适合查找和遍历
链表特点:
- 非连续内存,顺序访问
- 插入删除 O(1)
- 适合频繁插入删除
应用场景:
java
// 数组:适合固定大小、随机访问
int[] arr = new int[10];
arr[5] = 100; // O(1)
// 链表:适合动态大小、频繁插入删除
LinkedList<Integer> list = new LinkedList<>();
list.addFirst(1); // O(1)
2. 栈与队列
栈(LIFO):
java
Stack<Integer> stack = new Stack<>();
stack.push(1);
stack.pop(); // 后进先出
队列(FIFO):
java
Queue<Integer> queue = new LinkedList<>();
queue.offer(1);
queue.poll(); // 先进先出
应用场景:
- 栈:表达式求值、括号匹配、函数调用
- 队列:任务调度、消息队列、BFS遍历
3. 树与图
二叉树:
java
class TreeNode {
int val;
TreeNode left;
TreeNode right;
}
// 遍历方式
// 1. 前序遍历:根-左-右
// 2. 中序遍历:左-根-右
// 3. 后序遍历:左-右-根
// 4. 层序遍历:按层遍历
应用场景:
- 二叉搜索树:有序数据查找
- 红黑树:HashMap底层实现
- 堆:优先队列、TopK问题
常用算法
1. 排序算法
快速排序(Quick Sort)
实现:
java
public void quickSort(int[] arr, int left, int right) {
if (left >= right) return;
int pivot = partition(arr, left, right);
quickSort(arr, left, pivot - 1);
quickSort(arr, pivot + 1, right);
}
private int partition(int[] arr, int left, int right) {
int pivot = arr[right];
int i = left;
for (int j = left; j < right; j++) {
if (arr[j] < pivot) {
swap(arr, i, j);
i++;
}
}
swap(arr, i, right);
return i;
}
private void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
优化版本(三路快排,处理重复元素):
java
public void quickSort3Way(int[] arr, int left, int right) {
if (left >= right) return;
int pivot = arr[left];
int lt = left; // arr[left+1...lt] < pivot
int gt = right + 1; // arr[gt...right] > pivot
int i = left + 1; // arr[lt+1...i) == pivot
while (i < gt) {
if (arr[i] < pivot) {
swap(arr, i++, ++lt);
} else if (arr[i] > pivot) {
swap(arr, i, --gt);
} else {
i++;
}
}
swap(arr, left, lt);
quickSort3Way(arr, left, lt - 1);
quickSort3Way(arr, gt, right);
}
归并排序(Merge Sort)
实现:
java
public void mergeSort(int[] arr, int left, int right) {
if (left >= right) return;
int mid = left + (right - left) / 2;
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
merge(arr, left, mid, right);
}
private 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) {
if (arr[i] <= arr[j]) {
temp[k++] = arr[i++];
} else {
temp[k++] = arr[j++];
}
}
while (i <= mid) temp[k++] = arr[i++];
while (j <= right) temp[k++] = arr[j++];
System.arraycopy(temp, 0, arr, left, temp.length);
}
堆排序(Heap Sort)
实现:
java
public 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--) {
swap(arr, 0, i);
heapify(arr, i, 0);
}
}
private void heapify(int[] arr, int n, int i) {
int largest = i;
int left = 2 * i + 1;
int 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) {
swap(arr, i, largest);
heapify(arr, n, largest);
}
}
时间复杂度对比:
| 算法 | 平均 | 最坏 | 空间 | 稳定性 | 适用场景 |
|---|---|---|---|---|---|
| 快速排序 | O(n log n) | O(n²) | O(log n) | 不稳定 | 通用排序,平均性能最好 |
| 归并排序 | O(n log n) | O(n log n) | O(n) | 稳定 | 需要稳定排序,外部排序 |
| 堆排序 | O(n log n) | O(n log n) | O(1) | 不稳定 | TopK问题,空间受限 |
| 冒泡排序 | O(n²) | O(n²) | O(1) | 稳定 | 教学示例,小规模数据 |
| 插入排序 | O(n²) | O(n²) | O(1) | 稳定 | 小规模数据,部分有序 |
| 计数排序 | O(n + k) | O(n + k) | O(k) | 稳定 | 数据范围小,整数排序 |
2. 查找算法
二分查找(Binary Search)
基础版本:
java
public int binarySearch(int[] arr, int target) {
int left = 0, right = arr.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2; // 防止溢出
if (arr[mid] == target) {
return mid;
} else if (arr[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}
查找左边界(第一个等于target的位置):
java
public int binarySearchLeft(int[] arr, int target) {
int left = 0, right = arr.length;
while (left < right) {
int mid = left + (right - left) / 2;
if (arr[mid] < target) {
left = mid + 1;
} else {
right = mid;
}
}
return left < arr.length && arr[left] == target ? left : -1;
}
查找右边界(最后一个等于target的位置):
java
public int binarySearchRight(int[] arr, int target) {
int left = 0, right = arr.length;
while (left < right) {
int mid = left + (right - left) / 2;
if (arr[mid] <= target) {
left = mid + 1;
} else {
right = mid;
}
}
return left > 0 && arr[left - 1] == target ? left - 1 : -1;
}
旋转数组查找:
java
public int searchRotatedArray(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;
// 左半部分有序
if (nums[left] <= nums[mid]) {
if (nums[left] <= target && target < nums[mid]) {
right = mid - 1;
} else {
left = mid + 1;
}
} else { // 右半部分有序
if (nums[mid] < target && target <= nums[right]) {
left = mid + 1;
} else {
right = mid - 1;
}
}
}
return -1;
}
时间复杂度:O(log n)
应用场景:
- 有序数组查找
- 查找插入位置
- 查找范围
- 旋转数组查找
3. 双指针算法
核心思想:使用两个指针在数组或链表中协同工作,减少遍历次数。
快慢指针
链表环检测:
java
public boolean hasCycle(ListNode head) {
if (head == null || head.next == null) return false;
ListNode slow = head;
ListNode fast = head.next;
while (fast != null && fast.next != null) {
if (slow == fast) return true;
slow = slow.next;
fast = fast.next.next;
}
return false;
}
链表中点:
java
public ListNode findMiddle(ListNode head) {
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
左右指针
两数之和(有序数组):
java
public int[] twoSum(int[] numbers, int target) {
int left = 0, right = numbers.length - 1;
while (left < right) {
int sum = numbers[left] + numbers[right];
if (sum == target) {
return new int[]{left + 1, right + 1};
} else if (sum < target) {
left++;
} else {
right--;
}
}
return new int[]{-1, -1};
}
三数之和:
java
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(nums);
for (int i = 0; i < nums.length - 2; i++) {
if (i > 0 && nums[i] == nums[i - 1]) continue; // 去重
int left = i + 1, right = nums.length - 1;
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum == 0) {
result.add(Arrays.asList(nums[i], nums[left], nums[right]));
while (left < right && nums[left] == nums[left + 1]) left++;
while (left < right && nums[right] == nums[right - 1]) right--;
left++;
right--;
} else if (sum < 0) {
left++;
} else {
right--;
}
}
}
return result;
}
盛水最多的容器:
java
public int maxArea(int[] height) {
int left = 0, right = height.length - 1;
int maxArea = 0;
while (left < right) {
int area = Math.min(height[left], height[right]) * (right - left);
maxArea = Math.max(maxArea, area);
if (height[left] < height[right]) {
left++;
} else {
right--;
}
}
return maxArea;
}
4. 滑动窗口算法
核心思想:维护一个窗口,通过移动窗口边界来解决问题。
固定窗口大小
大小为K的子数组最大和:
java
public int maxSumSubarray(int[] nums, int k) {
int windowSum = 0;
for (int i = 0; i < k; i++) {
windowSum += nums[i];
}
int maxSum = windowSum;
for (int i = k; i < nums.length; i++) {
windowSum = windowSum - nums[i - k] + nums[i];
maxSum = Math.max(maxSum, windowSum);
}
return maxSum;
}
可变窗口大小
无重复字符的最长子串:
java
public int lengthOfLongestSubstring(String s) {
Map<Character, Integer> map = new HashMap<>();
int left = 0, maxLen = 0;
for (int right = 0; right < s.length(); right++) {
char c = s.charAt(right);
if (map.containsKey(c)) {
left = Math.max(left, map.get(c) + 1);
}
map.put(c, right);
maxLen = Math.max(maxLen, right - left + 1);
}
return maxLen;
}
最小覆盖子串:
java
public String minWindow(String s, String t) {
Map<Character, Integer> need = new HashMap<>();
Map<Character, Integer> window = new HashMap<>();
for (char c : t.toCharArray()) {
need.put(c, need.getOrDefault(c, 0) + 1);
}
int left = 0, right = 0;
int valid = 0;
int start = 0, len = Integer.MAX_VALUE;
while (right < s.length()) {
char c = s.charAt(right);
right++;
if (need.containsKey(c)) {
window.put(c, window.getOrDefault(c, 0) + 1);
if (window.get(c).equals(need.get(c))) {
valid++;
}
}
while (valid == need.size()) {
if (right - left < len) {
start = left;
len = right - left;
}
char d = s.charAt(left);
left++;
if (need.containsKey(d)) {
if (window.get(d).equals(need.get(d))) {
valid--;
}
window.put(d, window.get(d) - 1);
}
}
}
return len == Integer.MAX_VALUE ? "" : s.substring(start, start + len);
}
应用场景:
- 子串/子数组问题
- 字符串匹配
- 连续子数组问题
- 固定窗口统计
5. 动态规划(Dynamic Programming)
核心思想:将问题分解为子问题,通过记忆化避免重复计算。
基础DP
斐波那契数列:
java
// 递归(低效,O(2^n))
public int fib(int n) {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2);
}
// 动态规划(高效,O(n))
public int fibDP(int n) {
if (n <= 1) return n;
int[] dp = new int[n + 1];
dp[0] = 0;
dp[1] = 1;
for (int i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
// 空间优化(O(1))
public int fibOptimized(int n) {
if (n <= 1) return n;
int prev2 = 0, prev1 = 1;
for (int i = 2; i <= n; i++) {
int curr = prev1 + prev2;
prev2 = prev1;
prev1 = curr;
}
return prev1;
}
经典DP问题
最长公共子序列(LCS):
java
public int longestCommonSubsequence(String text1, String text2) {
int m = text1.length(), n = text2.length();
int[][] dp = new int[m + 1][n + 1];
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (text1.charAt(i - 1) == text2.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[m][n];
}
0-1背包问题:
java
public int knapsack(int[] weights, int[] values, int capacity) {
int n = weights.length;
int[][] dp = new int[n + 1][capacity + 1];
for (int i = 1; i <= n; i++) {
for (int w = 1; w <= capacity; w++) {
if (weights[i - 1] > w) {
dp[i][w] = dp[i - 1][w];
} else {
dp[i][w] = Math.max(
dp[i - 1][w],
dp[i - 1][w - weights[i - 1]] + values[i - 1]
);
}
}
}
return dp[n][capacity];
}
// 空间优化版本
public int knapsackOptimized(int[] weights, int[] values, int capacity) {
int[] dp = new int[capacity + 1];
for (int i = 0; i < weights.length; i++) {
for (int w = capacity; w >= weights[i]; w--) {
dp[w] = Math.max(dp[w], dp[w - weights[i]] + values[i]);
}
}
return dp[capacity];
}
股票买卖问题(最多一次交易):
java
public int maxProfit(int[] prices) {
int minPrice = Integer.MAX_VALUE;
int maxProfit = 0;
for (int price : prices) {
minPrice = Math.min(minPrice, price);
maxProfit = Math.max(maxProfit, price - minPrice);
}
return maxProfit;
}
股票买卖问题(无限次交易):
java
public int maxProfitUnlimited(int[] prices) {
int profit = 0;
for (int i = 1; i < prices.length; i++) {
if (prices[i] > prices[i - 1]) {
profit += prices[i] - prices[i - 1];
}
}
return profit;
}
最长递增子序列(LIS):
java
public int lengthOfLIS(int[] nums) {
int[] dp = new int[nums.length];
Arrays.fill(dp, 1);
int maxLen = 1;
for (int i = 1; i < nums.length; i++) {
for (int j = 0; j < i; j++) {
if (nums[j] < nums[i]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
maxLen = Math.max(maxLen, dp[i]);
}
return maxLen;
}
// 二分优化版本 O(n log n)
public int lengthOfLISOptimized(int[] nums) {
List<Integer> tails = new ArrayList<>();
for (int num : nums) {
int pos = Collections.binarySearch(tails, num);
if (pos < 0) pos = -(pos + 1);
if (pos == tails.size()) {
tails.add(num);
} else {
tails.set(pos, num);
}
}
return tails.size();
}
DP解题步骤:
- 定义状态:dp[i] 或 dp[i][j] 表示什么
- 状态转移方程:如何从子问题推导当前问题
- 初始状态:边界条件
- 计算顺序:确保子问题先计算
- 空间优化:如果可能,减少空间复杂度
应用场景:
- 最优化问题
- 计数问题
- 存在性问题
- 序列问题
6. 贪心算法(Greedy)
核心思想:每一步都做出当前看起来最优的选择,希望最终得到全局最优解。
贪心算法适用条件:
- 最优子结构:问题的最优解包含子问题的最优解
- 贪心选择性质:可以通过局部最优选择达到全局最优
经典问题:
活动选择问题
java
// 选择最多的不重叠活动
public int maxActivities(int[][] intervals) {
if (intervals.length == 0) return 0;
// 按结束时间排序
Arrays.sort(intervals, (a, b) -> a[1] - b[1]);
int count = 1;
int end = intervals[0][1];
for (int i = 1; i < intervals.length; i++) {
if (intervals[i][0] >= end) {
count++;
end = intervals[i][1];
}
}
return count;
}
跳跃游戏
java
// 判断能否到达最后一个位置
public boolean canJump(int[] nums) {
int maxReach = 0;
for (int i = 0; i < nums.length; i++) {
if (i > maxReach) return false;
maxReach = Math.max(maxReach, i + nums[i]);
if (maxReach >= nums.length - 1) return true;
}
return true;
}
// 最少跳跃次数
public int jump(int[] nums) {
int jumps = 0, farthest = 0, end = 0;
for (int i = 0; i < nums.length - 1; i++) {
farthest = Math.max(farthest, i + nums[i]);
if (i == end) {
jumps++;
end = farthest;
}
}
return jumps;
}
分发糖果
java
public int candy(int[] ratings) {
int n = ratings.length;
int[] candies = new int[n];
Arrays.fill(candies, 1);
// 从左到右:如果右边评分更高,右边糖果数+1
for (int i = 1; i < n; i++) {
if (ratings[i] > ratings[i - 1]) {
candies[i] = candies[i - 1] + 1;
}
}
// 从右到左:如果左边评分更高,左边糖果数+1
for (int i = n - 2; i >= 0; i--) {
if (ratings[i] > ratings[i + 1]) {
candies[i] = Math.max(candies[i], candies[i + 1] + 1);
}
}
return Arrays.stream(candies).sum();
}
区间调度问题
java
// 用最少的箭射爆所有气球
public int findMinArrowShots(int[][] points) {
if (points.length == 0) return 0;
Arrays.sort(points, (a, b) -> Integer.compare(a[1], b[1]));
int arrows = 1;
int end = points[0][1];
for (int i = 1; i < points.length; i++) {
if (points[i][0] > end) {
arrows++;
end = points[i][1];
}
}
return arrows;
}
贪心 vs 动态规划:
| 特性 | 贪心算法 | 动态规划 |
|---|---|---|
| 选择方式 | 局部最优 | 全局最优 |
| 计算方式 | 自顶向下 | 自底向上 |
| 适用条件 | 贪心选择性质 | 最优子结构 |
| 时间复杂度 | 通常较低 | 可能较高 |
| 正确性 | 需要证明 | 保证正确 |
7. 回溯算法(Backtracking)
核心思想:通过尝试所有可能的路径来找到解,如果当前路径不可行,则回退到上一步。
回溯算法模板:
java
public void backtrack(参数列表) {
// 终止条件
if (终止条件) {
保存结果;
return;
}
// 遍历所有可能的选择
for (选择 : 所有可能的选择) {
// 做选择
做出选择;
// 递归
backtrack(新参数);
// 撤销选择(回溯)
撤销选择;
}
}
经典问题:
全排列
java
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
List<Integer> path = new ArrayList<>();
boolean[] used = new boolean[nums.length];
backtrack(nums, used, path, result);
return result;
}
private void backtrack(int[] nums, boolean[] used,
List<Integer> path, List<List<Integer>> result) {
// 终止条件
if (path.size() == nums.length) {
result.add(new ArrayList<>(path));
return;
}
// 遍历所有可能的选择
for (int i = 0; i < nums.length; i++) {
if (used[i]) continue;
// 做选择
path.add(nums[i]);
used[i] = true;
// 递归
backtrack(nums, used, path, result);
// 撤销选择
path.remove(path.size() - 1);
used[i] = false;
}
}
N皇后问题
java
public List<List<String>> solveNQueens(int n) {
List<List<String>> result = new ArrayList<>();
char[][] board = new char[n][n];
for (char[] row : board) {
Arrays.fill(row, '.');
}
backtrack(board, 0, result);
return result;
}
private void backtrack(char[][] board, int row, List<List<String>> result) {
if (row == board.length) {
result.add(construct(board));
return;
}
for (int col = 0; col < board.length; col++) {
if (!isValid(board, row, col)) continue;
board[row][col] = 'Q';
backtrack(board, row + 1, result);
board[row][col] = '.';
}
}
private boolean isValid(char[][] board, int row, int col) {
// 检查列
for (int i = 0; i < row; i++) {
if (board[i][col] == 'Q') return false;
}
// 检查左上对角线
for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
if (board[i][j] == 'Q') return false;
}
// 检查右上对角线
for (int i = row - 1, j = col + 1; i >= 0 && j < board.length; i--, j++) {
if (board[i][j] == 'Q') return false;
}
return true;
}
组合总和
java
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> result = new ArrayList<>();
List<Integer> path = new ArrayList<>();
Arrays.sort(candidates);
backtrack(candidates, target, 0, path, result);
return result;
}
private void backtrack(int[] candidates, int target, int start,
List<Integer> path, List<List<Integer>> result) {
if (target == 0) {
result.add(new ArrayList<>(path));
return;
}
if (target < 0) return;
for (int i = start; i < candidates.length; i++) {
path.add(candidates[i]);
backtrack(candidates, target - candidates[i], i, path, result);
path.remove(path.size() - 1);
}
}
单词搜索
java
public boolean exist(char[][] board, String word) {
int m = board.length, n = board[0].length;
boolean[][] visited = new boolean[m][n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (backtrack(board, word, 0, i, j, visited)) {
return true;
}
}
}
return false;
}
private boolean backtrack(char[][] board, String word, int index,
int row, int col, boolean[][] visited) {
if (index == word.length()) return true;
if (row < 0 || row >= board.length || col < 0 || col >= board[0].length ||
visited[row][col] || board[row][col] != word.charAt(index)) {
return false;
}
visited[row][col] = true;
boolean found = backtrack(board, word, index + 1, row + 1, col, visited) ||
backtrack(board, word, index + 1, row - 1, col, visited) ||
backtrack(board, word, index + 1, row, col + 1, visited) ||
backtrack(board, word, index + 1, row, col - 1, visited);
visited[row][col] = false;
return found;
}
应用场景:
- 排列组合问题
- 棋盘类问题(N皇后、数独)
- 路径搜索问题
- 约束满足问题
应用系统常用算法
1. 哈希算法
一致性哈希(Consistent Hashing)
应用场景:分布式缓存、负载均衡
实现:
java
import java.util.*;
public class ConsistentHash {
// 虚拟节点数
private static final int VIRTUAL_NODES = 150;
// 哈希环:TreeMap保证有序
private final TreeMap<Long, String> hashRing = new TreeMap<>();
public ConsistentHash(List<String> servers) {
for (String server : servers) {
// 为每个物理节点创建虚拟节点
for (int i = 0; i < VIRTUAL_NODES; i++) {
String virtualNode = server + "#" + i;
long hash = hash(virtualNode);
hashRing.put(hash, server);
}
}
}
// 根据key获取服务器
public String getServer(String key) {
if (hashRing.isEmpty()) return null;
long hash = hash(key);
// 找到第一个大于等于hash的节点
Map.Entry<Long, String> entry = hashRing.ceilingEntry(hash);
if (entry == null) {
// 如果没有,返回第一个节点(环形)
entry = hashRing.firstEntry();
}
return entry.getValue();
}
// 添加服务器
public void addServer(String server) {
for (int i = 0; i < VIRTUAL_NODES; i++) {
String virtualNode = server + "#" + i;
long hash = hash(virtualNode);
hashRing.put(hash, server);
}
}
// 移除服务器
public void removeServer(String server) {
for (int i = 0; i < VIRTUAL_NODES; i++) {
String virtualNode = server + "#" + i;
long hash = hash(virtualNode);
hashRing.remove(hash);
}
}
// FNV-1a哈希算法
private long hash(String key) {
final int p = 16777619;
long hash = 2166136261L;
for (int i = 0; i < key.length(); i++) {
hash = (hash ^ key.charAt(i)) * p;
}
hash += hash << 13;
hash ^= hash >> 7;
hash += hash << 3;
hash ^= hash >> 17;
hash += hash << 5;
return hash < 0 ? Math.abs(hash) : hash;
}
}
布隆过滤器(Bloom Filter)
应用场景:缓存穿透防护、URL去重、垃圾邮件过滤
实现:
java
import java.util.BitSet;
public class BloomFilter {
private BitSet bitSet;
private int bitSetSize;
private int numHashFunctions;
private int numAdded;
public BloomFilter(int expectedInsertions, double falsePositiveRate) {
// 计算位数组大小:m = -n * ln(p) / (ln(2)^2)
this.bitSetSize = (int) (-expectedInsertions * Math.log(falsePositiveRate) /
(Math.log(2) * Math.log(2)));
// 计算哈希函数数量:k = (m/n) * ln(2)
this.numHashFunctions = (int) (bitSetSize / expectedInsertions * Math.log(2));
this.bitSet = new BitSet(bitSetSize);
this.numAdded = 0;
}
// 添加元素
public void add(String item) {
for (int i = 0; i < numHashFunctions; i++) {
int hash = hash(item, i);
bitSet.set(hash % bitSetSize, true);
}
numAdded++;
}
// 检查元素是否存在(可能误判,但不会漏判)
public boolean mightContain(String item) {
for (int i = 0; i < numHashFunctions; i++) {
int hash = hash(item, i);
if (!bitSet.get(hash % bitSetSize)) {
return false; // 肯定不存在
}
}
return true; // 可能存在
}
private int hash(String item, int seed) {
int hash = 0;
for (char c : item.toCharArray()) {
hash = hash * seed + c;
}
return Math.abs(hash);
}
}
2. 限流算法
令牌桶算法(Token Bucket)
应用场景:API限流、流量控制
实现:
java
import java.util.concurrent.atomic.AtomicLong;
public class TokenBucket {
private final long capacity; // 桶容量
private final long refillRate; // 每秒补充令牌数
private AtomicLong tokens; // 当前令牌数
private long lastRefillTime; // 上次补充时间
public TokenBucket(long capacity, long refillRate) {
this.capacity = capacity;
this.refillRate = refillRate;
this.tokens = new AtomicLong(capacity);
this.lastRefillTime = System.currentTimeMillis();
}
// 尝试获取令牌
public boolean tryAcquire(int permits) {
refill();
long currentTokens = tokens.get();
if (currentTokens >= permits) {
return tokens.compareAndSet(currentTokens, currentTokens - permits);
}
return false;
}
// 补充令牌
private synchronized void refill() {
long now = System.currentTimeMillis();
long elapsed = now - lastRefillTime;
if (elapsed > 0) {
// 计算需要补充的令牌数
long tokensToAdd = (elapsed * refillRate) / 1000;
if (tokensToAdd > 0) {
long currentTokens = tokens.get();
long newTokens = Math.min(capacity, currentTokens + tokensToAdd);
tokens.set(newTokens);
lastRefillTime = now;
}
}
}
}
滑动窗口限流
实现:
java
import java.util.concurrent.ConcurrentLinkedQueue;
public class SlidingWindowRateLimiter {
private final int maxRequests; // 窗口内最大请求数
private final long windowSizeMs; // 窗口大小(毫秒)
private final ConcurrentLinkedQueue<Long> requests; // 请求时间戳队列
public SlidingWindowRateLimiter(int maxRequests, long windowSizeMs) {
this.maxRequests = maxRequests;
this.windowSizeMs = windowSizeMs;
this.requests = new ConcurrentLinkedQueue<>();
}
public boolean allow() {
long now = System.currentTimeMillis();
long windowStart = now - windowSizeMs;
// 移除窗口外的请求
while (!requests.isEmpty() && requests.peek() < windowStart) {
requests.poll();
}
// 检查是否超过限制
if (requests.size() < maxRequests) {
requests.offer(now);
return true;
}
return false;
}
}
3. LRU缓存算法
应用场景:缓存系统、页面置换
实现:
java
import java.util.*;
public class LRUCache<K, V> {
class Node {
K key;
V value;
Node prev;
Node next;
Node(K key, V value) {
this.key = key;
this.value = value;
}
}
private final int capacity;
private final Map<K, Node> cache;
private final Node head; // 虚拟头节点
private final Node tail; // 虚拟尾节点
public LRUCache(int capacity) {
this.capacity = capacity;
this.cache = new HashMap<>();
this.head = new Node(null, null);
this.tail = new Node(null, null);
head.next = tail;
tail.prev = head;
}
public V get(K key) {
Node node = cache.get(key);
if (node == null) return null;
// 移动到头部
moveToHead(node);
return node.value;
}
public void put(K key, V value) {
Node node = cache.get(key);
if (node != null) {
// 更新值并移动到头部
node.value = value;
moveToHead(node);
} else {
// 新建节点
node = new Node(key, value);
cache.put(key, node);
addToHead(node);
// 如果超过容量,删除尾部节点
if (cache.size() > capacity) {
Node last = removeTail();
cache.remove(last.key);
}
}
}
private void addToHead(Node node) {
node.prev = head;
node.next = head.next;
head.next.prev = node;
head.next = node;
}
private void removeNode(Node node) {
node.prev.next = node.next;
node.next.prev = node.prev;
}
private void moveToHead(Node node) {
removeNode(node);
addToHead(node);
}
private Node removeTail() {
Node last = tail.prev;
removeNode(last);
return last;
}
}
4. TopK算法
应用场景:热门商品、热搜词、排行榜
实现:
java
// 方法1:堆排序 O(n log k)
public int[] topKByHeap(int[] nums, int k) {
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
for (int num : nums) {
if (minHeap.size() < k) {
minHeap.offer(num);
} else if (num > minHeap.peek()) {
minHeap.poll();
minHeap.offer(num);
}
}
int[] result = new int[k];
for (int i = k - 1; i >= 0; i--) {
result[i] = minHeap.poll();
}
return result;
}
// 方法2:快速选择 O(n) 平均时间复杂度
public int[] topKByQuickSelect(int[] nums, int k) {
quickSelect(nums, 0, nums.length - 1, k);
int[] result = new int[k];
System.arraycopy(nums, nums.length - k, result, 0, k);
Arrays.sort(result);
return result;
}
private void quickSelect(int[] nums, int left, int right, int k) {
if (left >= right) return;
int pivot = partition(nums, left, right);
int rightSize = right - pivot + 1;
if (rightSize == k) return;
else if (rightSize > k) {
quickSelect(nums, pivot + 1, right, k);
} else {
quickSelect(nums, left, pivot - 1, k - rightSize);
}
}
5. 并查集(Union-Find)
应用场景:朋友圈、连通性问题、最小生成树
实现:
java
public class UnionFind {
private int[] parent;
private int[] rank; // 用于路径压缩优化
public UnionFind(int n) {
parent = new int[n];
rank = new int[n];
for (int i = 0; i < n; i++) {
parent[i] = i;
rank[i] = 0;
}
}
// 查找根节点(路径压缩)
public int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]); // 路径压缩
}
return parent[x];
}
// 合并两个集合(按秩合并)
public void union(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX == rootY) return;
// 按秩合并:将秩小的树合并到秩大的树下
if (rank[rootX] < rank[rootY]) {
parent[rootX] = rootY;
} else if (rank[rootX] > rank[rootY]) {
parent[rootY] = rootX;
} else {
parent[rootY] = rootX;
rank[rootX]++;
}
}
// 判断是否连通
public boolean connected(int x, int y) {
return find(x) == find(y);
}
}
6. 字符串匹配算法
KMP算法
实现:
java
public class KMP {
// 查找pattern在text中的位置
public int search(String text, String pattern) {
int[] next = buildNext(pattern);
int i = 0, j = 0;
while (i < text.length() && j < pattern.length()) {
if (j == -1 || text.charAt(i) == pattern.charAt(j)) {
i++;
j++;
} else {
j = next[j];
}
}
return j == pattern.length() ? i - j : -1;
}
// 构建next数组(部分匹配表)
private int[] buildNext(String pattern) {
int[] next = new int[pattern.length()];
next[0] = -1;
int i = 0, j = -1;
while (i < pattern.length() - 1) {
if (j == -1 || pattern.charAt(i) == pattern.charAt(j)) {
i++;
j++;
next[i] = j;
} else {
j = next[j];
}
}
return next;
}
}
7. 分布式ID生成算法(雪花算法)
应用场景:分布式系统唯一ID生成
实现:
java
public class SnowflakeIdGenerator {
// 时间戳起始点(2020-01-01)
private static final long EPOCH = 1577836800000L;
// 各部分占用位数
private static final long WORKER_ID_BITS = 5L;
private static final long DATACENTER_ID_BITS = 5L;
private static final long SEQUENCE_BITS = 12L;
// 最大值
private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);
private static final long MAX_DATACENTER_ID = ~(-1L << DATACENTER_ID_BITS);
private static final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BITS);
// 位移
private static final long WORKER_ID_SHIFT = SEQUENCE_BITS;
private static final long DATACENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;
private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATACENTER_ID_BITS;
private long workerId;
private long datacenterId;
private long sequence = 0L;
private long lastTimestamp = -1L;
public SnowflakeIdGenerator(long workerId, long datacenterId) {
if (workerId > MAX_WORKER_ID || workerId < 0) {
throw new IllegalArgumentException("workerId must be between 0 and " + MAX_WORKER_ID);
}
if (datacenterId > MAX_DATACENTER_ID || datacenterId < 0) {
throw new IllegalArgumentException("datacenterId must be between 0 and " + MAX_DATACENTER_ID);
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}
public synchronized long nextId() {
long timestamp = System.currentTimeMillis();
if (timestamp < lastTimestamp) {
throw new RuntimeException("Clock moved backwards");
}
if (timestamp == lastTimestamp) {
sequence = (sequence + 1) & MAX_SEQUENCE;
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
return ((timestamp - EPOCH) << TIMESTAMP_SHIFT) |
(datacenterId << DATACENTER_ID_SHIFT) |
(workerId << WORKER_ID_SHIFT) |
sequence;
}
private long tilNextMillis(long lastTimestamp) {
long timestamp = System.currentTimeMillis();
while (timestamp <= lastTimestamp) {
timestamp = System.currentTimeMillis();
}
return timestamp;
}
}
8. 负载均衡算法
加权轮询(Weighted Round Robin)
实现:
java
import java.util.*;
public class WeightedRoundRobin {
static class Server {
String name;
int weight;
int currentWeight;
Server(String name, int weight) {
this.name = name;
this.weight = weight;
this.currentWeight = 0;
}
}
private List<Server> servers;
public WeightedRoundRobin(List<Server> servers) {
this.servers = new ArrayList<>(servers);
}
public Server select() {
Server selected = null;
int totalWeight = 0;
for (Server server : servers) {
server.currentWeight += server.weight;
totalWeight += server.weight;
if (selected == null || server.currentWeight > selected.currentWeight) {
selected = server;
}
}
if (selected != null) {
selected.currentWeight -= totalWeight;
}
return selected;
}
}
游戏主程常用算法
1. A*寻路算法
应用场景:游戏AI寻路、路径规划
实现:
java
import java.util.*;
public class AStar {
static class Node {
int x, y;
int g; // 从起点到当前节点的实际代价
int h; // 从当前节点到终点的启发式估计代价
int f; // f = g + h
Node parent;
Node(int x, int y) {
this.x = x;
this.y = y;
}
int getF() {
return g + h;
}
}
public List<Node> findPath(int[][] grid, Node start, Node end) {
PriorityQueue<Node> openList = new PriorityQueue<>(
Comparator.comparingInt(Node::getF)
);
Set<String> closedSet = new HashSet<>();
Map<String, Node> openMap = new HashMap<>();
start.g = 0;
start.h = heuristic(start, end);
openList.offer(start);
openMap.put(key(start), start);
int[][] directions = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
while (!openList.isEmpty()) {
Node current = openList.poll();
openMap.remove(key(current));
closedSet.add(key(current));
if (current.x == end.x && current.y == end.y) {
return reconstructPath(current);
}
for (int[] dir : directions) {
int newX = current.x + dir[0];
int newY = current.y + dir[1];
if (newX < 0 || newX >= grid.length ||
newY < 0 || newY >= grid[0].length ||
grid[newX][newY] == 1) { // 1表示障碍物
continue;
}
String neighborKey = key(newX, newY);
if (closedSet.contains(neighborKey)) continue;
Node neighbor = new Node(newX, newY);
int tentativeG = current.g + 1;
Node existing = openMap.get(neighborKey);
if (existing == null || tentativeG < existing.g) {
neighbor.g = tentativeG;
neighbor.h = heuristic(neighbor, end);
neighbor.parent = current;
if (existing == null) {
openList.offer(neighbor);
openMap.put(neighborKey, neighbor);
}
}
}
}
return new ArrayList<>(); // 无路径
}
// 曼哈顿距离作为启发式函数
private int heuristic(Node a, Node b) {
return Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
}
private List<Node> reconstructPath(Node node) {
List<Node> path = new ArrayList<>();
while (node != null) {
path.add(node);
node = node.parent;
}
Collections.reverse(path);
return path;
}
private String key(Node node) {
return key(node.x, node.y);
}
private String key(int x, int y) {
return x + "," + y;
}
}
2. 碰撞检测算法
AABB(轴对齐包围盒)
实现:
java
public class AABB {
float minX, minY, maxX, maxY;
public boolean intersects(AABB other) {
return !(this.maxX < other.minX || this.minX > other.maxX ||
this.maxY < other.minY || this.minY > other.maxY);
}
public boolean contains(float x, float y) {
return x >= minX && x <= maxX && y >= minY && y <= maxY;
}
}
3. 对象池算法
应用场景:频繁创建销毁的对象(子弹、粒子、特效)
实现:
java
import java.util.*;
import java.util.function.Supplier;
import java.util.function.Consumer;
public class ObjectPool<T> {
private final Queue<T> pool;
private final Supplier<T> factory;
private final Consumer<T> reset;
private final int maxSize;
public ObjectPool(Supplier<T> factory, Consumer<T> reset, int maxSize) {
this.factory = factory;
this.reset = reset;
this.maxSize = maxSize;
this.pool = new LinkedList<>();
}
// 获取对象
public T acquire() {
T obj = pool.poll();
if (obj == null) {
obj = factory.get();
}
return obj;
}
// 归还对象
public void release(T obj) {
if (pool.size() < maxSize) {
reset.accept(obj);
pool.offer(obj);
}
}
}
4. 平滑插值算法
实现:
java
public class Interpolation {
// 线性插值
public static float lerp(float a, float b, float t) {
return a + (b - a) * t;
}
// 平滑插值(缓入缓出)
public static float smoothStep(float a, float b, float t) {
t = t * t * (3.0f - 2.0f * t); // smoothstep函数
return lerp(a, b, t);
}
// 球面线性插值(用于旋转)
public static float slerp(float a, float b, float t) {
// 简化版本,实际需要处理角度环绕
return lerp(a, b, t);
}
}
5. 四叉树(Quadtree)
应用场景:空间分区、碰撞检测优化、LOD(细节层次)
实现:
java
import java.util.*;
public class Quadtree {
static class Rectangle {
float x, y, width, height;
Rectangle(float x, float y, float width, float height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
boolean contains(Point point) {
return point.x >= x && point.x < x + width &&
point.y >= y && point.y < y + height;
}
boolean intersects(Rectangle other) {
return !(x + width < other.x || other.x + other.width < x ||
y + height < other.y || other.y + other.height < y);
}
}
static class Point {
float x, y;
Object data;
Point(float x, float y, Object data) {
this.x = x;
this.y = y;
this.data = data;
}
}
private static final int MAX_OBJECTS = 4;
private static final int MAX_LEVELS = 5;
private int level;
private List<Point> objects;
private Rectangle bounds;
private Quadtree[] nodes;
public Quadtree(int level, Rectangle bounds) {
this.level = level;
this.bounds = bounds;
this.objects = new ArrayList<>();
this.nodes = new Quadtree[4];
}
// 插入点
public void insert(Point point) {
if (!bounds.contains(point)) return;
if (objects.size() < MAX_OBJECTS || level >= MAX_LEVELS) {
objects.add(point);
return;
}
if (nodes[0] == null) {
split();
}
for (Quadtree node : nodes) {
node.insert(point);
}
}
// 查询范围内的点
public List<Point> query(Rectangle range) {
List<Point> results = new ArrayList<>();
if (!bounds.intersects(range)) {
return results;
}
for (Point point : objects) {
if (range.contains(point)) {
results.add(point);
}
}
if (nodes[0] != null) {
for (Quadtree node : nodes) {
results.addAll(node.query(range));
}
}
return results;
}
// 分割四叉树
private void split() {
float subWidth = bounds.width / 2;
float subHeight = bounds.height / 2;
float x = bounds.x;
float y = bounds.y;
nodes[0] = new Quadtree(level + 1,
new Rectangle(x + subWidth, y, subWidth, subHeight));
nodes[1] = new Quadtree(level + 1,
new Rectangle(x, y, subWidth, subHeight));
nodes[2] = new Quadtree(level + 1,
new Rectangle(x, y + subHeight, subWidth, subHeight));
nodes[3] = new Quadtree(level + 1,
new Rectangle(x + subWidth, y + subHeight, subWidth, subHeight));
}
}
6. 伪随机数生成器
应用场景:游戏随机事件、地图生成、掉落系统
实现:
java
// 线性同余生成器(LCG)
public class PRNG {
private long seed;
private static final long MULTIPLIER = 1103515245L;
private static final long INCREMENT = 12345L;
private static final long MODULUS = (1L << 31);
public PRNG(long seed) {
this.seed = seed;
}
// 生成0到1之间的随机数
public double nextDouble() {
seed = (MULTIPLIER * seed + INCREMENT) % MODULUS;
return (double) seed / MODULUS;
}
// 生成指定范围的随机整数
public int nextInt(int min, int max) {
return min + (int) (nextDouble() * (max - min));
}
// 设置种子(用于可重现的随机序列)
public void setSeed(long seed) {
this.seed = seed;
}
}
7. 地图生成算法(Perlin噪声简化版)
应用场景:程序化生成地形、纹理
实现:
java
public class PerlinNoise {
private static final int[] permutation = {
151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225,
// ... 更多随机排列
};
private static final int[] p = new int[512];
static {
for (int i = 0; i < 256; i++) {
p[256 + i] = p[i] = permutation[i % permutation.length];
}
}
// 简化版Perlin噪声
public static double noise(double x, double y) {
int X = (int) Math.floor(x) & 255;
int Y = (int) Math.floor(y) & 255;
double fx = x - Math.floor(x);
double fy = y - Math.floor(y);
double u = fade(fx);
double v = fade(fy);
int a = p[X] + Y;
int b = p[X + 1] + Y;
return lerp(v,
lerp(u, grad(p[a], fx, fy), grad(p[b], fx - 1, fy)),
lerp(u, grad(p[a + 1], fx, fy - 1), grad(p[b + 1], fx - 1, fy - 1))
);
}
private static double fade(double t) {
return t * t * t * (t * (t * 6 - 15) + 10);
}
private static double lerp(double t, double a, double b) {
return a + t * (b - a);
}
private static double grad(int hash, double x, double y) {
int h = hash & 3;
return ((h & 1) == 0 ? x : -x) + ((h & 2) == 0 ? y : -y);
}
}
面试高频算法题
1. 链表相关
反转链表
java
// 迭代方式
public ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode curr = head;
while (curr != null) {
ListNode next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev;
}
// 递归方式
public ListNode reverseListRecursive(ListNode head) {
if (head == null || head.next == null) return head;
ListNode newHead = reverseListRecursive(head.next);
head.next.next = head;
head.next = null;
return newHead;
}
合并K个有序链表
java
public ListNode mergeKLists(ListNode[] lists) {
if (lists == null || lists.length == 0) return null;
PriorityQueue<ListNode> pq = new PriorityQueue<>(
Comparator.comparingInt(a -> a.val)
);
for (ListNode node : lists) {
if (node != null) pq.offer(node);
}
ListNode dummy = new ListNode(0);
ListNode curr = dummy;
while (!pq.isEmpty()) {
ListNode node = pq.poll();
curr.next = node;
curr = curr.next;
if (node.next != null) {
pq.offer(node.next);
}
}
return dummy.next;
}
链表相交
java
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode pA = headA, pB = headB;
while (pA != pB) {
pA = pA == null ? headB : pA.next;
pB = pB == null ? headA : pB.next;
}
return pA;
}
2. 二叉树相关
二叉树最大深度
java
public int maxDepth(TreeNode root) {
if (root == null) return 0;
return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
}
验证二叉搜索树
java
public boolean isValidBST(TreeNode root) {
return validate(root, Long.MIN_VALUE, Long.MAX_VALUE);
}
private boolean validate(TreeNode node, long min, long max) {
if (node == null) return true;
if (node.val <= min || node.val >= max) return false;
return validate(node.left, min, node.val) &&
validate(node.right, node.val, max);
}
路径总和
java
public boolean hasPathSum(TreeNode root, int targetSum) {
if (root == null) return false;
if (root.left == null && root.right == null) {
return root.val == targetSum;
}
return hasPathSum(root.left, targetSum - root.val) ||
hasPathSum(root.right, targetSum - root.val);
}
3. 数组/字符串相关
接雨水
java
public int trap(int[] height) {
int left = 0, right = height.length - 1;
int leftMax = 0, rightMax = 0;
int water = 0;
while (left < right) {
if (height[left] < height[right]) {
if (height[left] >= leftMax) {
leftMax = height[left];
} else {
water += leftMax - height[left];
}
left++;
} else {
if (height[right] >= rightMax) {
rightMax = height[right];
} else {
water += rightMax - height[right];
}
right--;
}
}
return water;
}
最长回文子串
java
public String longestPalindrome(String s) {
if (s == null || s.length() < 1) return "";
int start = 0, end = 0;
for (int i = 0; i < s.length(); i++) {
int len1 = expandAroundCenter(s, i, i);
int len2 = expandAroundCenter(s, i, i + 1);
int len = Math.max(len1, len2);
if (len > end - start) {
start = i - (len - 1) / 2;
end = i + len / 2;
}
}
return s.substring(start, end + 1);
}
private int expandAroundCenter(String s, int left, int right) {
while (left >= 0 && right < s.length() &&
s.charAt(left) == s.charAt(right)) {
left--;
right++;
}
return right - left - 1;
}
编辑距离
java
public int minDistance(String word1, String word2) {
int m = word1.length(), n = word2.length();
int[][] dp = new int[m + 1][n + 1];
for (int i = 0; i <= m; i++) dp[i][0] = i;
for (int j = 0; j <= n; j++) dp[0][j] = j;
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = 1 + Math.min(
dp[i - 1][j], // 删除
Math.min(
dp[i][j - 1], // 插入
dp[i - 1][j - 1] // 替换
)
);
}
}
}
return dp[m][n];
}
4. 动态规划经典题
打家劫舍
java
public int rob(int[] nums) {
if (nums.length == 0) return 0;
if (nums.length == 1) return nums[0];
int[] dp = new int[nums.length];
dp[0] = nums[0];
dp[1] = Math.max(nums[0], nums[1]);
for (int i = 2; i < nums.length; i++) {
dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]);
}
return dp[nums.length - 1];
}
// 空间优化版本
public int robOptimized(int[] nums) {
int prev2 = 0, prev1 = 0;
for (int num : nums) {
int temp = prev1;
prev1 = Math.max(prev1, prev2 + num);
prev2 = temp;
}
return prev1;
}
零钱兑换
java
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount + 1];
Arrays.fill(dp, amount + 1);
dp[0] = 0;
for (int i = 1; i <= amount; i++) {
for (int coin : coins) {
if (coin <= i) {
dp[i] = Math.min(dp[i], dp[i - coin] + 1);
}
}
}
return dp[amount] > amount ? -1 : dp[amount];
}
6. 图算法相关
岛屿数量
java
public int numIslands(char[][] grid) {
if (grid == null || grid.length == 0) return 0;
int m = grid.length, n = grid[0].length;
int count = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == '1') {
dfs(grid, i, j);
count++;
}
}
}
return count;
}
private void dfs(char[][] grid, int i, int j) {
if (i < 0 || i >= grid.length || j < 0 || j >= grid[0].length ||
grid[i][j] == '0') {
return;
}
grid[i][j] = '0'; // 标记为已访问
dfs(grid, i + 1, j);
dfs(grid, i - 1, j);
dfs(grid, i, j + 1);
dfs(grid, i, j - 1);
}
合并区间
java
public int[][] merge(int[][] intervals) {
if (intervals.length <= 1) return intervals;
// 按起始位置排序
Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0]));
List<int[]> result = new ArrayList<>();
int[] current = intervals[0];
result.add(current);
for (int[] interval : intervals) {
if (interval[0] <= current[1]) {
// 重叠,合并
current[1] = Math.max(current[1], interval[1]);
} else {
// 不重叠,添加新区间
current = interval;
result.add(current);
}
}
return result.toArray(new int[result.size()][]);
}
括号匹配
java
public boolean isValid(String s) {
Stack<Character> stack = new Stack<>();
for (char c : s.toCharArray()) {
if (c == '(' || c == '[' || c == '{') {
stack.push(c);
} else {
if (stack.isEmpty()) return false;
char top = stack.pop();
if ((c == ')' && top != '(') ||
(c == ']' && top != '[') ||
(c == '}' && top != '{')) {
return false;
}
}
}
return stack.isEmpty();
}
7. 字符串处理
字符串转整数(atoi)
java
public int myAtoi(String s) {
if (s == null || s.length() == 0) return 0;
int i = 0;
// 跳过空格
while (i < s.length() && s.charAt(i) == ' ') i++;
if (i >= s.length()) return 0;
// 判断符号
int sign = 1;
if (s.charAt(i) == '+' || s.charAt(i) == '-') {
sign = s.charAt(i) == '-' ? -1 : 1;
i++;
}
long result = 0;
while (i < s.length() && Character.isDigit(s.charAt(i))) {
result = result * 10 + (s.charAt(i) - '0');
if (sign * result > Integer.MAX_VALUE) {
return Integer.MAX_VALUE;
}
if (sign * result < Integer.MIN_VALUE) {
return Integer.MIN_VALUE;
}
i++;
}
return (int) (sign * result);
}
最长公共前缀
java
public String longestCommonPrefix(String[] strs) {
if (strs == null || strs.length == 0) return "";
String prefix = strs[0];
for (int i = 1; i < strs.length; i++) {
while (!strs[i].startsWith(prefix)) {
prefix = prefix.substring(0, prefix.length() - 1);
if (prefix.isEmpty()) return "";
}
}
return prefix;
}
8. 数学算法
快速幂
java
public long fastPower(long base, long exponent, long mod) {
long result = 1;
base = base % mod;
while (exponent > 0) {
if (exponent % 2 == 1) {
result = (result * base) % mod;
}
exponent = exponent >> 1;
base = (base * base) % mod;
}
return result;
}
最大公约数(欧几里得算法)
java
// 递归版本
public int gcd(int a, int b) {
if (b == 0) return a;
return gcd(b, a % b);
}
// 迭代版本
public int gcdIterative(int a, int b) {
while (b != 0) {
int temp = b;
b = a % b;
a = temp;
}
return a;
}
判断质数
java
public boolean isPrime(int n) {
if (n < 2) return false;
if (n == 2) return true;
if (n % 2 == 0) return false;
for (int i = 3; i * i <= n; i += 2) {
if (n % i == 0) return false;
}
return true;
}
5. 位运算技巧
java
// 判断是否为2的幂
public boolean isPowerOfTwo(int n) {
return n > 0 && (n & (n - 1)) == 0;
}
// 计算1的个数
public int countBits(int n) {
int count = 0;
while (n != 0) {
n &= (n - 1); // 清除最低位的1
count++;
}
return count;
}
// 只出现一次的数字(其他都出现两次)
public int singleNumber(int[] nums) {
int result = 0;
for (int num : nums) {
result ^= num; // 异或运算
}
return result;
}
高频面试问答(深度解析)
1. 时间复杂度和空间复杂度?
标准答案:
- 时间复杂度:算法执行时间随输入规模增长的趋势
- 空间复杂度:算法所需内存空间随输入规模增长的趋势
- 常见复杂度:O(1)、O(log n)、O(n)、O(n log n)、O(n²)、O(2ⁿ)
深入追问与回答思路:
Q: 如何分析复杂度?
java
// O(1):常数时间
int get(int[] arr, int index) {
return arr[index];
}
// O(n):线性时间
int sum(int[] arr) {
int sum = 0;
for (int num : arr) {
sum += num;
}
return sum;
}
// O(n²):平方时间
void bubbleSort(int[] arr) {
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr.length - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
swap(arr, j, j + 1);
}
}
}
}
2. 如何选择合适的数据结构?
标准答案:
- 查找频繁:HashMap、TreeMap
- 插入删除频繁:LinkedList
- 需要排序:TreeSet、PriorityQueue
- 需要去重:HashSet
深入追问与回答思路:
Q: 实际应用场景?
java
// 场景1:用户ID查找用户信息
// 选择:HashMap(O(1)查找)
Map<Long, User> userMap = new HashMap<>();
// 场景2:维护有序列表
// 选择:TreeSet(自动排序)
TreeSet<Integer> sortedSet = new TreeSet<>();
// 场景3:任务优先级队列
// 选择:PriorityQueue(堆实现)
PriorityQueue<Task> queue = new PriorityQueue<>(
Comparator.comparing(Task::getPriority)
);
3. 如何优化算法性能?
标准答案:
- 减少时间复杂度:选择更优算法
- 减少空间复杂度:原地算法、空间复用
- 缓存结果:记忆化搜索
- 并行处理:多线程、分治
深入追问与回答思路:
Q: 优化示例?
java
// 优化前:O(n²)
public int[] twoSum(int[] nums, int target) {
for (int i = 0; i < nums.length; i++) {
for (int j = i + 1; j < nums.length; j++) {
if (nums[i] + nums[j] == target) {
return new int[]{i, j};
}
}
}
return null;
}
// 优化后:O(n)
public int[] twoSumOptimized(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int complement = target - nums[i];
if (map.containsKey(complement)) {
return new int[]{map.get(complement), i};
}
map.put(nums[i], i);
}
return null;
}
延伸阅读
- 《算法导论》《数据结构与算法分析》
- LeetCode、牛客网算法练习
- 《编程珠玑》《算法竞赛进阶指南》
发布建议:拆分为"数据结构基础""排序与查找""动态规划与贪心""面试算法题"四篇文章,每篇附代码示例和复杂度分析。