1. Java算法题常用工具类概览
在力扣(LeetCode)等算法平台刷题时,Java选手常会遇到一些高频使用的工具类和方法。这些工具能显著提升编码效率,避免重复造轮子。根据我参加数十场算法面试和300+力扣题解的经验,以下是最核心的几类工具:
- Arrays类:提供数组排序、二分查找、填充等静态方法
- Collections类:针对集合的排序、反转、最大最小值等操作
- String/StringBuilder:字符串处理的核心类
- PriorityQueue:堆结构的标准实现
- Map/Set实现类:HashMap、TreeMap等高频数据结构
提示:Java标准库中约60%的算法题解法都依赖于这些基础类,掌握它们能覆盖大部分题目需求。
1.1 数组处理三剑客
Arrays类是处理数组问题的瑞士军刀。以力扣第75题(颜色分类)为例,虽然题目要求手写排序,但实际工程中直接使用Arrays.sort()就能解决:
java复制// 实际工程解法(非题目要求)
public void sortColors(int[] nums) {
Arrays.sort(nums); // 双轴快排实现,时间复杂度O(n log n)
}
但要注意面试时的限制条件。Arrays类最常用的方法包括:
| 方法签名 | 时间复杂度 | 典型应用场景 |
|---|---|---|
| sort(int[] a) | O(n log n) | 多数排序问题 |
| binarySearch(int[] a, int key) | O(log n) | 有序数组查找 |
| fill(int[] a, int val) | O(n) | 数组初始化 |
| copyOf(int[] original, int newLength) | O(n) | 数组扩容/截断 |
1.2 集合操作神器
Collections类在链表、列表相关题目中尤为实用。比如力扣第206题(反转链表)的递归解法:
java复制// 递归解法示例
public ListNode reverseList(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode newHead = reverseList(head.next);
head.next.next = head;
head.next = null;
return newHead;
}
Collections类常用方法对比:
sort(List<T> list): 采用TimSort算法,适合部分有序数据reverse(List<?> list): 线性时间复杂度反转max(Collection<?> coll): 依赖Comparable接口实现frequency(Collection<?> c, Object o): 统计元素出现次数
2. 字符串处理技巧
2.1 String与StringBuilder的选择
在力扣第151题(反转字符串中的单词)中,两种实现方式的性能差异显著:
java复制// 使用String拼接(不推荐)
String result = "";
for (String word : words) {
result = word + " " + result; // 每次拼接创建新对象
}
// 使用StringBuilder(推荐)
StringBuilder sb = new StringBuilder();
for (String word : words) {
sb.insert(0, word).insert(0, " ");
}
关键区别:
- String对象不可变,每次操作产生新对象
- StringBuilder内部维护可变char数组,适合高频修改场景
2.2 正则表达式应用
力扣第65题(有效数字)可以用正则优雅解决:
java复制public boolean isNumber(String s) {
String pattern = "^[+-]?(\\d+\\.?\\d*|\\.\\d+)([eE][+-]?\\d+)?$";
return s.trim().matches(pattern);
}
常用字符串方法:
split(String regex): 注意空字符串处理substring(int beginIndex, int endIndex): 左闭右开区间charAt(int index): 没有Python的负索引支持toCharArray(): 转为char数组处理更高效
3. 数据结构工具类实战
3.1 优先队列的妙用
力扣第215题(数组中的第K个最大元素)的标准解法:
java复制public int findKthLargest(int[] nums, int k) {
PriorityQueue<Integer> heap = new PriorityQueue<>();
for (int num : nums) {
heap.offer(num);
if (heap.size() > k) {
heap.poll(); // 维护大小为k的小顶堆
}
}
return heap.peek();
}
PriorityQueue的构造参数:
- 初始容量(默认11)
- Comparator:自定义排序规则
- 注意:非线程安全,多线程需用PriorityBlockingQueue
3.2 哈希表的进阶用法
力扣第1题(两数之和)的经典解法:
java复制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);
}
throw new IllegalArgumentException("No solution");
}
Map接口的实用技巧:
computeIfAbsent(): 懒加载模式merge(): 统计频率时特别有用getOrDefault(): 避免null检查
4. 算法模板与工具类组合
4.1 二分查找模板
虽然Arrays.binarySearch()可用,但掌握手写模板更重要:
java复制// 标准二分模板
int binarySearch(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;
else if (nums[mid] < target) left = mid + 1;
else right = mid - 1;
}
return -1;
}
4.2 回溯算法工具集
力扣第46题(全排列)的典型解法:
java复制public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
backtrack(res, new ArrayList<>(), nums);
return res;
}
private void backtrack(List<List<Integer>> res, List<Integer> temp, int[] nums) {
if (temp.size() == nums.length) {
res.add(new ArrayList<>(temp)); // 注意深拷贝
return;
}
for (int num : nums) {
if (temp.contains(num)) continue; // 效率不高,可用visited数组优化
temp.add(num);
backtrack(res, temp, nums);
temp.remove(temp.size() - 1);
}
}
优化技巧:
- 使用boolean数组替代List.contains()
- 提前排序进行剪枝
- 使用Deque替代List提升移除末尾元素的性能
5. 其他实用工具类
5.1 数学运算工具
Math类在随机题目中特别有用,如力扣第384题(打乱数组):
java复制private int[] array;
private int[] original;
private Random rand = new Random();
public Solution(int[] nums) {
array = nums;
original = nums.clone();
}
public int[] reset() {
array = original;
original = original.clone();
return array;
}
public int[] shuffle() {
for (int i = 0; i < array.length; i++) {
int j = rand.nextInt(array.length - i) + i;
swap(array, i, j);
}
return array;
}
常用数学方法:
Math.random(): 返回[0,1)之间的doubleMath.pow(double a, double b): 注意精度问题Math.max/min: 避免手写三元运算符
5.2 位运算技巧
力扣第191题(位1的个数)的几种解法:
java复制// 逐位检查
public int hammingWeight(int n) {
int bits = 0;
int mask = 1;
for (int i = 0; i < 32; i++) {
if ((n & mask) != 0) bits++;
mask <<= 1;
}
return bits;
}
// 优化解法
public int hammingWeight(int n) {
int sum = 0;
while (n != 0) {
sum++;
n &= (n - 1); // 将最低位的1置0
}
return sum;
}
位运算常用技巧:
n & (n-1): 消除最低位的1n & (-n): 获取最低位的1(a ^ b) < 0: 判断两数符号是否相反
6. 调试与性能优化工具
6.1 断言的使用
在算法竞赛中,合理使用断言可以快速定位问题:
java复制public int divide(int dividend, int divisor) {
assert divisor != 0 : "Divisor cannot be zero";
// ...其他逻辑
}
注意:默认不启用断言,需添加VM参数
-ea
6.2 性能测试方法
使用System.nanoTime()进行简单性能测试:
java复制long startTime = System.nanoTime();
// 被测代码
for (int i = 0; i < 10000; i++) {
solution.twoSum(nums, target);
}
long duration = (System.nanoTime() - startTime) / 1000000;
System.out.println("耗时:" + duration + "ms");
更专业的做法是使用JMH基准测试框架,但算法题中简单计时通常足够。
7. 常见问题与解决方案
7.1 整数溢出处理
力扣第7题(整数反转)中的典型陷阱:
java复制public int reverse(int x) {
int rev = 0;
while (x != 0) {
int pop = x % 10;
x /= 10;
// 检查溢出
if (rev > Integer.MAX_VALUE/10 || (rev == Integer.MAX_VALUE/10 && pop > 7)) return 0;
if (rev < Integer.MIN_VALUE/10 || (rev == Integer.MIN_VALUE/10 && pop < -8)) return 0;
rev = rev * 10 + pop;
}
return rev;
}
其他溢出场景:
- 二分查找中的中间值计算
- 累加和大数问题
- 乘法运算结果验证
7.2 边界条件处理
力扣第8题(字符串转换整数)的完整处理:
java复制public int myAtoi(String str) {
str = str.trim();
if (str.isEmpty()) return 0;
int sign = 1, base = 0, i = 0;
if (str.charAt(i) == '-' || str.charAt(i) == '+') {
sign = str.charAt(i++) == '-' ? -1 : 1;
}
while (i < str.length() && Character.isDigit(str.charAt(i))) {
if (base > Integer.MAX_VALUE / 10 ||
(base == Integer.MAX_VALUE / 10 && str.charAt(i) - '0' > 7)) {
return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE;
}
base = base * 10 + (str.charAt(i++) - '0');
}
return base * sign;
}
常见边界:
- 空输入处理
- 正负号判断
- 前导零处理
- 非法字符终止
8. 工具类组合应用案例
8.1 力扣第49题(字母异位词分组)
综合运用多种工具类的典型示例:
java复制public List<List<String>> groupAnagrams(String[] strs) {
Map<String, List<String>> map = new HashMap<>();
for (String s : strs) {
char[] ca = s.toCharArray();
Arrays.sort(ca);
String key = String.valueOf(ca);
map.computeIfAbsent(key, k -> new ArrayList<>()).add(s);
}
return new ArrayList<>(map.values());
}
技术点拆解:
- 使用toCharArray()转换字符串
- 通过Arrays.sort()标准化表示
- 利用HashMap分组
- 使用computeIfAbsent()简化代码
8.2 力扣第56题(合并区间)
集合与数组的混合操作:
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()][]);
}
关键技巧:
- 二维数组的自定义排序
- 使用ArrayList动态维护结果
- 数组与集合的相互转换
9. Java 8+新特性应用
9.1 Stream API简化代码
力扣第347题(前K个高频元素)的现代Java解法:
java复制public int[] topKFrequent(int[] nums, int k) {
return Arrays.stream(nums)
.boxed()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
.entrySet()
.stream()
.sorted(Map.Entry.<Integer, Long>comparingByValue().reversed())
.limit(k)
.mapToInt(Map.Entry::getKey)
.toArray();
}
注意点:
- 原始类型数组需先boxed()
- 收集器groupingBy的多种重载形式
- 排序时注意类型推断
9.2 Lambda表达式优化比较器
替代匿名内部类的简洁写法:
java复制// 传统写法
Arrays.sort(intervals, new Comparator<int[]>() {
@Override
public int compare(int[] a, int[] b) {
return a[0] - b[0];
}
});
// Lambda写法
Arrays.sort(intervals, (a, b) -> a[0] - b[0]);
// 更安全的写法(防溢出)
Arrays.sort(intervals, Comparator.comparingInt(a -> a[0]));
10. 算法竞赛专用技巧
10.1 快速输入输出
针对大数据量的优化(如力扣的某些极端测试用例):
java复制// 快速输入模板
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String[Tokenizer](https://taotoken.net?utm_source=general) st = new StringTokenizer(br.readLine());
int n = Integer.parseInt(st.nextToken());
// 快速输出
PrintWriter pw = new PrintWriter(System.out);
pw.println(result);
pw.flush();
10.2 自定义数据结构
实现力扣第380题(O(1)时间插入、删除和获取随机元素)的解决方案:
java复制class RandomizedSet {
List<Integer> list;
Map<Integer, Integer> valToIndex;
Random rand;
public RandomizedSet() {
list = new ArrayList<>();
valToIndex = new HashMap<>();
rand = new Random();
}
public boolean insert(int val) {
if (valToIndex.containsKey(val)) return false;
valToIndex.put(val, list.size());
list.add(val);
return true;
}
public boolean remove(int val) {
if (!valToIndex.containsKey(val)) return false;
int index = valToIndex.get(val);
int lastVal = list.get(list.size() - 1);
list.set(index, lastVal);
valToIndex.put(lastVal, index);
list.remove(list.size() - 1);
valToIndex.remove(val);
return true;
}
public int getRandom() {
return list.get(rand.nextInt(list.size()));
}
}
设计要点:
- ArrayList保证O(1)随机访问
- HashMap维护值到索引的映射
- 删除时交换元素位置避免数组移动
