问题:在一个长度为n的数组里的所有数字都在0-n~1的范围里,不知道重复的数字,也不知道每个重复数字重复几次,找出数组中所有重复的数字,如输入{2,3,1,0,2,5,3},则应输出{2,3}。
要求1:能改变数组中的数字
思路1:将数组排序,然后遍历数组,找出重复的数字。时间复杂度o(nlogn),空间复杂度o(1)。
思路1的实现:
vector<int> FindDuplicationNum(vector<int> &TestData)
{
vector<int> res;
sort(TestData.begin(),TestData.end());
int i = 0;
int j = 1;
int flag = -1; //判断当前重复数字是否需要存入结果数组中。
for (j; j < TestData.size();j++)
{
if (TestData[i] != TestData[j]) i = j;
else
{
if (flag != i)
{
res.push_back(TestData[i]);
flag = i;
}
}
}
return res;
}
思路2:因数组中的数字都在0~n-1的范围内,所以若数组中没有重复的数字,则排序后数字i将出现在下标为i的位置,而有重复的数字时,多个位置出现同一数字。因此可以遍历该数组,当扫描到下标为i的数字时,首先比较这个数字(用m表示)与i是否相等,若相等,则接着扫描下一个数字,若不相等,则比较m与第m个位置上的数字n是否相等,若相等,则m为重复的数字(第i个位置与第m个位置的值相等),若不等,则交换m与n的值,直到找到重复数字或下标为i的数字与i相等,然后扫描下一个数字。时间复杂度o(n),空间复杂度o(1)。
思路2的实现
vector<int> FindDuplicationNum(vector<int> &TestData)
{
vector<int> res;
//合法输入验证
if (TestData.size() == 0) return res;
for (int i = 0; i < TestData.size();i++)
{
if (TestData[i] < 0 || TestData[i] > TestData.size() - 1)return res;
}
for (int i = 0; i < TestData.size(); i++)
{
while (i != TestData[i])
{
if (TestData[i] == TestData[TestData[i]])
{
res.push_back(TestData[i]);
break;
}
else
{
int temp = TestData[i];
TestData[i] = TestData[TestData[i]];
TestData[temp] = temp;
}
}
}
return res;
}
要求2:不能改变数组
思路1:遍历数组,统计每个数字出现的次数,将结果存入map中;遍历map,找出重复的数字。时间复杂度o(n),空间复杂度o(n)。
思路1 的实现
vector<int> FindDuplicationNum(vector<int> &TestData)
{
map<int, int>count;
vector<int> res;
for (int i = 0; i < TestData.size(); i++)
{
count[TestData[i]]++;
}
auto i = count.begin();
while (i != count.end())
{
if (i->second > 1)
{
res.push_back(i->first);
}
i++;
}
return res;
}
思路2:把0~n-1的数字从中间的数字m分为两部分,前一部分为0~m,后一部分为m+1~n-1,如果0~m的数字的数目超过m+1个,则该部分一定含有重复数字,反之,另一半的区间里一定含有重复数字。然后继续把包含有重复数字的区间一分为二,直到找到一个重复数字为止。
思路2的实现
int countRange(vector<int> &TestData, int start, int middle) //统计当前部分各数字在数组中出现的次数。
{
int count = 0;
for (int i = 0; i < TestData.size();i++)
{
if (TestData[i] >= start && TestData[i] <= middle) count++;
}
return count;
}
int FindDuplicationNum(vector<int> &TestData)
{
int start = 0;
int end = TestData.size() - 1;
while (start <= end)
{
int middle = start + (end - start) / 2;
int count = countRange(TestData, start, middle);
if (end == start)
{
if (count > 1)return start;
else break;
}
if (count > middle- start + 1)
{
end = middle;
}
else start = middle + 1;
}
return -1;
}
在该思路的中,调用countRange函数o(logn)次,每次需要o(n)的时间,因此总的时间复杂度为o(nlogn),空间复杂度为o(1)。但值得注意的是,该算法中,不能保证找到所有重复的数字。