持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第15天,点击查看活动详情
题目描述
给你一个整数数组 nums ,该数组具有以下属性:
nums.length == 2 * nnums包含n + 1个 不同的 元素nums中恰有一个元素重复n次 找出并返回重复了n次的那个元素。
提示:
nums由n + 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 中是否存在当前元素即可。
方法二:数学
我们尝试构造题目规定的数组:
可以发现利用 n 个相同元素和 n 个互不相同的元素来构造这个数组,为了使得每个相同元素之间的间隔最大,我们利用 n 个互不相同的元素将相同的元素两两隔开,最后剩余一个不同元素可以放任意位置。此时我们发现两个相同的元素间隔最多不会超过 2 ,如果某两个相同元素之间间距超过 2 ,必定有两个相同元素相邻,根据这个规律,我们只需要遍历每个元素后面 3 个元素是否存在与当前元素相等即可。
方法三:概率期望
由于总共有 2n 个元素,其中有 n 个元素相同,我们从中任意取两个元素相同的概率为 ,通过概率期望可得,我们每次从数组中任取两个元素,取到相同的元素的期望为 4 次这个操作。
具体实现
方法一:哈希表
- 使用哈希表
unordered_map(因为这里不考虑元素有序问题,采用unordered_map效率更高)。 - 遍历数组,记录每个元素出现次数:
mp[nums[i]] + 1; - 输出
mp[nums[i]] == n的元素即可。 优化: - 使用
unordered_set(同样不考虑有序问题) - 遍历数组,每次判断
set集合中是否存在当前元素:set.count(nums[i])- 存在:输出当前元素;
- 不存在:将当前元素放入集合; 复杂度分析
- 时间复杂度:。我们只需要对数组
nums进行一次遍历。 - 空间复杂度:,即为哈希集合需要使用的空间。
方法二:数学
- 遍历数组中每个元素
- 查看后面三个元素是否与当前元素相同:
- 相同:输出当前元素
- 不相同:继续遍历 复杂度分析
- 时间复杂度:。我们最多对数组进行三次遍历(除了
n = 2之外,最多两次遍历)。 - 空间复杂度:,无需额外存储空间。
方法三:概率期望
- 采用随机函数
rand(),随机取得数组中[0, n - 1]不相同的两个下标。 - 判断是否相同:
- 相同:输出当前元素;
- 不相同:继续随机选取下标。
需要注意随机函数的初始化问题:
srand((unsigned)time(NULL));复杂度分析
- 时间复杂度:。概率期望为
4次。 - 空间复杂度:,无需额外存储空间。
代码实现
方法一:哈希表
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];
}
}
};
总结
该题虽然是简单题,可以使用最暴力的哈希表计数通过,但在复杂度方面并不是最优的。
根据题目对数组的限制要求,我们可以通过数学构造以及概率期望等方法对复杂度进行优化,对比优化前后的效率差异还是很明显的:
核心在于对题目的解读上,总共 2n 个元素,其中有 n 个相同的元素以及 n 个互不相同的元素。
结束语
台上一分钟,台下十年功,如果你期待在未来有所成就,就必须付出成倍的努力。无数光鲜亮丽的背后,都是日复一日、年复一年的坚持。今天的你,比别人多一点执着,就会多一点机会;比别人多一点坚持,就会多一点收获。