杰瑞科技汇

LeetCode Java题解,如何高效解题?

Java 解题核心思路

解题通常遵循以下四个步骤:

LeetCode Java题解,如何高效解题?-图1
(图片来源网络,侵删)
  1. 理解题意

    • 输入/输出: 明确函数的参数类型和返回值类型。
    • 约束条件: 注意题目给出的数据范围(如数组长度、数值大小),这决定了你选择的算法的时间/空间复杂度是否可行。
    • 边界条件: 思考特殊情况,如空数组、单个元素、所有元素相同等。
  2. 确定数据结构与算法

    • 根据问题类型(数组、字符串、链表、树、图、动态规划等)选择合适的数据结构。
    • 分析问题,找到最优的算法策略。
      • 查找问题 -> 哈希表、二分查找
      • 排序问题 -> 快速排序、归并排序
      • 遍历问题 -> 深度优先搜索、广度优先搜索
      • 最优问题 -> 动态规划、贪心算法
  3. 编写代码

    • 将算法思路转化为清晰、规范的 Java 代码。
    • 注意代码的健壮性,处理所有边界条件。
  4. 测试与优化

    LeetCode Java题解,如何高效解题?-图2
    (图片来源网络,侵删)
    • 在 LeetCode 提交前,自己用几个测试用例(包括边界用例)跑一下。
    • 提交后,根据结果(通过/超时/内存超限)进行优化。

Java 代码结构与通用模板

一个标准的 Java LeetCode 解题类通常如下结构:

import java.util.*; // 常常需要导入集合框架
public class Solution {
    /**
     * 主解题方法
     * @param param1 参数1
     * @param param2 参数2
     * @return 返回值
     */
    public int methodName(int[] param1, String param2) {
        // 1. 处理边界条件
        if (param1 == null || param1.length == 0) {
            return 0; // 或其他默认值
        }
        // 2. 核心逻辑实现
        // ...
        // 3. 返回结果
        return result;
    }
    // 如果需要,可以在此处定义辅助方法,如私有方法
    private int helper(int x) {
        return x * x;
    }
    // 对于一些类,如链表、二叉树,需要先定义节点类
    // public class ListNode { ... }
    // public class TreeNode { ... }
}

通用技巧:

  • 使用 ArraysCollections 工具类:它们提供了很多便捷的排序、查找、转换等方法。
  • 使用 StringBuilder:在频繁进行字符串拼接时,StringBuilder 的性能远高于 号拼接。
  • 善用 MapSetHashMapHashSet 是解决查找、去重、统计频率等问题的利器。

按题型分类的解题技巧与代码示例

数组 & 字符串

这是最基础的题型,核心是遍历、排序、查找和双指针

  • 技巧
    • 排序Arrays.sort() 是 O(n log n) 的基础。
    • 双指针:一个快指针,一个慢指针,常用于移除元素、寻找特定和等。
    • 滑动窗口:用于处理子数组/子串问题,维护一个窗口来满足特定条件。

示例:移除元素 (LeetCode 27)

LeetCode Java题解,如何高效解题?-图3
(图片来源网络,侵删)

给你一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,并返回移除后数组的新长度。

class Solution {
    public int removeElement(int[] nums, int val) {
        // 使用双指针,slow 指向下一个不等于 val 的元素应该放置的位置
        int slow = 0;
        for (int fast = 0; fast < nums.length; fast++) {
            // 如果当前元素不等于要移除的值
            if (nums[fast] != val) {
                // 将它放到 slow 的位置,slow 后移
                nums[slow] = nums[fast];
                slow++;
            }
        }
        // slow 最终的值就是新数组的长度
        return slow;
    }
}

哈希表

哈希表(HashMap, HashSet)是面试中的高频考点,核心是空间换时间

  • 技巧
    • 查找/去重HashSet 可以在 O(1) 时间内完成。
    • 频率统计HashMap<K, V> 可以用来统计元素出现的次数。
    • 查找补数:在求两数之和等问题中,可以用哈希表存储已遍历过的元素,快速查找目标值是否存在。

示例:两数之和 (LeetCode 1)

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出和为目标值 target 的那两个整数,并返回它们的数组下标。

import java.util.Map;
import java.util.HashMap;
class Solution {
    public int[] twoSum(int[] nums, int target) {
        // key: 数值, value: 索引
        Map<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < nums.length; i++) {
            int complement = target - nums[i];
            // 检查补数是否已经存在于 map 中
            if (map.containsKey(complement)) {
                // 如果存在,返回当前索引和补数的索引
                return new int[] { map.get(complement), i };
            }
            // 如果不存在,将当前元素和它的索引存入 map
            map.put(nums[i], i);
        }
        // 题目保证有解,这里可以返回 null 或抛出异常
        return null;
    }
}

链表

链表操作的核心是指针的移动和指向

  • 技巧
    • 虚拟头节点:在处理头节点可能被删除的链表问题时,创建一个虚拟头节点可以简化逻辑。
    • 快慢指针:一个指针每次走一步,一个指针每次走两步,常用于寻找环、寻找中间节点等。
    • 递归:链表天然适合递归,但要注意递归深度和栈溢出问题。

示例:反转链表 (LeetCode 206)

反转一个单链表。

// Definition for singly-linked list.
class ListNode {
    int val;
    ListNode next;
    ListNode() {}
    ListNode(int val) { this.val = val; }
    ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}
class Solution {
    // 方法一:迭代(双指针)
    public ListNode reverseList(ListNode head) {
        ListNode prev = null;
        ListNode curr = head;
        while (curr != null) {
            // 保存下一个节点
            ListNode nextTemp = curr.next;
            // 反转指针
            curr.next = prev;
            // prev 和 curr 后移
            prev = curr;
            curr = nextTemp;
        }
        // prev 最终指向新的头节点
        return prev;
    }
    // 方法二:递归
    public ListNode reverseListRecursive(ListNode head) {
        // 递归终止条件
        if (head == null || head.next == null) {
            return head;
        }
        // 递归反转剩余部分
        ListNode reversedList = reverseListRecursive(head.next);
        // 将当前节点接到反转后链表的尾部
        head.next.next = head;
        head.next = null; // 防止形成环
        return reversedList;
    }
}

树(特别是二叉树)的遍历是基础,分为深度优先搜索广度优先搜索

  • 技巧
    • DFS (前序、中序、后序):递归或栈实现。
    • BFS (层序遍历):队列实现。
    • 递归三要素:终止条件、递归调用、处理当前层。

示例:二叉树的前序遍历 (LeetCode 144)

给你二叉树的根节点 root,返回它前序遍历的结果。

// Definition for a binary tree node.
class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode() {}
    TreeNode(int val) { this.val = val; }
    TreeNode(int val, TreeNode left, TreeNode right) {
        this.val = val;
        this.left = left;
        this.right = right;
    }
}
class Solution {
    // 方法一:递归
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        helper(root, result);
        return result;
    }
    private void helper(TreeNode node, List<Integer> result) {
        if (node == null) {
            return;
        }
        // 前序:根 -> 左 -> 右
        result.add(node.val);
        helper(node.left, result);
        helper(node.right, result);
    }
    // 方法二:迭代(使用栈)
    public List<Integer> preorderTraversalIterative(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        if (root == null) {
            return result;
        }
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);
        while (!stack.isEmpty()) {
            TreeNode node = stack.pop();
            result.add(node.val);
            // 先压入右孩子,再压入左孩子,保证左孩子先出栈
            if (node.right != null) {
                stack.push(node.right);
            }
            if (node.left != null) {
                stack.push(node.left);
            }
        }
        return result;
    }
}

动态规划

DP 是难点,核心是找到状态转移方程

  • 技巧
    • 定义状态dp[i]dp[i][j] 代表什么。
    • 找到状态转移方程dp[i] 如何由 dp[i-1], dp[i-2] 等得到。
    • 确定初始状态dp[0], dp[1] 等的值。
    • 考虑空间优化:有时可以将一维 DP 优化为常数空间。

示例:爬楼梯 (LeetCode 70)

假设你正在爬楼梯,需要 n 阶你才能到达楼顶,每次你可以爬 12 个台阶,你有多少种不同的方法可以爬到楼顶?

class Solution {
    public int climbStairs(int n) {
        // dp[i] 表示爬到第 i 阶楼梯的方法数
        // dp[i] = dp[i-1] + dp[i-2]
        if (n == 1) {
            return 1;
        }
        int[] dp = new int[n + 1];
        dp[1] = 1;
        dp[2] = 2;
        for (int i = 3; i <= n; i++) {
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];
    }
    // 空间优化版本
    public int climbStairsOptimized(int n) {
        if (n == 1) {
            return 1;
        }
        if (n == 2) {
            return 2;
        }
        int prev1 = 1; // dp[i-2]
        int prev2 = 2; // dp[i-1]
        int current = 0;
        for (int i = 3; i <= n; i++) {
            current = prev1 + prev2;
            prev1 = prev2;
            prev2 = current;
        }
        return current;
    }
}

学习资源与社区

  1. 官方题解

    LeetCode 每道题下方都有官方题解,通常是 C++ 版本,但思路清晰,是学习的首选。

  2. 优质社区题解

    • LeetCode 评论区经常有非常精彩的 Java 解题思路,可以多看、多学习。
    • 关注点赞数高的回答,通常代码质量高且思路清晰。
  3. GitHub 仓库

    • 搜索 "LeetCode Java" 可以找到很多开源的题解仓库,gaurav5430/LeetCode,这些仓库整理了不同题目的解法,方便查阅。
  4. 博客与教程

    • 算法可视化网站:VisuAlgo (https://visualgo.net) 可以帮助你直观地理解排序、搜索、树、图等算法。
    • 技术博客:如 CSDN、掘金、知乎等,搜索具体题目名称,常有详细的图文解析。

刷题建议

  • 循序渐进:从 "简单" 难度开始,建立信心,熟悉常见模式。
  • 按专题刷:集中刷一个专题(如所有链表题、所有动态规划题),有助于归纳总结,形成知识体系。
  • 多写多练:不要只看不写,亲手敲代码是检验是否掌握的唯一标准。
  • 定期回顾:对于做错的题或经典题,要定期回顾,重新思考。

希望这份指南能对您有所帮助!祝您刷题顺利,早日成为算法大神!

分享:
扫描分享到社交APP
上一篇
下一篇