1.数组介绍
首先对数组进行基本的介绍,数组的特点就是连续存储。在C++中,我们一般有两种类型的数组:一种是静态数组,我们事先知道其大小。一种是动态数组,也就是vector,事先可以不规定大小。
2.题目介绍
这一类型题目有很多种类型,比如数组是否有序,重复元素的个数等等。
2.1 找出数组中的重复元素
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。
根据题意,我们可以知道数组元素的范围是确定的,以及最后的要求是输出任意一个元素。
2.1.1排序,然后遍历
已知数组是无序的(没有说有序就是无序),我们可以先对数组进行排序,比如 { 2 , 3 , 1 , 0 , 2 , 5 , 3 } \{2,3,1,0,2,5,3\} {2,3,1,0,2,5,3},排序之后得到 { 0 , 1 , 2 , 2 , 3 , 3 , 5 } \{0,1,2,2,3,3,5\} {0,1,2,2,3,3,5},这样一来,我们就可以通过判断相邻元素是否相等,得到重复元素。这种时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn),空间复杂度为 O ( 1 ) O(1) O(1)。因为排序是在数组上进行的。代码如下:
bool duplicate1(int numbers[], int length, int* duplication) {
sort(numbers,numbers+length);
bool flag=false;
for(int i=0;i<length-1;i++)
{
if(numbers[i]==numbers[i+1])
{
*duplication= numbers[i];
flag=true;
break;
}
}
return flag;
}
2.1.2 使用哈希表
因为数组中的每个元素都在确定的范围里,并且都为正数。所以我们可以直接使用数组当作哈希表。因此我们额外创建一个长度为n的数组,使用输入数组的元素作为下标。以 n u m = { 2 , 3 , 1 , 0 , 2 , 5 , 3 } num=\{2,3,1,0,2,5,3\} num={2,3,1,0,2,5,3}为例,我们初始化一个全是0的哈希数组, { 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 } \{0,0,0,0,0,0,0,0\} {0,0,0,0,0,0,0,0},我们最终要求的哈希表是 { 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 } \{0,1,2,3,4,5,6,7\} {0,1,2,3,4,5,6,7},即 h a s h [ i ] = i hash[i]=i hash[i]=i;所以当 h a s h [ n u m [ i ] ] ! = n u m [ i ] hash[num[i]]!=num[i] hash[num[i]]!=num[i]的时候,我们就将对应元素放入哈希表。比如, n u m [ 0 ] = 2 num[0]=2 num[0]=2; h a s h [ 2 ] hash[2] hash[2]此时为0,所以将 h a s h [ 2 ] = 2 hash[2]=2 hash[2]=2;当我们遍历到 n u m [ 4 ] num[4] num[4]的时候,我们发现此时 h a s h [ 2 ] hash[2] hash[2]已经等于2了,则说明之前已经遇到过2了,那么2为重复元素。空间复杂度为 O ( n ) O(n) O(n),时间复杂度为 O ( n ) O(n) O(n).
bool duplicate2(int numbers[], int length, int* duplication) {
bool flag=false;
//array initialize
int hash_t[length];
for(int i=0;i<length;i++)
{
hash_t[i]=0;
}
for(int i=0;i<length;i++)
{
if(hash_t[numbers[i]]!=0)
{
flag=true;
*duplication=numbers[i];
break;
}
else
hash_t[numbers[i]]=numbers[i];
}
return flag;
}
2.1.3 手动重排数组
使用哈希表,空间复杂度过高;使用排序,时间复杂度过高。因为采取一种折中的办法,通过交换手动将输入数组排序为哈希表的样子,也就是把每个数组元素放在以该元素为下标的位置。
- { 2 , 3 , 1 , 0 , 2 , 5 , 3 } \{2,3,1,0,2,5,3\} {2,3,1,0,2,5,3} 对于第0个位置,num[0]!=0,我们不知道0在哪里,但是我们知道num[0]=2 这个2应该放在哪里。所以我们先查看第2个位置的值,发现第二个位置的值为1,不为2,因此我们交换第0个位置的元素和第二个位置的元素,即 { 1 , 3 , 2 , 0 , 2 , 5 , 3 } \{1,3,2,0,2,5,3\} {1,3,2,0,2,5,3}
- 交换之后,其实第0个位置的元素依然不符合要求,因此我们进行如上操作,交换第0个位置和第1个位置的元素,即 { 3 , 1 , 2 , 0 , 2 , 5 , 3 } \{3,1,2,0,2,5,3\} {3,1,2,0,2,5,3}
- 依然不符合要求,进行操作,即 { 0 , 1 , 2 , 3 , 2 , 5 , 3 } \{0,1,2,3,2,5,3\} {0,1,2,3,2,5,3}
- 操作完成之后,发现第0~3位置都符合要求,因此我们对于第4个位置元素进行操作,第4个位置元素为2,我们查看发现此位置的元素已经是2了,也就是说明此时不需要交换了,出现了一个多余的2。依次类推,可以找出所有多余元素。
时间复杂度为 O ( n ) O(n) O(n),空间复杂度 O ( 1 ) O(1) O(1).
bool duplicate3(int numbers[], int length, int* duplication) {
bool flag=false;
for(int i=0;i<length;i++)
{
while(numbers[i]!=i)
{
if(numbers[numbers[i]]==numbers[i])//satisfied hash condition
{
flag=true;
*duplication=numbers[i];
return flag;
}
//exchange
int temp=numbers[i];
numbers[i]=numbers[temp];
numbers[temp]=temp;
}
}
return flag;
}
第三种方法效率最高,但是修改了数组本身,如果题目要求不修改数组呢?
2.2 不修改数组找到重复数字(Leetcode.287寻找重复数)
给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。
示例 1:
输入: [1,3,4,2,2]
输出: 2
示例 2:
输入: [3,1,3,4,2]
输出: 3
说明:
不能更改原数组(假设数组是只读的)。
只能使用额外的 O(1) 的空间。
时间复杂度小于 O(n2) 。
数组中只有一个重复的数字,但它可能不止重复出现一次。
2.2.1 使用hash表
因为给定了数组元素的范围,所以可以直接使用数组做hash表。和上面一样,有 O ( n ) O(n) O(n)的空间开销。
int findDuplicate(vector<int>& nums) {
int n=nums.size();
int result;
vector<int>hash_n(n,0);
for(int i=0;i<n;i++)
{
if(hash_n[nums[i]]!=0)
{
result=nums[i];
break;
}
else
{
hash_n[nums[i]]=nums[i];
}
}
return result;
}
2.2.2 使用二分查找
因为不可以修改数组,所以我们应该就是查找重复元素。并且只有一个重复元素,所以目标就是找到那个重复元素。
因为所有元素都在1-n之间,所以我们可以划分前后两个区间,比如n=5,我们分成两份1-2,3-5,我们统计所有元素在这两个区间的个数,以 [ 1 , 3 , 4 , 2 , 2 ] [1,3,4,2,2] [1,3,4,2,2]为例,在[1,2]之间的有3个,但是区间长度为2,说明那个重复元素在[1,2]之间。然后再次进行划分,因为长度为1,也就是统计1,2的次数,可以找到重复元素为2. 时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn),空间复杂度为 O ( 1 ) O(1) O(1)。
public:
int CountNumber(vector<int>& nums,int start,int end_)
{
int n=nums.size();
int ans=0;
for(int i=0;i<n;i++)
{
if(nums[i]>=start&&nums[i]<=end_)
++ans;
}
return ans;
}
int findDuplicate(vector<int>& nums) {
int length=nums.size();
int start=1;
int end_=length;
while(start<=end_)
{
int mid=(start+end_)/2;
int count_s_m=CountNumber(nums,start,mid);
if(end_==start&&count_s_m>1)
return start;
if(count_s_m>mid-start+1)
end_=mid;
else
start=mid+1;
}
return -1;
}