LeetCode剑指offer 03 数组中的重复数字(c++)


题目要求

找出数组中重复的数字。

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

示例:
输入:[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof

下面展示的解法代表了我对题目的处理思路——首先是自己思考的解法(可能是暴力解法),然后综合leetcode部分题解和论坛上的其他回答写出的较优化解法。


一、基础解法——排序后比较相邻元素

拿到题目第一瞬间想到的就是排序后看相邻两元素是否相等,比如示例数组排序后: {0, 1, 2, 2, 3, 3, 5}。依次遍历数组中的元素,并比较该元素(i)与相邻元素(i+1),代码如下:

int findRepeatNumber_m01(vector<int>& nums) 
{
	//标准库函数排序
	sort(nums.begin(),nums.end());
	//遍历到倒数第二位
	for (int i = 0; i < nums.size() - 1; i++)
	{
		if (nums[i] == nums[i+1])
		{
			//返回
			return nums[i];
		}
	}
	return -1;
}

在这里插入图片描述

思路比较简单,只是需要在完成排序的基础上,再进行一次遍历,时间复杂度是排序的复杂度N(nlogn)。但是很明显,该方法一个显著的缺陷是会破坏原有数组的原有顺序。针对这个缺陷,可以应用辅助数组来改进,但会造成额外的开销(空间占用,数组拷贝)。

二、取巧做法——原地置换

针对本道题,其实本道题给的条件很特殊,即数据的大小都小于数组的长度,且只需要返回某一个重复的值,所以没有必要对数组整体做排序。

原地置换的核心思想就是为每个数据找寻他的“正确”位置,即将nums[i]这个数据放到nums[i]为下标的位置,而原先在nums[i]为下标的位置的数据放到下标为i的位置(nums[i]数据原先所在的位置),即交换nums[i]与nums[nums[i]]。当然如果nums[i]刚巧就在“正确”的位置(nums[i] = i),则不需要执行操作,而继续对下一个位置的元素做该检测。

代码如下:

int findRepeatNumber_m03(vector<int>& nums)
{
	for (int i = 0; i < nums.size(); i++)
	{
		while (nums[i] != i) 
		{
			if (nums[i] == nums[nums[i]])
			{
				return nums[i];
			}
			swap(nums[i], nums[nums[i]]);
		}
	}
	return -1;
}

在这里插入图片描述
时间复杂度:遍历需要O(n),同时需要O(1)来做交换。

三、通用做法——哈希法

通过构建哈希表来解决这个问题,通过监视键值对(key-value)中的value值可以第一时间发现重复元素。

对数组{2, 3, 1, 0, 2, 5, 3}构建哈希表的构建过程如下:

keyvalue
21
31
11
01

在遇到重复的第一个2后,返回此时的键值key

keyvalue
22
31
11
01

示例代码如下:

int findRepeatNumber_m02(const vector<int>& nums)
{
	unordered_map<int, int> hsm;
	for (int i = 0; i < nums.size() -1; i++)
	{
		hsm[nums[i]]++;
		if (hsm[nums[i]] > 1)
		{
			return nums[i];
		}
	}
	return -1;
}

在这里插入图片描述
时间复杂度O(n)。

四、完整测试代码

#include <iostream>
#include <vector>
#include <unordered_map>
#include <algorithm>
using namespace std;

//打印方法
void print(const vector<int> &v)
{
	for (auto item: v)
	{
		cout << item << "\t";
	}
	cout << endl;
}

//方法一:排序后比较相邻数字
int findRepeatNumber_m01(vector<int>& nums) 
{
	sort(nums.begin(),nums.end());
	for (int i = 0; i < nums.size() - 1; i++)
	{
		if (nums[i] == nums[i+1])
		{
			return nums[i];
		}
	}
	return -1;
}

//方法二:哈希法
int findRepeatNumber_m02(const vector<int>& nums)
{
	unordered_map<int, int> hsm;
	for (int i = 0; i < nums.size(); i++)
	{
		hsm[nums[i]]++;
		if (hsm[nums[i]] > 1)
		{
			return nums[i];
		}
	}
	return -1;
}

//方法三:原地置换
int findRepeatNumber_m03(vector<int>& nums)
{
	for (int i = 0; i < nums.size(); i++)
	{
		while (nums[i] != i) 
		{
			if (nums[i] == nums[nums[i]])
			{
				return nums[i];
			}
			swap(nums[i], nums[nums[i]]);
		}
	}
	return -1;
}

int main()
{
	/*
	测试用例
		输入:[2, 3, 1, 0, 2, 5, 3]
		输出:2 或 3
	*/

	vector<int> vTest = { 2,3,1,0,2,5,3 };

	//方法一:排序后比较相邻数字
	//cout << findRepeatNumber_m01(vTest) << endl;
	//print(vTest);

	//方法二:哈希法
	cout << findRepeatNumber_m02(vTest) << endl;

	//方法三:原地置换
	//cout << findRepeatNumber_m03(vTest) << endl;

	system("pause");
	return 0;
}

总结

简单题其实不简单,原地交换的做法是建立在对数据特性有着清晰认识的基础上,所以哈希法是我认为这类型题的通用解法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Claude的羽毛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值