Java常见算法题深度解析
Java常见算法题深度解析
掌握这些算法,面试更轻松
前言
在Java后端面试中,算法题是必考环节。本文整理了最常考的10类算法题,从基础到进阶,帮助你快速掌握面试所需算法知识。
一、双指针算法
1. 两数之和
题目描述: 给定一个整数数组 nums 和一个目标值 target,在数组中找出和为目标值的两个整数,并返回它们的数组下标。
解题思路:
- 使用哈希表存储已遍历的数字和下标
- 遍历数组时,检查 target - nums[i] 是否在哈希表中
- 时间复杂度:O(n),空间复杂度:O(n)
代码实现:
public int[] twoSum(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 new int[]{};
}
2. 盛最多水的容器
题目描述: 给定 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai)。在坐标内画 n 条垂直线,找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
解题思路:
- 使用左右双指针
- 容器容量 = min(left, right) * (right - left)
- 每次移动较小值的那一端
- 时间复杂度:O(n),空间复杂度:O(1)
代码实现:
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;
}
二、滑动窗口
3. 无重复字符的最长子串
题目描述: 给定一个字符串 s,请你找出其中不含有重复字符的最长子串的长度。
解题思路:
- 使用滑动窗口和哈希表
- 维护一个窗口,记录字符最后出现的位置
- 遇到重复字符时,移动左边界
- 时间复杂度:O(n),空间复杂度:O(min(m, n)),m为字符集大小
代码实现:
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) && map.get(c) >= left) {
left = map.get(c) + 1;
}
map.put(c, right);
maxLen = Math.max(maxLen, right - left + 1);
}
return maxLen;
}
4. 长度最小的子数组
题目描述: 给定一个含有 n 个正整数的数组和一个正整数 target,找出该数组中满足其和 ≥ target 的长度最小的连续子数组,并返回其长度。
解题思路:
- 使用滑动窗口
- 窗口和小于target时,扩展右边界
- 窗口和≥target时,记录长度并收缩左边界
- 时间复杂度:O(n),空间复杂度:O(1)
代码实现:
public int minSubArrayLen(int target, int[] nums) {
int left = 0, sum = 0, minLen = Integer.MAX_VALUE;
for (int right = 0; right < nums.length; right++) {
sum += nums[right];
while (sum >= target) {
minLen = Math.min(minLen, right - left + 1);
sum -= nums[left];
left++;
}
}
return minLen == Integer.MAX_VALUE ? 0 : minLen;
}
三、二分查找
5. 搜索旋转排序数组
题目描述: 升序排列的整数数组 nums 在预先未知的某个点上进行了旋转,编写函数在数组中搜索目标值,如果存在返回下标,否则返回 -1。
解题思路:
- 先判断哪部分是有序的
- 判断目标值是否在有序部分
- 逐步缩小搜索范围
- 时间复杂度:O(log n),空间复杂度:O(1)
代码实现:
public int search(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;
}
四、链表算法
6. 反转链表
题目描述: 给你单链表的头节点 head,请你反转链表,并返回反转后的链表。
解题思路:
- 使用三个指针:prev、curr、next
- 逐个反转节点指向
- 时间复杂度:O(n),空间复杂度:O(1)
代码实现:
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;
}
7. 合并两个有序链表
题目描述: 将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
解题思路:
- 使用虚拟头节点简化操作
- 依次比较两个链表节点,较小的链接到新链表
- 时间复杂度:O(m + n),空间复杂度:O(1)
代码实现:
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode dummy = new ListNode(-1);
ListNode curr = dummy;
while (list1 != null && list2 != null) {
if (list1.val <= list2.val) {
curr.next = list1;
list1 = list1.next;
} else {
curr.next = list2;
list2 = list2.next;
}
curr = curr.next;
}
curr.next = list1 != null ? list1 : list2;
return dummy.next;
}
五、动态规划
8. 爬楼梯
题目描述: 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
解题思路:
- 状态转移方程:dp[i] = dp[i-1] + dp[i-2]
- 可以优化为只用两个变量
- 时间复杂度:O(n),空间复杂度:O(1)
代码实现:
public int climbStairs(int n) {
if (n <= 2) return n;
int first = 1, second = 2;
for (int i = 3; i <= n; i++) {
int third = first + second;
first = second;
second = third;
}
return second;
}
9. 零钱兑换
题目描述: 给你一个整数数组 coins,表示不同面额的硬币;以及一个整数 amount,表示总金额。计算并返回可以凑成总金额所需的最少硬币个数。
解题思路:
- dp[i] 表示凑成金额 i 的最少硬币数
- 状态转移方程:dp[i] = min(dp[i], dp[i - coin] + 1)
- 时间复杂度:O(m * n),空间复杂度:O(n)
代码实现:
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];
}
六、回溯算法
10. 全排列
题目描述: 给定一个不含重复数字的数组 nums,返回其所有可能的全排列。
解题思路:
- 使用回溯法
- 每次选择一个未使用的数字
- 递归处理剩余数字
- 时间复杂度:O(n * n!),空间复杂度:O(n)
代码实现:
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
backtrack(result, new ArrayList<>(), nums);
return result;
}
private void backtrack(List<List<Integer>> result, List<Integer> tempList, int[] nums) {
if (tempList.size() == nums.length) {
result.add(new ArrayList<>(tempList));
return;
}
for (int i = 0; i < nums.length; i++) {
if (tempList.contains(nums[i])) continue;
tempList.add(nums[i]);
backtrack(result, tempList, nums);
tempList.remove(tempList.size() - 1);
}
}
七、BFS和DFS
11. 二叉树的层序遍历
题目描述: 给你一个二叉树,请你返回其按层序遍历得到的节点值(即逐层地,从左到右访问所有节点)。
解题思路:
- 使用队列进行BFS
- 每次处理当前层的所有节点
- 时间复杂度:O(n),空间复杂度:O(n)
代码实现:
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> result = new ArrayList<>();
if (root == null) return result;
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);
}
result.add(level);
}
return result;
}
12. 二叉树的最大深度
题目描述: 给定一个二叉树,找出其最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
解题思路:
- 使用DFS递归
- 深度 = max(左子树深度, 右子树深度) + 1
- 时间复杂度:O(n),空间复杂度:O(h),h为树高度
代码实现:
public int maxDepth(TreeNode root) {
if (root == null) return 0;
return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
}
八、排序算法
13. 快速排序
题目描述: 实现快速排序算法,对数组进行升序排序。
解题思路:
- 选择一个基准元素
- 将小于基准的放左边,大于基准的放右边
- 递归排序左右两部分
- 时间复杂度:平均 O(n log n),最坏 O(n²),空间复杂度:O(log n)
代码实现:
public void quickSort(int[] nums, int left, int right) {
if (left >= right) return;
int pivot = partition(nums, left, right);
quickSort(nums, left, pivot - 1);
quickSort(nums, pivot + 1, right);
}
private int partition(int[] nums, int left, int right) {
int pivot = nums[right];
int i = left;
for (int j = left; j < right; j++) {
if (nums[j] < pivot) {
swap(nums, i, j);
i++;
}
}
swap(nums, i, right);
return i;
}
private void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
九、贪心算法
14. 跳跃游戏
题目描述: 给定一个非负整数数组 nums,你最初位于数组的第一个下标。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个下标。
解题思路:
- 维护一个可以到达的最远位置
- 遍历数组,更新最远位置
- 如果最远位置 >= 最后一个下标,返回true
- 时间复杂度:O(n),空间复杂度:O(1)
代码实现:
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;
}
十、堆算法
15. 数组中的第K个最大元素
题目描述: 给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。
解题思路:
- 使用最小堆,维护大小为k的堆
- 堆顶就是第k大的元素
- 时间复杂度:O(n log k),空间复杂度:O(k)
代码实现:
public int findKthLargest(int[] nums, int k) {
PriorityQueue<Integer> minHeap = new PriorityQueue<>(k);
for (int num : nums) {
if (minHeap.size() < k) {
minHeap.offer(num);
} else if (num > minHeap.peek()) {
minHeap.poll();
minHeap.offer(num);
}
}
return minHeap.peek();
}
总结
掌握这些算法题,基本可以应对大部分面试场景。建议:
- 理解算法原理:不要死记硬背,理解每个算法的核心思想
- 多动手实践:在LeetCode上多刷题,保持手感
- 分析时间复杂度:养成分析算法复杂度的习惯
- 举一反三:学会将类似问题归为一类,掌握通用解法
面试技巧:
- 先说思路,再写代码
- 注意边界条件处理
- 分析时间和空间复杂度
- 如果时间不够,先写伪代码
希望这篇文章对你有帮助,祝面试顺利!

浙公网安备 33010602011771号