【C/C++】961. 在长度 2N 的数组中找出重复 N 次的元素

145 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第15天,点击查看活动详情


题目链接:961. 在长度 2N 的数组中找出重复 N 次的元素

题目描述

给你一个整数数组 nums ,该数组具有以下属性:

  • nums.length == 2 * n
  • nums 包含 n + 1不同的 元素
  • nums 中恰有一个元素重复 n 次 找出并返回重复了 n 次的那个元素。

提示:

  • 2n50002 \leqslant n \leqslant 5000
  • nums.length==2nnums.length == 2 * n
  • 0nums[i]1040 \leqslant nums[i] \leqslant 10^4
  • numsn + 1不同的 元素组成,且其中一个元素恰好重复 n

示例 1:

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

示例 2:

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

示例 3:

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

整理题意

题目给定一个整数数组 nums ,且告诉我们数组中包含 2n 个元素,其中有 n + 1 个不同的元素,且其中一个元素恰好出现 n 次,让我们找到出现 n 次的这个元素并返回。

解题思路分析

首先观察题目数据范围,数组最长为 5000 且数据范围不超过 int

方法一:哈希表

根据题目数据范围以及题目要求,很容易想到使用哈希表的做法来统计每个数出现的次数,最后返回出现 n 次的数即可。

优化: 因为题目规定在 2n 个元素中存在 n + 1 个不同元素,且其中一个元素恰好出现 n 次,那么意味着数组中有一个元素重复了 n 次,其余元素只出现了 1 次,那么当我们找到出现 2 次的元素即可输出答案,优化后我们可以使用 set 容器来存储,只需判断 set 中是否存在当前元素即可。

方法二:数学

我们尝试构造题目规定的数组:

2n.jpg

可以发现利用 n 个相同元素和 n 个互不相同的元素来构造这个数组,为了使得每个相同元素之间的间隔最大,我们利用 n 个互不相同的元素将相同的元素两两隔开,最后剩余一个不同元素可以放任意位置。此时我们发现两个相同的元素间隔最多不会超过 2 ,如果某两个相同元素之间间距超过 2 ,必定有两个相同元素相邻,根据这个规律,我们只需要遍历每个元素后面 3 个元素是否存在与当前元素相等即可。

2n (1).jpg

方法三:概率期望

由于总共有 2n 个元素,其中有 n 个元素相同,我们从中任意取两个元素相同的概率为 n2n×n12n14\dfrac{n}{2n} \times \dfrac{n-1}{2n} \approx \dfrac{1}{4},通过概率期望可得,我们每次从数组中任取两个元素,取到相同的元素的期望为 4 次这个操作。

具体实现

方法一:哈希表

  1. 使用哈希表 unordered_map(因为这里不考虑元素有序问题,采用 unordered_map 效率更高)。
  2. 遍历数组,记录每个元素出现次数:mp[nums[i]] + 1
  3. 输出 mp[nums[i]] == n 的元素即可。 优化:
  4. 使用 unordered_set(同样不考虑有序问题)
  5. 遍历数组,每次判断 set 集合中是否存在当前元素:set.count(nums[i])
    • 存在:输出当前元素;
    • 不存在:将当前元素放入集合; 复杂度分析
  • 时间复杂度:O(n)O(n)。我们只需要对数组 nums 进行一次遍历。
  • 空间复杂度:O(n)O(n),即为哈希集合需要使用的空间。

方法二:数学

  1. 遍历数组中每个元素
  2. 查看后面三个元素是否与当前元素相同:
    • 相同:输出当前元素
    • 不相同:继续遍历 复杂度分析
  • 时间复杂度:O(n)O(n)。我们最多对数组进行三次遍历(除了 n = 2 之外,最多两次遍历)。
  • 空间复杂度:O(1)O(1),无需额外存储空间。

方法三:概率期望

  1. 采用随机函数 rand(),随机取得数组中 [0, n - 1] 不相同的两个下标。
  2. 判断是否相同:
    • 相同:输出当前元素;
    • 不相同:继续随机选取下标。

需要注意随机函数的初始化问题:srand((unsigned)time(NULL)); 复杂度分析

  • 时间复杂度:O(1)O(1)。概率期望为 4 次。
  • 空间复杂度:O(1)O(1),无需额外存储空间。

代码实现

方法一:哈希表

class Solution {
public:
    int repeatedNTimes(vector<int>& nums) {
        unordered_map<int, int> mp;
        mp.clear();
        int n = nums.size();
        //遍历数组
        for(int i = 0; i < n; i++){
            //记录数组中每个元素出现的次数
            mp[nums[i]]++;
            //输出出现n次的元素
            if(mp[nums[i]] == n / 2) return nums[i];
        }
        return 0;
    }
};

set 集合优化:

class Solution {
public:
    int repeatedNTimes(vector<int>& nums) {
        unordered_set<int> s;
        s.clear();
        int n = nums.size();
        //遍历数组
        for(int i = 0; i < n; i++){
            //查看nums[i]是否在集合中
            if(s.count(nums[i])) return nums[i];
            s.insert(nums[i]);
        }
        return -1;
    }
};

方法二:数学

class Solution {
public:
    int repeatedNTimes(vector<int>& nums) {
        int n = nums.size();
        //遍历数组
        for(int i = 0; i < n; i++){
            //查看当前元素后三个
            for(int j = 1; j <= 3; j++){
                //注意下标不要越界
                if(i + j < n && nums[i] == nums[i + j]) return nums[i];
            }
        }
        return -1;
    }
};

方法三:概率期望

class Solution {
public:
    int repeatedNTimes(vector<int>& nums) {
        //初始化随机函数
        srand((unsigned)time(NULL));
        int n = nums.size();
        while(1){
            //随机选取两个下标
            int x = rand() % n;
            int y = rand() % n;
            //注意下标不能相同
            if(x != y && nums[x] == nums[y]) return nums[x];
        }
    }
};

总结

该题虽然是简单题,可以使用最暴力的哈希表计数通过,但在复杂度方面并不是最优的。

根据题目对数组的限制要求,我们可以通过数学构造以及概率期望等方法对复杂度进行优化,对比优化前后的效率差异还是很明显的: 微信截图_20220521153327.png

核心在于对题目的解读上,总共 2n 个元素,其中有 n 个相同的元素以及 n 个互不相同的元素。

结束语

台上一分钟,台下十年功,如果你期待在未来有所成就,就必须付出成倍的努力。无数光鲜亮丽的背后,都是日复一日、年复一年的坚持。今天的你,比别人多一点执着,就会多一点机会;比别人多一点坚持,就会多一点收获。