挑战刷leetcode第25天(寻找重复数)

99 阅读4分钟

哪吒大战“重复数”:一场算法与幽默的奇妙冒险

大家好,我是哪吒,今天我要带大家进入一个充满挑战的算法世界——寻找重复数!别担心,虽然题目听起来有点枯燥,但我会用我的幽默和智慧,让这场冒险变得有趣又充满知识增量!


题目描述:重复数的“阴谋”

我们有一个数组 nums,里面有 n + 1 个整数,范围是 [1, n]。这意味着至少有一个数字是重复的。我们的任务就是找到这个“狡猾”的重复数,而且不能修改数组,只能用常量级的额外空间。

举个例子:

  • 输入:nums = [1,3,4,2,2]
  • 输出:2

看起来简单吧?但别急,这个题目可是有点“心机”的!接下来,我会用我的“风火轮”和“混天绫”来揭开它的秘密。


解题思路:哪吒的“环形链表”法宝

这道题的难点在于不能修改数组,而且只能用常量级的额外空间。这意味着我们不能用哈希表或者排序这种“暴力”方法。那怎么办呢?别急,哪吒有法宝——环形链表

1. 数组到链表的“魔法转换”

首先,我们需要把数组转换成链表的形式。怎么转换呢?我们可以把数组的下标和值看作链表的节点和指针。比如,数组 nums = [1,3,4,2,2],我们可以建立如下映射关系:

  • 0 -> 1
  • 1 -> 3
  • 2 -> 4
  • 3 -> 2
  • 4 -> 2

这样,我们从下标 0 出发,根据映射关系 f(n) = nums[n],可以生成一个链表:
0 -> 1 -> 3 -> 2 -> 4 -> 2 -> 4 -> 2 -> ……

看到没?这里出现了一个环!因为数字 2 和 4 重复指向彼此,形成了一个环路。

2. 环形链表的“秘密”

既然数组中存在重复数,那么生成的链表就一定会有一个环。那么问题就变成了:如何找到这个环的入口?因为环的入口就是我们要找的重复数!

这让我想起了另一道题——142. 环形链表 II。在那道题中,我们需要找到链表中环的入口。而这道题,本质上是一样的!所以我们可以用同样的方法来解决。

 1. 快慢指针的“初次相遇”

想象一下,数组中的每个数字都是一个“传送门”,指向另一个位置。比如,nums[0] = 1 表示从位置 0 可以传送到位置 1。如果数组中有重复数,那么这个“传送门”就会形成一个环。

我们可以用两个指针,一个慢指针(slow)和一个快指针(fast)。慢指针每次走一步,快指针每次走两步。如果存在环,快慢指针最终一定会相遇。

int slow = 0;
int fast = 0;
slow = nums[slow];          // 慢指针走一步
fast = nums[nums[fast]];    // 快指针走两步

while (slow != fast) {
    slow = nums[slow];      // 慢指针继续走一步
    fast = nums[nums[fast]]; // 快指针继续走两步
}

2. 寻找环的入口

当快慢指针相遇时,说明环存在。但我们需要找到环的入口,也就是重复的数。这里有一个巧妙的数学原理:从起点到环入口的距离,等于从相遇点到环入口的距离

所以,我们可以让一个指针从起点出发,另一个指针从相遇点出发,两个指针每次都走一步,最终它们会在环的入口相遇,这个入口就是重复的数!

int pre = 0;
int pre1 = slow;
while (pre != pre1) {
    pre = nums[pre];    // 从起点出发
    pre1 = nums[pre1];  // 从相遇点出发
}
return pre;  // 返回重复的数

代码实现:哪吒的“风火轮”代码

以下是 Java 和 C++ 的实现代码,大家可以拿去直接用,就像我的风火轮一样,又快又稳!

Java 版本:

public int findDuplicate(int[] nums) {
    int slow = 0;
    int fast = 0;
    slow = nums[slow];
    fast = nums[nums[fast]];

    while (slow != fast) {
        slow = nums[slow];
        fast = nums[nums[fast]];
    }
    int pre = 0;
    int pre1 = slow;
    while (pre != pre1) {
        pre = nums[pre];
        pre1 = nums[pre1];
    }
    return pre;
}

C++ 版本:

int findDuplicate(vector<int>& nums) {
    int slow = 0;
    int fast = 0;
    slow = nums[slow];
    fast = nums[nums[fast]];

    while (slow != fast) {
        slow = nums[slow];
        fast = nums[nums[fast]];
    }
    int pre = 0;
    int pre1 = slow;
    while (pre != pre1) {
        pre = nums[pre];
        pre1 = nums[pre1];
    }
    return pre;
}

坚持的意义:哪吒的“修行”心得

在算法的世界里,坚持是最重要的。就像我修炼风火轮和混天绫一样,一开始可能会觉得很难,但只要不断练习,终会掌握其中的奥妙。

这道题的解法看似简单,但背后蕴含了深刻的数学原理和算法思想。通过这道题,我学会了如何用双指针解决环的问题,也理解了递归的思维方式。每一次解题,都是一次成长。


结语:哪吒的“胜利”宣言

好了,今天的冒险就到这里!希望大家通过这篇文章,不仅学会了如何解决“寻找重复数”的问题,还能感受到算法的乐趣。记住,无论多难的题目,只要坚持,就一定能找到答案!

我是哪吒,下次再见!记得点赞、收藏、转发哦!