题目:
官方链接:
参考答案:
【新手入门】LeetCode 287. 寻找重复数:Java & JavaScript 双解法详解
目录
1. 题目描述
给定一个包含 n + 1
个整数的数组 nums
,其中每个整数都在 [1, n]
范围内(包括 1 和 n),可知至少存在一个重复的整数。假设 nums
中只有一个整数出现两次或多次,其余整数均只出现一次。返回这个重复的数。
要求:
- 不修改数组
nums
- 只能使用 O(1) 的额外空间
示例:
输入:nums = [1,3,4,2,2]
输出:2
输入:nums = [3,1,3,4,2]
输出:3
输入:nums = [3,3,3,3,3]
输出:3
2. 问题分析
- 核心问题:在 O(1) 空间、不修改数组的条件下,找出唯一重复的数字。
- 关键特性:
- 数字范围是
[1, n]
,数组长度是n + 1
- 只有一个数字重复(可能重复多次)
- 数字范围是
- 常见误区:
- 使用哈希表统计次数(空间 O(n))
- 排序后查找(修改了数组,且时间 O(nlogn))
3. 解题思路
3.1 快慢指针法(最优解)
核心思想:
- 将数组视为链表,数字表示下一个节点的索引(如
nums[i]
指向nums[nums[i]]
)。 - 重复数字会导致链表出现环,问题转化为寻找环的入口(类似 LeetCode 142)。
- 使用快慢指针:
- 快指针每次走两步,慢指针每次走一步,直到相遇。
- 将快指针重置到起点,然后快慢指针每次各走一步,再次相遇点即为重复数字。
优势:
- 时间复杂度 O(n)
- 空间复杂度 O(1)
3.2 二分查找法
核心思想:
- 在
[1, n]
范围内二分查找:- 统计数组中 <= mid 的数字个数。
- 如果个数 > mid,说明重复数在左半部分,否则在右半部分。
- 逐步缩小范围直到找到重复数。
优势:
- 时间复杂度 O(nlogn)
- 空间复杂度 O(1)
4. Java代码实现
快慢指针法
class Solution {
public int findDuplicate(int[] nums) {
int slow = nums[0], fast = nums[0];
do {
slow = nums[slow];
fast = nums[nums[fast]];
} while (slow != fast);
fast = nums[0];
while (slow != fast) {
slow = nums[slow];
fast = nums[fast];
}
return slow;
}
}
二分查找法
class Solution {
public int findDuplicate(int[] nums) {
int left = 1, right = nums.length - 1;
while (left < right) {
int mid = left + (right - left) / 2;
int count = 0;
for (int num : nums) {
if (num <= mid) count++;
}
if (count > mid) right = mid;
else left = mid + 1;
}
return left;
}
}
5. JavaScript代码实现
快慢指针法
/**
* @param {number[]} nums
* @return {number}
*/
var findDuplicate = function(nums) {
let slow = nums[0], fast = nums[0];
do {
slow = nums[slow];
fast = nums[nums[fast]];
} while (slow !== fast);
fast = nums[0];
while (slow !== fast) {
slow = nums[slow];
fast = nums[fast];
}
return slow;
};
二分查找法
/**
* @param {number[]} nums
* @return {number}
*/
var findDuplicate = function(nums) {
let left = 1, right = nums.length - 1;
while (left < right) {
const mid = Math.floor((left + right) / 2);
let count = 0;
for (const num of nums) {
if (num <= mid) count++;
}
if (count > mid) right = mid;
else left = mid + 1;
}
return left;
};
6. 复杂度分析
方法 | 时间复杂度 | 空间复杂度 |
---|---|---|
快慢指针法 | O(n) | O(1) |
二分查找法 | O(nlogn) | O(1) |
7. 边界条件与注意事项
- 输入验证:
- 数组长度必须为
n + 1
- 数字范围必须在
[1, n]
内
- 数组长度必须为
- 快慢指针法:
- 初始时
slow
和fast
必须从nums[0]
开始 - 确保
nums
中只有一个重复数
- 初始时
- 二分查找法:
- 适用于重复数出现多次的情况
- 需要确保
[1, n]
范围正确
8. 总结
- 面试推荐:快慢指针法是本题的标准解法,考察对链表环检测的理解。
- 核心技巧:
- 将数组视为链表,利用快慢指针找环的入口。
- 二分查找法适用于更一般的场景(如重复数出现多次)。
- 适用场景:
- 需要 O(1) 空间复杂度且不修改数组的场景。
- 类似问题:检测链表中的环。
掌握这道题,你就能轻松解决类似的数组重复数问题! 🚀
博客标题:寻找重复数问题:Java与JavaScript的解决方案
目录
1. 引言
在力扣(LeetCode)的287题“寻找重复数”中,我们需要在一个包含n+1个整数的数组中找到唯一重复的数字。数组中的数字范围在[1, n]之间,且至少有一个数字是重复的。我们的目标是设计一个不修改原数组且只使用常量级O(1)额外空间的解决方案。
2. 问题分析
由于数组中只有一个数字是重复的,我们可以利用这个特性来找到重复的数字。以下是一些可能的解决方案:
- 排序法:对数组进行排序,然后遍历数组,找到第一个重复的数字。
- 哈希表法:使用哈希表记录每个数字出现的次数,然后找到出现次数大于1的数字。
- Floyd的循环检测法:这是一个基于数学原理的算法,它利用了“快慢指针”的概念来找到重复的数字。
3. Java实现
3.1 算法思路
使用Floyd的循环检测法,我们可以将数组看作是一个有向图,其中每个数字都是一个节点,如果数字i在数组中出现,则存在一条从节点i到节点nums[i-1]的边。由于存在重复的数字,必然存在一个环。我们可以使用两个指针,一个快指针和一个慢指针,它们以不同的速度遍历这个图,最终它们会在环中相遇。
3.2 代码实现
public class FindDuplicate {
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;
}
}
4. JavaScript实现
4.1 算法思路
JavaScript的实现与Java类似,只是语法上有所不同。
4.2 代码实现
function findDuplicate(nums) {
let slow = nums[0];
let 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;
}
5. 总结
通过本文,我们学习了如何使用Java和JavaScript解决寻找重复数问题。Floyd的循环检测法是一种非常巧妙的算法,它能够在不修改原数组且只使用常量级额外空间的情况下找到重复的数字。希望这篇文章能帮助你更好地理解并掌握这一知识点。
好的,我很乐意为你提供详细的解答和博客内容。以下是一篇新手入门博客,包含Java和JavaScript的解答以及相关知识点的详细介绍。
博客标题: 从零开始学习力扣题 287. 寻找重复数
目录:
-
问题描述
-
Java解答
2.1 使用快慢指针 -
JavaScript解答
3.1 使用快慢指针 -
知识点总结
4.1 快慢指针
4.2 时间复杂度和空间复杂度 -
问题描述
力扣题 287. 寻找重复数要求实现一个函数findDuplicate(nums)
,其中nums
是一个整数数组。该函数需要返回数组中重复出现的数字。 -
Java解答
2.1 使用快慢指针
class Solution {
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;
}
}
- JavaScript解答
3.1 使用快慢指针
/**
* @param {number[]} nums
* @return {number}
*/
var findDuplicate = function(nums) {
let slow = nums[0];
let 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;
};
- 知识点总结
4.1 快慢指针
快慢指针是一种常见的解决环形链表问题的方法。在本题中,我们将数组视为一个环形链表,其中每个元素都指向另一个元素。通过快慢指针,我们可以找到环的入口,即重复的数字。
4.2 时间复杂度和空间复杂度
findDuplicate()
方法的时间复杂度为 O(n),空间复杂度为 O(1),其中 n 是数组的长度。这是因为我们只需要遍历一次数组,并使用常量额外空间来存储结果。
寻找重复数:Java与JavaScript解题指南(LeetCode 287)
目录
- 引言
- 题目简介和分析
- 解题思路详解
- Java实现方案
- JavaScript实现方案
- 方法总结与注意事项
- 结语
1. 引言
在算法面试及实际应用中,寻找数组中的重复元素是常见的题目之一。本篇博客将围绕LeetCode第287题——“寻找重复数”,讲解两种主流解法的思路及其代码实现(Java和JavaScript),帮助你理解并掌握这类题目解决技巧。
2. 题目简介和分析
题目描述
给定一个长度为 n+1 的数组 nums
,元素都在 [1, n]
范围内,且数组中只有一个数字重复出现,但可能有多个重复的数字。
要求: 不修改数组,使用常量空间(O(1)),找出这个重复数字。
示例
输入:nums = [1, 3, 4, 2, 2]
输出:2
输入:nums = [3, 1, 3, 4, 2]
输出:3
条件限制
- 1 <= n <= 10^5
- nums.length == n + 1
- 1 <= nums[i] <= n
- 只存在一个重复数字(可能多次出现)
3. 解题思路详解
方法一:不可修改数组应用“快慢指针” (Floyd’s Tortoise and Hare)
这是题目最常用的解法,其直观理解:
- 数组中的数字可被视作“链表”中的节点,数组元素的值指向下一个节点的索引。
- 由于数组有重复数字,链表就会出现“环”。
- 我们可以用快慢指针检测环,找出环的入口(重复元素)。
具体过程:
- 使用两个指针,快指针每次走两步,慢指针每次走一步。
- 当两个指针相遇时,说明存在环。
- 将一个指针重新指向起点,两个指针每次走一步,直到再次相遇,即为环的入口,也就是重复的数字。
时间复杂度: O(n),空间复杂度:O(1)
4. Java实现方案
public class FindDuplicate {
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;
}
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
}
}
5. JavaScript实现方案
var findDuplicate = function(nums) {
let slow = nums[0];
let fast = nums[0];
// Phase 1: 找到相遇点
do {
slow = nums[slow];
fast = nums[nums[fast]];
} while (slow !== fast);
// Phase 2: 找入口点
slow = nums[0];
while (slow !== fast) {
slow = nums[slow];
fast = nums[fast];
}
return slow;
};
// 测试
console.log(findDuplicate([1,3,4,2,2])); // 2
console.log(findDuplicate([3,1,3,4,2])); // 3
技巧: JavaScript中,数组索引和数值都作为指针,逻辑与Java类似。
6. 方法总结与注意事项
- 核心思想: 利用链表环检测(Floyd算法)映射数组中的“重复元素”问题。
- 优点: 不修改数组,空间复杂度O(1),时间复杂度O(n)。
- 注意事项:
- 需确保数组符合题目条件(长度为 n + 1,元素在 [1, n])
- 适合数组中只有一个重复元素的情况。
7. 结语
“寻找重复数”借助“链表环检测”算法,是常用且巧妙的解决方案。理解其思路后,无论用Java还是JavaScript,都可以快速编写出高效代码。在面试中遇到类似问题时,运用此技巧必定用得上。
祝你学习顺利,写出让面试官眼前一亮的代码!
力扣题解:287. 寻找重复数
目录
- 题目描述
- 解题思路
- 2.1 使用哈希表
- 2.2 快慢指针(Floyd 判圈算法)
- Java 实现
- JavaScript 实现
- 总结
1. 题目描述
给定一个包含 n + 1
个整数的数组 nums
,其数字都在 [1, n]
范围内(包括 1 和 n),可知至少存在一个重复的整数。假设 nums
只有一个重复的整数,返回这个重复的数。
示例
-
输入:
nums = [1, 3, 4, 2, 2]
- 输出:
2
- 输出:
-
输入:
nums = [3, 1, 3, 4, 2]
- 输出:
3
- 输出:
-
输入:
nums = [3, 3, 3, 3, 3]
- 输出:
3
- 输出:
提示
1 <= n <= 10^5
nums.length == n + 1
1 <= nums[i] <= n
nums
中只有一个整数出现两次或多次,其余整数均只出现一次。
2. 解题思路
2.1 使用哈希表
一种简单的方法是使用哈希表来存储已经访问过的数字。在遍历数组时,如果发现某个数字已经存在于哈希表中,则说明这个数字是重复的。这种方法的时间复杂度为 (O(n)),但空间复杂度为 (O(n)),不符合题目要求的常量级空间。
2.2 快慢指针(Floyd 判圈算法)
为了满足空间复杂度为 (O(1)) 的要求,我们可以使用快慢指针的方法。这个方法的核心思想是将数组视为一个链表,重复的数字会导致链表中出现环。
- 初始化:设置两个指针,慢指针
slow
和快指针fast
,都指向数组的第一个元素。 - 移动指针:慢指针每次移动一步,快指针每次移动两步,直到它们相遇。
- 寻找入口:相遇后,将慢指针重置到数组的起始位置,然后两个指针每次都移动一步,直到再次相遇。相遇的点就是重复的数字。
这种方法的时间复杂度为 (O(n)),空间复杂度为 (O(1))。
3. Java 实现
以下是 Java 的实现代码:
public class Solution {
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; // 或者 return fast;
}
}
4. JavaScript 实现
以下是 JavaScript 的实现代码:
var findDuplicate = function(nums) {
// 使用快慢指针
let slow = nums[0];
let 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; // 或者 return fast;
};
5. 总结
在这篇博客中,我们讨论了力扣题目“寻找重复数”的解法。我们首先介绍了题目的基本要求,然后分析了两种解法:使用哈希表和快慢指针(Floyd 判圈算法)。最后,我们提供了 Java 和 JavaScript 的实现代码。
通过使用快慢指针的方法,我们能够在不使用额外空间的情况下有效地找到重复的数字。希望这篇博客能帮助你更好地理解这个问题及其解法!如果你有任何问题或想法,欢迎在评论区留言讨论。
好的,为你准备一篇关于 LeetCode 287. 寻找重复数的详细入门博客,包含 Java 和 JavaScript 的解法,并深入讲解算法思路和复杂度分析。
博客标题: LeetCode 287. 寻找重复数:新手入门指南 (Java & JavaScript)
目录:
- 引言:在有限空间中寻找重复
- 题目描述
- 解题思路:快慢指针(Floyd 判圈算法)
- 3.1 为什么选择快慢指针?
- 3.2 将数组视为链表
- 3.3 算法步骤
- Java 代码实现
- 4.1 代码
- 4.2 代码解释
- JavaScript 代码实现
- 5.1 代码
- 5.2 代码解释
- 复杂度分析
- 6.1 时间复杂度
- 6.2 空间复杂度
- 总结:掌握快慢指针,巧妙解决数组中的循环问题!
博客正文:
1. 引言:在有限空间中寻找重复
“寻找重复数”是 LeetCode 上一道中等难度的题目。它考察了你对数组和链表的理解,以及你解决问题的能力。这道题的难点在于要求不修改数组且只使用常量额外空间。掌握这道题的解法,对于你提升算法思维非常有帮助。
2. 题目描述
给定一个包含 n + 1
个整数的数组 nums
,其数字都在 [1, n]
范围内(包括 1
和 n
),可知至少存在一个重复的整数。
假设 nums
只有 一个重复的整数 ,返回 这个重复的数 。
你设计的解决方案必须 不修改 数组 nums
且只用常量级 O(1) 的额外空间。
示例:
输入:nums = [1,3,4,2,2]
输出:2
输入:nums = [3,1,3,4,2]
输出:3
输入:nums = [3,3,3,3,3]
输出:3
3. 解题思路:快慢指针(Floyd 判圈算法)
-
3.1 为什么选择快慢指针?
- 满足题目要求: 快慢指针可以在不修改数组且只使用常量额外空间的情况下解决此问题。
- 巧妙转换: 将数组转换为链表,利用链表中的循环来寻找重复的数。
-
3.2 将数组视为链表
可以将数组
nums
视为一个链表,其中nums[i]
表示节点i
的下一个节点是nums[i]
。由于数组中存在重复的数,因此这个链表中必然存在循环。例如,对于数组
nums = [1,3,4,2,2]
,可以将其视为以下链表:0 -> 1 -> 3 -> 2 -> 4 -> 2 ^ | --------------------
可以看到,链表中存在一个循环,循环的入口就是重复的数
2
。 -
3.3 算法步骤
- 初始化两个指针
slow
和fast
,都指向数组的第一个元素nums[0]
。 slow
指针每次移动一步:slow = nums[slow]
。fast
指针每次移动两步:fast = nums[nums[fast]]
。- 当
slow
和fast
相遇时,说明链表中存在循环。 - 将
slow
指针重新指向数组的第一个元素nums[0]
。 slow
和fast
指针每次都移动一步,直到它们再次相遇。- 此时,
slow
指针指向的就是重复的数。
- 初始化两个指针
4. Java 代码实现
-
4.1 代码
class Solution { 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; } }
-
4.2 代码解释
int slow = nums[0];
: 初始化slow
指针。int fast = nums[0];
: 初始化fast
指针。do { ... } while (slow != fast);
: 找到快慢指针相遇的点。slow = nums[0];
: 将slow
指针重新指向数组的第一个元素。while (slow != fast) { ... }
: 找到循环的入口。return slow;
: 返回重复的数。
5. JavaScript 代码实现
-
5.1 代码
function findDuplicate(nums) { let slow = nums[0]; let 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; }
-
5.2 代码解释
let slow = nums[0];
: 初始化slow
指针。let fast = nums[0];
: 初始化fast
指针。do { ... } while (slow !== fast);
: 找到快慢指针相遇的点。slow = nums[0];
: 将slow
指针重新指向数组的第一个元素。while (slow !== fast) { ... }
: 找到循环的入口。return slow;
: 返回重复的数。
6. 复杂度分析
-
6.1 时间复杂度
- O(n),其中 n 是数组的长度。快慢指针最多遍历数组两次。
-
6.2 空间复杂度
- O(1),只需要使用两个指针
slow
和fast
。
- O(1),只需要使用两个指针
7. 总结:掌握快慢指针,巧妙解决数组中的循环问题!
“寻找重复数”是一道经典的算法题,通过学习这道题,你可以掌握快慢指针这种重要的算法技巧,并学会如何使用它来解决数组中的循环问题。 希望这篇博客能帮助你入门算法和数据结构,并在你的编程之路上助你一臂之力! 继续学习和实践,你一定会成为一名优秀的程序员!
力扣 287 题:寻找重复数新手攻略
一、引言
在算法学习的道路上,我们常常会遇到各种有趣且具有挑战性的问题。力扣第 287 题“寻找重复数”就是这样一道经典题目。本题要求我们在一个包含 n + 1
个整数的数组中,找出唯一的重复整数,并且要满足不修改数组且只用常量级
O
(
1
)
O(1)
O(1) 额外空间的条件。接下来,我们将详细分析这道题,并分别给出 Java 和 JavaScript 的实现方案。
二、题目分析
2.1 题目描述
给定一个包含 n + 1
个整数的数组 nums
,其数字都在 [1, n]
范围内(包括 1 和 n),可知至少存在一个重复的整数。假设 nums
只有一个重复的整数,我们需要返回这个重复的数。同时,设计的解决方案必须不修改数组 nums
且只用常量级
O
(
1
)
O(1)
O(1) 的额外空间。
2.2 示例分析
- 示例 1:
- 输入:
nums = [1, 3, 4, 2, 2]
- 输出:
2
- 解释:数组中 2 出现了两次,其他数字只出现一次,所以重复的数是 2。
- 输入:
- 示例 2:
- 输入:
nums = [3, 1, 3, 4, 2]
- 输出:
3
- 解释:数组中 3 出现了两次,其他数字只出现一次,所以重复的数是 3。
- 输入:
- 示例 3:
- 输入:
nums = [3, 3, 3, 3, 3]
- 输出:
3
- 解释:数组中 3 多次重复,所以重复的数是 3。
- 输入:
2.3 提示信息
1 <= n <= 10^5
nums.length == n + 1
1 <= nums[i] <= n
nums
中只有一个整数出现两次或多次,其余整数均只出现一次。
三、解题思路
本题可以使用快慢指针(弗洛伊德判圈算法)来解决。我们可以将数组看作一个链表,数组中的每个元素的值作为下一个节点的索引。由于存在重复的数字,必然会形成一个环,而重复的数字就是环的入口。
快慢指针的基本思想是:定义两个指针,一个快指针 fast
每次移动两步,一个慢指针 slow
每次移动一步。当快慢指针相遇时,说明存在环。然后将其中一个指针重新指向数组的起始位置,两个指针都以每次一步的速度移动,再次相遇的位置就是环的入口,也就是重复的数字。
四、Java 实现
class Solution {
public int findDuplicate(int[] nums) {
// 初始化快慢指针
int slow = nums[0];
int fast = nums[nums[0]];
// 快慢指针相遇
while (slow != fast) {
slow = nums[slow];
fast = nums[nums[fast]];
}
// 其中一个指针回到起点
fast = 0;
while (slow != fast) {
slow = nums[slow];
fast = nums[fast];
}
return slow;
}
public static void main(String[] args) {
Solution solution = new Solution();
int[] nums = {1, 3, 4, 2, 2};
int duplicate = solution.findDuplicate(nums);
System.out.println("重复的数字是: " + duplicate);
}
}
4.1 代码解释
- 初始化慢指针
slow
指向nums[0]
,快指针fast
指向nums[nums[0]]
。 - 快慢指针开始移动,快指针每次移动两步,慢指针每次移动一步,直到它们相遇。
- 相遇后,将快指针重新指向数组的起始位置
0
。 - 然后快慢指针都以每次一步的速度移动,再次相遇的位置就是重复的数字。
4.2 复杂度分析
- 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组的长度。快慢指针在环中相遇以及找到环的入口的过程,最多遍历数组两次。
- 空间复杂度: O ( 1 ) O(1) O(1),只使用了常数级的额外空间。
五、JavaScript 实现
/**
* @param {number[]} nums
* @return {number}
*/
var findDuplicate = function(nums) {
// 初始化快慢指针
let slow = nums[0];
let fast = nums[nums[0]];
// 快慢指针相遇
while (slow !== fast) {
slow = nums[slow];
fast = nums[nums[fast]];
}
// 其中一个指针回到起点
fast = 0;
while (slow !== fast) {
slow = nums[slow];
fast = nums[fast];
}
return slow;
};
// 测试代码
const nums = [1, 3, 4, 2, 2];
const duplicate = findDuplicate(nums);
console.log("重复的数字是: " + duplicate);
5.1 代码解释
- 初始化慢指针
slow
指向nums[0]
,快指针fast
指向nums[nums[0]]
。 - 使用
while
循环让快慢指针移动,快指针每次移动两步,慢指针每次移动一步,直到它们相遇。 - 相遇后,将快指针重新指向数组的起始位置
0
。 - 再次使用
while
循环,让快慢指针都以每次一步的速度移动,再次相遇的位置就是重复的数字。
5.2 复杂度分析
- 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组的长度。快慢指针在环中相遇以及找到环的入口的过程,最多遍历数组两次。
- 空间复杂度: O ( 1 ) O(1) O(1),只使用了常数级的额外空间。
六、总结
本题通过巧妙地运用快慢指针(弗洛伊德判圈算法),在不修改数组且只用常量级额外空间的条件下,高效地解决了寻找重复数的问题。无论是 Java 还是 JavaScript,实现代码都非常简洁。希望这篇博客能帮助新手朋友们更好地理解和掌握这道题的解法。
七、参考资料
- 力扣(LeetCode)官方网站
- 《算法导论》相关章节
【新手入门】LeetCode 287. 寻找重复数:Java & JavaScript双语言详解
目录
1. 题目描述
给定一个包含 n + 1
个整数的数组 nums
,其中每个整数都在 [1, n]
范围内(包括 1 和 n)。已知数组中至少存在一个重复的整数,且只有一个重复数(可能重复多次)。要求在不修改数组且只使用常量额外空间(O(1))的情况下,找出这个重复的数。
示例 1:
输入:nums = [1,3,4,2,2]
输出:2
示例 2:
输入:nums = [3,1,3,4,2]
输出:3
示例 3:
输入:nums = [3,3,3,3,3]
输出:3
提示:
1 <= n <= 10^5
nums.length == n + 1
1 <= nums[i] <= n
- 只有一个整数出现两次或多次,其余整数均只出现一次
2. 核心概念解析
关键限制条件:
- 不修改原数组:不能通过排序或标记法解决
- 常量空间复杂度:不能使用哈希表等额外存储
解题突破口:
- 将数组视为链表,利用快慢指针检测环(类似链表找环入口问题)
- 数字范围
[1, n]
且长度为n+1
,确保可以形成有环链表
3. 解题思路分析
方法:快慢指针(Floyd判圈算法)
-
第一阶段:检测环的存在
- 慢指针每次走一步:
slow = nums[slow]
- 快指针每次走两步:
fast = nums[nums[fast]]
- 当
slow == fast
时,说明存在环
- 慢指针每次走一步:
-
第二阶段:找到环的入口(即重复数)
- 重置慢指针到起点:
slow = 0
- 快慢指针同时每次走一步,直到相遇
- 相遇点即为重复数
- 重置慢指针到起点:
为什么有效?
- 将数组视为链表,
nums[i]
表示节点i
指向的下一个节点 - 重复数会导致多个节点指向它,形成环
- 环的入口即为重复数
4. Java实现
class Solution {
public int findDuplicate(int[] nums) {
// 第一阶段:检测环
int slow = 0, fast = 0;
do {
slow = nums[slow];
fast = nums[nums[fast]];
} while (slow != fast);
// 第二阶段:找环入口
slow = 0;
while (slow != fast) {
slow = nums[slow];
fast = nums[fast];
}
return slow;
}
}
代码解析:
- 时间复杂度:O(n)
- 空间复杂度:O(1)
5. JavaScript实现
/**
* @param {number[]} nums
* @return {number}
*/
var findDuplicate = function(nums) {
// 第一阶段:检测环
let slow = 0, fast = 0;
do {
slow = nums[slow];
fast = nums[nums[fast]];
} while (slow !== fast);
// 第二阶段:找环入口
slow = 0;
while (slow !== fast) {
slow = nums[slow];
fast = nums[fast];
}
return slow;
};
代码解析:
- 逻辑与Java完全一致
- 注意:JavaScript中
===
用于严格相等比较
6. 复杂度分析
方法 | 时间复杂度 | 空间复杂度 |
---|---|---|
快慢指针 | O(n) | O(1) |
二分查找 | O(n log n) | O(1) |
哈希表 | O(n) | O(n) |
为什么选择快慢指针?
- 满足题目所有限制条件
- 比二分查找更高效(O(n) vs O(n log n))
7. 总结与扩展
核心技巧:
- 将数组视为链表,利用数字范围特性
- Floyd判圈算法的巧妙应用
扩展题目:
- 142. 环形链表 II(原理完全相同)
- 448. 找到所有数组中消失的数字
相关知识点:
- 链表环检测算法
- 二分查找的适用场景
🔗 推荐练习:LeetCode 链表专题
如果有帮助,请点赞收藏支持! 🚀
寻找重复数:Java与JavaScript的双指针与二分法详解
目录
- 问题描述与示例
- 暴力法与优化思路
- 二分查找法详解
- 快慢指针法详解
- Java代码实现
- JavaScript代码实现
- 复杂度分析
- 常见问题解答
- 总结与扩展思考
1. 问题描述与示例
题目:给定一个包含 n+1
个整数的数组 nums
,数字都在 [1, n]
范围内(包括 1 和 n),且只有一个重复的整数。要求不修改数组且仅用 O(1) 额外空间,找出这个重复的数。
示例:
- 输入:
[1,3,4,2,2]
→ 输出2
- 输入:
[3,1,3,4,2]
→ 输出3
- 输入:
[3,3,3,3,3]
→ 输出3
2. 暴力法与优化思路
暴力法
思路:遍历数组并记录每个元素的出现次数。
步骤:
- 使用哈希表统计每个元素的出现次数。
- 找到次数大于 1 的元素。
问题:
- 空间复杂度为 O(n),不满足题目要求。
3. 二分查找法详解
核心思想
利用数组元素的范围 [1, n]
,通过二分查找缩小搜索范围:
- 对于中间值
mid
,统计数组中小于等于mid
的元素个数。 - 如果个数超过
mid
,说明重复数在左半区间;否则在右半区间。
步骤:
- 初始化
left = 1
,right = n
。 - 循环直到
left < right
:- 计算
mid = (left + right) / 2
。 - 统计数组中 ≤
mid
的元素个数count
。 - 如果
count > mid
→ 重复数在左半区间(right = mid
)。 - 否则 → 重复数在右半区间(
left = mid + 1
)。
- 计算
- 最终
left
即为重复数。
示例分析:
输入 [1,3,4,2,2]
:
- 初始
left=1
,right=4
→mid=2
。 - 统计 ≤2 的元素有
[1,2,2]
→ 3 个,超过mid=2
→ 缩小到左半区间。 - 最终找到
left=2
。
4. 快慢指针法详解
核心思想
将问题转化为链表环检测问题:
- 数组中的每个元素视为链表的指针,值为下一个节点的索引。
- 重复的数导致链表中存在环,快慢指针相遇后可找到环的入口(即重复数)。
步骤:
- 寻找相遇点:
slow
每次走一步(slow = nums[slow]
)。fast
每次走两步(fast = nums[nums[fast]]
)。- 循环直到
slow == fast
。
- 寻找入口点:
- 重置
slow
到起点(slow = 0
),fast
保持相遇点。 - 两者每次各走一步,再次相遇时的位置即为重复数。
- 重置
示例分析:
输入 [3,1,3,4,2]
:
- 初始
slow=3
(nums[0]
),fast=nums[nums[0]]=nums[3]=4
。 - 继续移动直到相遇(如
slow=3
和fast=3
)。 - 重置
slow=0
,再次移动直到相遇于3
→ 输出3
。
5. Java代码实现
二分查找法
public class Solution {
public int findDuplicate(int[] nums) {
int left = 1, right = nums.length - 1;
while (left < right) {
int mid = left + (right - left) / 2;
int count = 0;
for (int num : nums) {
if (num <= mid) count++;
}
if (count > mid) {
right = mid;
} else {
left = mid + 1;
}
}
return left;
}
}
快慢指针法
public class Solution {
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 = 0;
while (slow != fast) {
slow = nums[slow];
fast = nums[fast];
}
return slow;
}
}
6. JavaScript代码实现
二分查找法
var findDuplicate = function(nums) {
let left = 1, right = nums.length - 1;
while (left < right) {
const mid = Math.floor((left + right) / 2);
let count = 0;
for (const num of nums) {
if (num <= mid) count++;
}
if (count > mid) {
right = mid;
} else {
left = mid + 1;
}
}
return left;
};
快慢指针法
var findDuplicate = function(nums) {
let slow = nums[0];
let fast = nums[0];
// 寻找相遇点
do {
slow = nums[slow];
fast = nums[nums[fast]];
} while (slow !== fast);
// 寻找入口点
slow = 0;
while (slow !== fast) {
slow = nums[slow];
fast = nums[fast];
}
return slow;
};
7. 复杂度分析
二分查找法
- 时间复杂度:O(n log n)(每次二分需遍历数组)。
- 空间复杂度:O(1)。
快慢指针法
- 时间复杂度:O(n)(两次遍历)。
- 空间复杂度:O(1)。
8. 常见问题解答
Q1:为什么快慢指针法能找到重复数?
- 数组中的重复数导致链表环,快慢指针相遇后,从起点和相遇点出发再次相遇即为环的入口。
Q2:二分法的时间复杂度是否过高?
- 是的,但快慢指针法更优(时间复杂度更低)。
Q3:如何想到将问题转化为链表环问题?
- 数组元素的范围与索引形成映射关系,重复元素必然导致环。
9. 总结与扩展思考
总结
- 快慢指针法 是最优解,时间复杂度 O(n),空间 O(1)。
- 二分法 是另一种可行方案,但时间复杂度更高。
进阶思考
- 扩展问题:若数组中有多个重复数,如何找到所有重复数?(需其他方法,如修改数组或哈希表)。
- 其他应用:快慢指针在链表问题中的经典应用(如环检测、寻找环入口)。
推荐阅读
- LeetCode 题目:142. 环形链表 II
- 算法学习:快慢指针在数组与链表问题中的应用。
通过本篇博客,你掌握了如何用二分查找与快慢指针解决寻找重复数问题。无论是 Java 还是 JavaScript,核心逻辑一致,仅需调整语法细节即可。
LeetCode 287. 寻找重复数:Java与JavaScript双解法详解|快慢指针与弗洛伊德算法
目录
一、问题描述
给定一个包含 n + 1
个整数的数组 nums
,其中每个整数都在 [1, n]
范围内。有且只有一个重复的整数(可能重复多次),要求在不修改数组且仅使用 O(1) 额外空间的前提下找到这个重复数。
示例:
输入: [3,1,3,4,2]
输出: 3
关键要求:
- 时间复杂度 O(n)
- 空间复杂度 O(1)
- 不可修改原数组
二、核心思路:快慢指针与环检测
问题转化为链表环检测
将数组视为链表结构,其中每个元素的值表示下一个节点的索引。例如:
数组索引: 0 → 1 → 2 → 3 → 4
元素值 : 3 → 1 → 3 → 4 → 2
此时,元素值 3
重复,形成环:3 → 4 → 2 → 3 → ...
弗洛伊德算法(龟兔赛跑)
- 阶段一:找到快慢指针的相遇点
- 慢指针每次走一步:
slow = nums[slow]
- 快指针每次走两步:
fast = nums[nums[fast]]
- 慢指针每次走一步:
- 阶段二:找到环的入口点(即重复数)
- 重置一个指针从头出发,与慢指针同步移动,直到相遇
三、Java解法详解
完整代码实现
class Solution {
public int findDuplicate(int[] nums) {
// 阶段一:找到相遇点
int slow = nums[0];
int fast = nums[0];
do {
slow = nums[slow];
fast = nums[nums[fast]];
} while (slow != fast);
// 阶段二:找到入口点
int ptr1 = nums[0];
int ptr2 = slow;
while (ptr1 != ptr2) {
ptr1 = nums[ptr1];
ptr2 = nums[ptr2];
}
return ptr1;
}
}
// 测试代码
public class Main {
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(sol.findDuplicate(new int[]{3,1,3,4,2})); // 输出3
}
}
关键代码解析
- do-while循环:确保至少执行一次移动,处理初始值相同的情况
- 入口点检测:数学证明见弗洛伊德算法推导
四、JavaScript解法详解
完整代码实现
const findDuplicate = (nums) => {
// 阶段一:找到相遇点
let slow = nums[0];
let fast = nums[0];
do {
slow = nums[slow];
fast = nums[nums[fast]];
} while (slow !== fast);
// 阶段二:找到入口点
let ptr1 = nums[0];
let ptr2 = slow;
while (ptr1 !== ptr2) {
ptr1 = nums[ptr1];
ptr2 = nums[ptr2];
}
return ptr1;
};
// 测试案例
console.log(findDuplicate([3,1,3,4,2])); // 输出3
关键代码解析
- 严格相等判断:使用
!==
避免类型转换 - 循环终止条件:指针相遇即找到重复数
五、复杂度分析
阶段 | 时间复杂度 | 空间复杂度 |
---|---|---|
阶段一 | O(n) | O(1) |
阶段二 | O(n) | O(1) |
总计 | O(n) | O(1) |
六、Java与JavaScript实现对比
特性 | Java | JavaScript |
---|---|---|
循环结构 | do-while 显式声明 | 同左 |
指针初始化 | 显式赋值初始值 | 同左 |
元素访问 | 数组索引从0开始 | 同左 |
代码结构 | 类方法封装 | 函数式实现 |
七、常见错误与解决方法
错误1:忽略初始移动
// 错误:使用while而非do-while
while (slow != fast) { ... }
后果:初始值相同时直接跳过循环
解决:必须使用 do-while
错误2:错误初始化指针
// 错误:ptr2未从slow开始
let ptr2 = fast;
后果:可能无法找到正确入口点
解决:严格按算法步骤实现
错误3:处理全相同元素
输入: [3,3,3,3]
输出: 3
验证:算法能正确处理全相同元素的极端情况
八、举一反三练习
- 变形题:142. 环形链表 II(链表环入口检测)
- 进阶题:141. 环形链表(环存在性判断)
- 思维拓展:如何统计重复次数?
学习建议:
在Chrome开发者工具中单步调试,观察指针移动路径,加深对环检测过程的理解。
掌握弗洛伊德算法的关键在于理解链表环的数学本质。这种将数组抽象为链表结构的思维方式,为解决复杂问题提供了全新的视角。当下次遇到类似限制条件的问题时,不妨尝试这种“化静为动”的巧妙转换! 🔥
力扣287题详解:寻找重复数(Java & JavaScript 双语言实现)
目录
1. 问题描述
给定一个包含 n + 1
个整数的数组 nums
,其数字都在 [1, n]
范围内(包括 1
和 n
),可知至少存在一个重复的整数。要求:
- 不修改原数组
- 只使用常量级
O(1)
的额外空间 - 时间复杂度低于
O(n²)
示例:
输入:nums = [3,1,3,4,2]
输出:3
2. 核心思路
3. 解法一:快慢指针法(Java & JavaScript)
原理
将数组值视为链表节点的指针(如 nums[i]
表示节点 i
的下一个节点)。重复元素会导致链表出现环,环的入口即为重复数。
Java实现
public class Solution {
public int findDuplicate(int[] nums) {
int slow = nums;
int fast = nums[nums];
// 第一次相遇
while (slow != fast) {
slow = nums[slow];
fast = nums[nums[fast]];
}
// 从头开始找环入口
int entry = 0;
while (entry != slow) {
entry = nums[entry];
slow = nums[slow];
}
return entry;
}
}
JavaScript实现
var findDuplicate = function(nums) {
let slow = nums;
let fast = nums[nums];
while (slow !== fast) {
slow = nums[slow];
fast = nums[nums[fast]];
}
let entry = 0;
while (entry !== slow) {
entry = nums[entry];
slow = nums[slow];
}
return entry;
};
4. 解法二:二分查找法(Java & JavaScript)
原理
在 [1, n]
范围内二分查找中间值 mid
,统计数组中 小于等于 mid
的元素个数:
- 若个数
> mid
,说明重复数在左半区间[left, mid]
- 否则,重复数在右半区间
[mid+1, right]
Java实现
public class Solution {
public int findDuplicate(int[] nums) {
int left = 1, right = nums.length - 1;
while (left < right) {
int mid = left + (right - left) / 2;
int count = 0;
for (int num : nums) {
if (num <= mid) count++;
}
if (count > mid) right = mid;
else left = mid + 1;
}
return left;
}
}
JavaScript实现
var findDuplicate = function(nums) {
let left = 1, right = nums.length - 1;
while (left < right) {
const mid = Math.floor((left + right) / 2);
let count = 0;
for (const num of nums) {
if (num <= mid) count++;
}
if (count > mid) right = mid;
else left = mid + 1;
}
return left;
};
5. 复杂度分析
方法 | 时间复杂度 | 空间复杂度 |
---|---|---|
快慢指针法 | O(n) | O(1) |
二分查找法 | O(n log n) | O(1) |
参考资料