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();
}

总结

掌握这些算法题,基本可以应对大部分面试场景。建议:

  1. 理解算法原理:不要死记硬背,理解每个算法的核心思想
  2. 多动手实践:在LeetCode上多刷题,保持手感
  3. 分析时间复杂度:养成分析算法复杂度的习惯
  4. 举一反三:学会将类似问题归为一类,掌握通用解法

面试技巧:
- 先说思路,再写代码
- 注意边界条件处理
- 分析时间和空间复杂度
- 如果时间不够,先写伪代码

希望这篇文章对你有帮助,祝面试顺利!

posted @ 2026-03-05 18:56  寒人病酒  阅读(0)  评论(0)    收藏  举报