题目来自于网上
题目:
一个大小为n的数组,元素范围为1-n,但是其中一些数组有重复,同时另一些数据丢失了。 现在要求找出其中的重复数据和丢失的数据。
要求:
时间O(n),空间O(1),即,只能遍历常数次数组,只能使用常数个临时空间
思想:
利用计数排序的思想,把数组的第i个数对应着数组i,并统计其出现次数。原始的计数排序需要O(n)的临时空间。这里要求空间复杂度为O(n),即使用传统的计数排序是不行的。这里可以使用原数组的计数排序。
基于原数组的计数排序的思想:
当处理数组第i个数arr[i]时,
(1)如果元素arr[arr[i]]放的数不是arr[i],即arr[i]没有放到它应该在的位置。则此时把arr[i]放到arr[arr[i]]的位置上。于此同时,还需要把原来arr[arr[i]]存放的元素放到arr[i],之后继续处理元素arr[i](此时的arr[i]是刚换过来的元素)。一直到arr[i]存放的元素是arr[arr[i]]为止。之后继续处理第i+1个数。
注意,每进行一次元素互换都使一个数放到正确的位置上。
(2)如果元素arr[arr[i]]放的数已经是arr[i],则表示arr[i]已经在它正确的位置上了。之后继续处理第i+1个数。
(3)重复执行(1)和(2),一直到所有的元素均处理完毕。
注意:直到arr[i]放到arr[arr[i]]上时,i才会++。
结果分析:
对于数组中的数据:
如果数组 i 位置放的是a[i],即,a[i]放到了正确的位置。
如果数组 i 位置放的不是a[i],但是程序结束的情况是所有的a[i]均放到起正确的位置(a[a[i]])。
则,对于位置i,肯定是在该数组中没出现i这个数。
对于a[i],由于这个a[i]未放到正确的位置(a[a[i]]),说明之前已经有a[i]放到正确的位置(a[a[i]])上了,则表示该数a[i]重复了。
举例:
待处理的数组:5 1 2 2 3
i = 1,处理a[1] = 5时,它本应该在的位置存放的是3,则互换位置,即 3 1 2 2 5
i = 1,处理a[1] = 3时,它本应该在的位置存放的是2,则互换位置,即 2 1 3 2 5
i = 1,处理a[1] = 2时,它本应该在的位置存放的是1,则互换位置,即 1 2 3 2 5
i = 1,处理a[1] = 1时,它本应该在的位置存放的是1,则位置正确,则i++
i = 2,处理a[2] = 2时,它本应该在的位置存放的是2,则位置正确,则i++
i = 3,处理a[3] = 3时,它本应该在的位置存放的是3,则位置正确,则i++
i = 4,处理a[4] = 2时,它本应该在的位置存放的是2,则位置正确,且此时的数应该是重复的。此时i++
i = 5,处理a[5] = 5时,它本应该在的位置存放的是5,则位置正确,此时i++
代码:
#include <iostream>
#include <assert.h>
using namespace std;
void FindNum(int arr[],int nLen,int nStart,int nEnd)
{
assert(arr != NULL && nStart <= nEnd && nStart >= 0 && nEnd >= 0);
int nTmp = 0;
for (int i = 1;i <= nLen;i++)
{
assert(arr[i] >= 1 && arr[i] <= nLen);
while (arr[arr[i]] != arr[i])
{
//arr[i]放的位置不对,则需要把Arr[i]放到正确的位置
//此时需要把 arr[i] 放到他原本的位置arr[arr[i]]上
//注意这里肯不是直接覆盖,而是替换
nTmp = arr[arr[i]];
arr[arr[i]] = arr[i];
arr[i] = nTmp;
}
}
for (int i = nStart;i <= nLen;i++)
{
if (i != arr[i])
{
cout<<"缺失的数: "<<i<<endl;
cout<<"重复的数: "<<arr[i]<<endl;
}
}
cout<<endl;
}
int main()
{
int arr[5 + 1] = {0,5,1,2,2,3};//第一个位置不用
FindNum(arr,5,1,5);
system("pause");
return 1;
}