题目:
Given an array of integers where 1 ≤ a[i] ≤ n (n = size of array), some elements appear twice and others appear once.
Find all the elements of [1, n] inclusive that do not appear in this array.
Could you do it without extra space and in O(n) runtime? You may assume the returned list does not count as extra space.
Example:
Input:
[4,3,2,7,8,2,3,1]
Output:
[5,6]
代码:
在网上搜了搜,找到了几种解法,写好程序后整理整理。
解法一(取余法):
数组的元素范围为1~n,第一次循环首先把每个元素对应的位置加上(n+1);第二次循环把每个位置除以(n+1),如果该位置为0,表示某个元素没有出现;如果该位置等于2,表示出现两次。
原理是什么呢?在第一次循环中,我们其实是将每个位置变成k*(n+1)+i,其中k表示该位置加(n+1)的次数,取值为0、1、2,i表示该位置本来的元素。在第二次循环中,因为i的范围是1~n,所以除以(n+1)就等于0,从而我们就获得了k的值。根据k的值,我们就很容易知道哪些元素没有出现,哪些元素出现了多次。
1 class Solution { 2 public: 3 vector<int> findDisappearedNumbers(vector<int>& nums) { 4 vector<int> result; 5 int length = nums.size()+1; 6 for (int i = 0; i < length-1; i++) 7 nums[nums[i]%length-1] += length; 8 for (int i = 0; i < length-1; i++){ 9 if ((nums[i]/length) == 0) 10 result.push_back(i+1); 11 } 12 return result; 13 } 14 };
解法二(元素归位法):
元素归位法很容易理解,就是将n个元素交换到它应该在的位置。例如,元素5就放到位置4(下标从0开始)。这里需要注意一点,将某个元素交换到正确位置可能会导致当前位置的元素还不在正确位置,需要继续交换直到不能交换为止,伪代码如下:
1 for i=1:n 2 while canSwap(i) do swap(i);
将元素归位之后,我们就很容易获得哪些元素没有出现。当某个位置不是正确元素的时候,就意味着这个元素没有出现。也即针对没有出现的元素,我们只需要返回下标;针对出现两次的元素,我们只需要返回该位置的值。
这里有一个疑问,伪代码有两个for循环,复杂度是不是O(n2)呢?不是,复杂度还是O(n),这个可以通过均摊分析来解释:如果满足交换条件,则每次都会使一个元素处在正确位置,因为总共有n个元素,所以至多需要n-1次交换(交换完n-1个元素,第n个元素自动满足)即可使所有的元素处在正确位置,也即while循环至多执行O(n)次,每次的平摊代价是O(1)。所以上述交换操作的复杂度为O(n)。
1 class Solution { 2 public: 3 vector<int> findDisappearedNumbers(vector<int>& nums) { 4 vector<int> result; 5 int length = nums.size()+1; 6 for (int i = 0; i < length-1; i++){ 7 while((nums[i] != i+1 )&& (nums[nums[i]-1] != nums[i])) 8 swap(nums[i],nums[nums[i]-1]); 9 } 10 11 for (int i = 0; i < length-1; i++){ 12 if (nums[i] != i+1) 13 result.push_back(i+1); 14 } 15 return result; 16 } 17 };
运行最快。
解法三(取负法):
含义是:将元素对应的位置取负。简单一句话可能不好理解,我们举个例子。假设在位置k放了元素i,则在取负的过程中i的取值有两种可能:为正,表示当前尚未遇到元素k将该位置取负;为负,表示当前已经有元素k出现,并将元素取负。但是我们不关心k,我们关心元素i。元素i既然出现,我们就看一下位置i:为正,表示这是元素i第一次出现,我们将位置i取负;为负,表示元素i已经出现过一次,我们不做任何操作。不管一个元素出现一次还是两次,只要出现它对应的位置就会被取负。当某个元素不出现的时候,该元素对应的位置始终访问不到,所以还是正值,通过这种方法我们就可以找到哪些元素没有出现。
通过上面的分析我们也很容易知道,在取负的过程中,如果发现要取负的位置已经为负,说明这个元素已经出现过,也即该元素出现了两次,我们可以将该元素保留下来。
1 class Solution { 2 public: 3 vector<int> findDisappearedNumbers(vector<int>& nums) { 4 vector<int> result; 5 int length = nums.size(); 6 for (int i = 0; i < length; i++){ 7 int index = abs(nums[i])-1; 8 if(nums[index]>0) 9 nums[index] = -nums[index]; 10 } 11 12 for (int i = 0; i < length; i++){ 13 if (nums[i] > 0) 14 result.push_back(i+1); 15 } 16 return result; 17 } 18 };
这三种方法都是通过某种方式将index与元素对应起来操作。