1. 力扣Hot100系列18(Java)技巧篇概述
作为Java开发者刷题路上的必经之路,力扣Hot100系列汇集了算法面试中的高频考点。本篇聚焦其中的技巧类题目,这类问题往往不需要复杂的数据结构,而是考察对问题本质的洞察和巧妙的解决思路。我将通过5道典型题目,分享如何用Java实现这些"思维体操"。
这类技巧题的特点是:
- 通常有严格的时空复杂度限制(如O(n)时间+O(1)空间)
- 解法往往突破常规思维模式
- 代码简洁但思路精妙
- 在真实面试中出现频率极高
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 只出现一次的数字(LeetCode 136)
2.1 问题描述与常规思路
给定一个非空整数数组,除了某个元素只出现一次外,其余每个元素均出现两次。找出那个只出现一次的元素。
最直观的解法是用哈希表统计频率,但这样需要O(n)额外空间。排序后遍历的解法也需要O(nlogn)时间。
2.2 位运算的巧妙解法
java复制public int singleNumber(int[] nums) {
int res = 0;
for (int num : nums) {
res ^= num;
}
return res;
}
这里利用了异或运算的三个性质:
- 任何数和0异或都是它本身(a ^ 0 = a)
- 任何数和自身异或都是0(a ^ a = 0)
- 异或满足交换律和结合律
因此,所有出现两次的数字异或后会相互抵消,最终剩下的就是只出现一次的数字。
注意:这种解法仅适用于其他数字都出现偶数次的情况。如果出现次数不同,需要更通用的解法。
3. 多数元素(LeetCode 169)
3.1 问题定义
给定一个大小为n的数组,找到其中的多数元素。多数元素是指在数组中出现次数大于⌊n/2⌋的元素。
3.2 摩尔投票法解析
java复制public int majorityElement(int[] nums) {
int count = 0;
Integer candidate = null;
for (int num : nums) {
if (count == 0) {
candidate = num;
}
count += (num == candidate) ? 1 : -1;
}
return candidate;
}
摩尔投票法的核心思想是对拼消耗:
- 维护一个候选人和计数器
- 遇到相同元素计数器+1,不同则-1
- 计数器归零时更换候选人
- 最后剩下的候选人就是多数元素
这种方法只需要O(1)空间,是处理这类问题的经典技巧。
4. 颜色分类(LeetCode 75)
4.1 问题背景
给定一个包含红色、白色和蓝色的数组,原地将它们按照红、白、蓝的顺序排序。我们使用整数0、1和2分别表示红、白、蓝。
4.2 荷兰国旗问题解法
java复制public void sortColors(int[] nums) {
int p0 = 0, curr = 0;
int p2 = nums.length - 1;
while (curr <= p2) {
if (nums[curr] == 0) {
swap(nums, curr++, p0++);
} else if (nums[curr] == 2) {
swap(nums, curr, p2--);
} else {
curr++;
}
}
}
private void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
这是经典的荷兰国旗问题,使用三指针法:
- p0指向0的最右边界
- p2指向2的最左边界
- curr是当前元素指针
通过交换元素将0移到左边,2移到右边,1自然留在中间。
5. 下一个排列(LeetCode 31)
5.1 字典序概念
实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。
5.2 算法步骤详解
java复制public void nextPermutation(int[] nums) {
int i = nums.length - 2;
while (i >= 0 && nums[i] >= nums[i + 1]) {
i--;
}
if (i >= 0) {
int j = nums.length - 1;
while (j >= 0 && nums[j] <= nums[i]) {
j--;
}
swap(nums, i, j);
}
reverse(nums, i + 1);
}
private void reverse(int[] nums, int start) {
int i = start, j = nums.length - 1;
while (i < j) {
swap(nums, i, j);
i++;
j--;
}
}
关键步骤:
- 从后向前找第一个降序的位置i
- 在i后面找第一个大于nums[i]的数nums[j]
- 交换nums[i]和nums[j]
- 反转i+1到末尾的部分
这个算法保证了我们找到的是"刚好"比当前排列大的下一个排列。
6. 寻找重复数(LeetCode 287)
6.1 问题约束
给定一个包含n+1个整数的数组nums,其数字都在1到n之间(包括1和n),假设只有一个重复的整数,找出这个重复的数。
6.2 快慢指针解法
java复制public int findDuplicate(int[] nums) {
int slow = nums[0];
int fast = nums[0];
do {
slow = nums[slow];
fast = nums[nums[fast]];
} while (slow != fast);
slow = nums[0];
while (slow != fast) {
slow = nums[slow];
fast = nums[fast];
}
return slow;
}
这道题将数组视为链表(值指向下一个节点的索引),重复元素会导致环的出现。使用快慢指针:
- 第一阶段找到相遇点
- 第二阶段找到环的入口
- 入口点即为重复数字
这种解法巧妙地将数组问题转化为链表环检测问题。
7. 技巧类题目通用解题思路
通过以上5道题目,我们可以总结出技巧类算法题的通用解法模式:
-
位运算技巧:
- 异或用于找唯一数
- 与/或运算处理二进制位问题
- 位掩码处理状态压缩
-
指针技巧:
- 双指针处理有序数组
- 快慢指针检测循环
- 多指针处理分类问题(如荷兰国旗)
-
数学技巧:
- 利用数学性质简化问题
- 排列组合的规律应用
- 数字本身的特性利用
-
逆向思维:
- 从后向前遍历
- 反向推导问题条件
- 利用问题的对称性
在实际面试中,当遇到时间复杂度或空间复杂度有严格限制的问题时,通常意味着需要寻找某种技巧性的解法,而非暴力或常规方法。培养这种解题直觉需要大量练习和对经典技巧的熟练掌握。
