目录
[一、LeetCode 31 下一个排列](#一、LeetCode 31 下一个排列)
[Java 完整代码](#Java 完整代码)
[二、LeetCode 287 寻找重复数](#二、LeetCode 287 寻找重复数)
[Java 完整代码](#Java 完整代码)
今天给大家分享两道面试高频中等题:31. 下一个排列 和287. 寻找重复数,两道题都有非常经典的最优解法,掌握后对面试和算法思维提升都很有帮助。
一、LeetCode 31 下一个排列
题目描述
实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。必须原地修改,只允许使用额外常数空间。
核心思路(三步法)
这道题的核心是找到字典序的下一个排列,有固定的三步流程:
- 从后向前找 "下降点" :找到第一个满足
nums[i] < nums[i+1]的位置i,这是排列中需要被替换的 "较小数"。 - 从后向前找 "替换数" :找到第一个比
nums[i]大的数nums[j],交换nums[i]和nums[j]。 - 反转尾部升序序列 :交换后,
i之后的序列是降序的,反转它就能得到最小的尾部,从而形成下一个排列。
Java 完整代码
java
运行
public class NextPermutation {
public void nextPermutation(int[] nums) {
if (nums == null || nums.length <= 1) {
return;
}
// 1. 从后向前找第一个 nums[i] < nums[i+1] 的位置
int i = nums.length - 2;
while (i >= 0 && nums[i] >= nums[i + 1]) {
i--;
}
if (i >= 0) {
// 2. 从后向前找第一个比 nums[i] 大的数,交换
int j = nums.length - 1;
while (nums[j] <= nums[i]) {
j--;
}
swap(nums, i, j);
}
// 3. 反转 i 之后的部分(升序排列)
reverse(nums, i + 1, nums.length - 1);
}
// 交换数组中两个元素
private void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
// 反转数组指定区间
private void reverse(int[] nums, int left, int right) {
while (left < right) {
swap(nums, left, right);
left++;
right--;
}
}
// 测试主函数
public static void main(String[] args) {
NextPermutation solution = new NextPermutation();
int[] nums1 = {1, 2, 3};
solution.nextPermutation(nums1);
printArray(nums1); // 输出:1 3 2
int[] nums2 = {3, 2, 1};
solution.nextPermutation(nums2);
printArray(nums2); // 输出:1 2 3
}
private static void printArray(int[] nums) {
for (int num : nums) {
System.out.print(num + " ");
}
System.out.println();
}
}
复杂度分析
- 时间复杂度:O (n),最多遍历两次数组,反转一次数组。
- 空间复杂度:O (1),原地修改,仅使用常数额外空间。
二、LeetCode 287 寻找重复数
题目描述
给定一个包含 n + 1 个整数的数组 nums,其数字都在 [1, n] 范围内(包括 1 和 n),可知至少存在一个重复的整数。假设 nums 只有一个重复的整数,返回这个重复的数。必须解决问题且不修改数组 nums 且只使用额外常数级别的空间。
核心思路(快慢指针法,弗洛伊德龟兔赛跑)
这道题可以转化为寻找链表环的入口节点问题:
- 把数组看作一个链表,数组的索引是节点,数组的值是下一个节点的索引。
- 因为存在重复数,链表中必然存在环,重复数就是环的入口节点。
- 使用快慢指针:快指针每次走两步,慢指针每次走一步,两者相遇后,再让快指针从头开始走,每次走一步,和慢指针再次相遇的节点就是环的入口(重复数)。
Java 完整代码
java
运行
public class FindDuplicate {
public int findDuplicate(int[] nums) {
// 快慢指针初始化
int slow = 0;
int fast = 0;
// 第一次相遇
do {
slow = nums[slow];
fast = nums[nums[fast]];
} while (slow != fast);
// 快指针回到起点,再次相遇即为环入口
fast = 0;
while (slow != fast) {
slow = nums[slow];
fast = nums[fast];
}
return slow;
}
// 测试主函数
public static void main(String[] args) {
FindDuplicate solution = new FindDuplicate();
int[] nums1 = {1, 3, 4, 2, 2};
System.out.println("重复数是:" + solution.findDuplicate(nums1)); // 输出:2
int[] nums2 = {3, 1, 3, 4, 2};
System.out.println("重复数是:" + solution.findDuplicate(nums2)); // 输出:3
}
}
复杂度分析
- 时间复杂度:O (n),快慢指针在链表中移动的总步数是线性的。
- 空间复杂度:O (1),仅使用两个指针变量,常数额外空间。
三、两道题面试重点总结
表格
| 题目 | 核心考点 | 关键技巧 | 易错点 |
|---|---|---|---|
| 下一个排列 | 字典序排列、原地算法 | 三步法(找下降点→交换→反转) | 边界处理(完全降序的情况)、反转区间的起点 |
| 寻找重复数 | 数组与链表转化、快慢指针 | 弗洛伊德龟兔赛跑算法 | 循环条件的设置、两次相遇的逻辑 |
这两道题都是面试中非常经典的 "中等题天花板",掌握它们不仅能应对面试,还能锻炼对数组、链表和原地算法的理解。