Bootstrap

Java岗高频算法题精讲(LeetCode+场景化拆解)

在这篇文章中,我将带你一起深入分析5道Java岗高频的LeetCode算法题,通过场景化拆解,结合,从浅入深进行讲解。我会在每道题目后附上详细的代码实现,并探讨一些优化技巧和背后的算法理论,希望能帮助你在面试中脱颖而出。


1. 题目一:两数之和(Two Sum)

场景化背景

假设你正在开发一个电商平台,用户在结算时输入一个目标金额,你需要帮用户找出购物车中两个商品的总价是否等于这个金额。如果找到了,就返回这两个商品的索引。这个问题看似简单,但如何设计高效的算法却考验着我们对数据结构的理解。

题目描述

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

解法一:暴力解法

暴力解法最直接,遍历数组中的每一对数,检查它们的和是否等于目标值。

public class TwoSum {
    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};
                }
            }
        }
        throw new IllegalArgumentException("No two sum solution");
    }
}
代码分析
  • 时间复杂度:O(n^2),因为我们需要遍历所有的元素组合。
  • 空间复杂度:O(1),没有使用额外的空间。
解法二:哈希表解法

哈希表是一种非常适合查找问题的数据结构,通过哈希表,我们可以在遍历数组的同时查找目标元素,避免了暴力解法的双重循环。

import java.util.HashMap;

public class TwoSum {
    public int[] twoSum(int[] nums, int target) {
        HashMap<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);
        }
        throw new IllegalArgumentException("No two sum solution");
    }
}
代码分析
  • 时间复杂度:O(n),每个元素最多被访问两次:一次用于查找,另一次用于插入哈希表。
  • 空间复杂度:O(n),需要额外的哈希表来存储元素。

在面试中,虽然暴力解法是最直观的,但是面试官更看重的是你的思维广度和深度。哈希表的解法不仅减少了时间复杂度,而且充分展示了你对数据结构的理解和运用。在实际项目中,我们也常常利用哈希表来做快速查找,效率上能够得到显著提升。


2. 题目二:无重复字符的最长子串(Longest Substring Without Repeating Characters)

场景化背景

假设你正在开发一个文本编辑器功能,需要处理一个输入字符串,找出其中最长的不包含重复字符的子串。这类问题在字符串处理、数据流分析等场景中非常常见。

题目描述

给定一个字符串,找出其中不含有重复字符的最长子串的长度。

解法:滑动窗口法

滑动窗口是一种优化的技术,通常用于查找满足某些条件的子数组或子串。通过使用两个指针来维护一个滑动窗口,在窗口内查找最优解。

import java.util.HashSet;

public class LongestSubstring {
    public int lengthOfLongestSubstring(String s) {
        HashSet<Character> set = new HashSet<>();
        int left = 0, right = 0;
        int maxLength = 0;
        
        while (right < s.length()) {
            if (!set.contains(s.charAt(right))) {
                set.add(s.charAt(right));
                right++;
                maxLength = Math.max(maxLength, right - left);
            } else {
                set.remove(s.charAt(left));
                left++;
            }
        }
        
        return maxLength;
    }
}
代码分析
  • 时间复杂度:O(n),每个字符最多访问两次,分别是左指针和右指针。
  • 空间复杂度:O(min(n, m)),其中n是字符串的长度,m是字符集的大小(通常是256)。

滑动窗口技术在处理这类动态窗口问题时非常高效。它不仅仅是一个算法技巧,还能帮助我们在实际开发中提高处理复杂字符串或数组问题的能力。面试中,这种方法能够展示你对窗口技术的掌握,给面试官留下深刻印象。


3. 题目三:合并区间(Merge Intervals)

场景化背景

假设你正在为一个会议室管理系统开发功能,多个用户可能会预定同一时间段的会议室。你需要合并重叠的时间段,避免重复预定。

题目描述

给定一组区间,合并所有重叠的区间。

解法:排序法

首先对区间按开始时间进行排序,然后遍历区间,合并重叠区间。

import java.util.Arrays;
import java.util.ArrayList;

public class MergeIntervals {
    public int[][] merge(int[][] intervals) {
        if (intervals.length == 0) return new int[0][0];
        
        Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0]));
        ArrayList<int[]> merged = new ArrayList<>();
        
        for (int[] interval : intervals) {
            if (merged.isEmpty() || merged.get(merged.size() - 1)[1] < interval[0]) {
                merged.add(interval);
            } else {
                merged.get(merged.size() - 1)[1] = Math.max(merged.get(merged.size() - 1)[1], interval[1]);
            }
        }
        
        return merged.toArray(new int[merged.size()][]);
    }
}
代码分析
  • 时间复杂度:O(n log n),排序的时间复杂度。
  • 空间复杂度:O(n),存储合并后的区间。

这个问题考察了你如何通过排序和线性遍历来解决问题。这种思路对于处理区间类问题非常有效,并且在实际开发中有广泛应用,比如任务调度、资源管理等。


4. 题目四:有效的括号(Valid Parentheses)

场景化背景

假设你正在开发一个编译器或解释器,编译时需要检查程序代码中的括号是否匹配。这是一个经典的栈应用问题。

题目描述

给定一个字符串,包含只由 ()[]{} 三种括号组成的字符串,判断字符串是否有效。

解法:栈解法

栈是一种非常适合括号匹配的工具。遇到左括号时入栈,遇到右括号时与栈顶元素匹配。

import java.util.Stack;

public class ValidParentheses {
    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();
    }
}
代码分析
  • 时间复杂度:O(n),每个字符最多入栈和出栈一次。
  • 空间复杂度:O(n),栈最多存储n个字符。

栈是一种非常经典的数据结构,尤其适合处理括号匹配问题。在面试中,如果你能熟练运用栈来解决括号匹配等问题,面试官会觉得你对数据结构有较深的理解。


5. 题目五:反转链表(Reverse Linked List)

场景化背景

假设你正在开发一个链表处理库,需要实现链表反转功能。这是链表类问题中非常经典的题目,适用于许多实际应用。

题目描述

反转一个单链表。

解法:迭代法

通过迭代逐步反转链表中的每个节点。

public class ReverseLinkedList {
    public ListNode reverseList(ListNode head) {
        ListNode prev = null;
        ListNode curr = head;
        
        while (curr != null) {
            ListNode nextTemp = curr.next;
            curr.next = prev;
            prev = curr;
            curr = nextTemp;
        }
        
        return prev;
    }
}
代码分析
  • 时间复杂度:O(n),需要遍历整个链表一次。
  • 空间复杂度:O(1),不需要额外的空间。

链表反转是一个非常常见且实用的题目,它考察了你对链表操作的熟悉程度。在实际开发中,链表反转常用于缓存系统、队列实现等场景,掌握链表操作对Java开发者非常重要。

;