[leetcode 剑指offer系列] 面试题03. 数组中重复的数字

题目难度: 简单

原题链接

今天我们开始一个全新的主题, 那就是 leetcode 的剑指 offer 系列, 这个系列大部分题目的出现频率都相当高, 必刷, 必刷, 必刷 👏👏👏 (重要的事情说三遍)

若无意外, 每天晚上 6 点 45 分准时更新, 中间可能会穿插一些周赛系列 (如果我参加的了话 😂). 大家在我的公众号"每日精选算法题"中的聊天框中回复 offer 就能看到该系列当前已经更新的文章了

大家有什么想法建议和反馈的话欢迎随时交流, 包括但不限于公众号聊天框/知乎私信评论等等~

题目描述

找出数组中重复的数字。

在一个长度为 n 的数组 nums 里的所有数字都在 0 ~ n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。

2 <= n <= 100000

题目样例

示例

输入

[2, 3, 1, 0, 2, 5, 3]

输出

2 或 3

题目思考

  1. 题目中给出了每个数的取值范围, 这对我们有什么启发?

解决方案

思路

  • 分析
    • 相信大家都能想到使用集合存储已有数字的办法, 就是遍历的过程中如果发现数字已在集合就说明该数字重复, 否则就将数字加入集合.
    • 但这样一是需要 O(N)空间, 二是完全利用不到每个数的取值范围为 0~n-1 这一条件, 肯定不是面试官心里的最优答案
    • 时间复杂度 O(N)应该是没法降低了, 因为最差情况就是最后两个数字是重复的, 我们至少需要扫描整个数组一遍
    • 所以我们只能在空间复杂度上做文章, 看能否降低到 O(1) 空间
  • 实现
    • 注意数字范围在0~n-1之间, 这说明每个数都可以放到等同于其自身值的下标中
    • 所以做法很简单, 我们遇到一个值 nums[i] 不等于自身下标 i 的数时, 我们就将 nums[i] 作为下标的值(也即nums[nums[i]])与当前值进行交换, 这样就保证nums[i] = nums[nums[i]]; 然后对于新的 nums[i], 我们继续这一过程, 直到 nums[i] 也等于 i 为止, 这样就保证了下标和值相等. 继续往下遍历 i, 重复这一过程直到发现重复数字为止
    • 至于如何发现重复也很简单, 如果本身就有nums[i] == nums[nums[i]]的话, 自然说明 nums[i] 就是重复项 (因为i!=nums[i]是前提条件), 直接返回nums[i]即可
    • 注意题目保证了一定有重复, 所以最后外层循环之外一定不可达 (否则就没重复了), 不需要 return; 如果题目改成不存在重复就返回-1, 那直接在外层循环结束后加上return -1即可
    • 下面代码中对每一步都有详细注释, 方便大家理解

复杂度

  • 时间复杂度 O(N)
    • 每个数字最多被访问两次(判断值和下标是否相等, 以及交换使得值和下标相等), 所以这里虽然看上去有两重循环(外层 for, 内层 while), 时间复杂度却为 O(N)
  • 空间复杂度 O(1)
    • 原地更改数组, 只使用了几个变量

代码

class Solution:
    def findRepeatNumber(self, nums: List[int]) -> int:
        for i in range(len(nums)):
            # 从头开始遍历数组
            while i != nums[i]:
                # 当前下标i和值nums[i]不相等, 进入/继续内层循环
                j = nums[i]
                if nums[i] == nums[j]:
                    # 前提条件: i!=j, 所以nums[i]和nums[j]是数组的两个数字, 它们值相等, 即为重复数字
                    return nums[i]
                # 交换两个值, 使得nums[j] == j, 这样可以继续循环判断i和新的nums[i]
                nums[i], nums[j] = nums[j], nums[i]

大家可以在下面这些地方找到我~😊

我的知乎专栏

我的 CSDN

我的 Leetcode

我的牛客网博客

我的公众号: 每日精选算法题, 欢迎大家扫码关注~😊

每日精选算法题 - 微信扫一扫关注我

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值