给定一个整数数组 a,其中1 ≤ a[i] ≤ n (n为数组长度), 其中有些元素出现两次而其他元素出现一次。
找到所有出现两次的元素。
你可以不用到任何额外空间并在O(n)时间复杂度内解决这个问题吗?
示例:
输入:
[4,3,2,7,8,2,3,1]
输出:
[2,3]
解法一:
不使用额外的空间(python)
这个题目开头暗示了n的范围,所以可以加以利用,将元素转换成数组的索引并对应的将该处的元素乘以-1;
若数组索引对应元素的位置本身就是负数,则表示已经对应过一次;在结果列表里增加该索引的正数就行;
class Solution:
def findDuplicates(self, nums: List[int]) -> List[int]:
count = []
for n in nums:
if nums[abs(n)-1]>0:
nums[abs(n)-1] *=-1
else:
count.append(abs(n))
return count
解法二:(java)
抽屉原理+异或运算
思路分析:“桶排序”的思想是“抽屉原理”,即“一个萝卜一个坑”,8 个萝卜要放在 7 个坑里,则至少有 1 个坑里至少有 2 个萝卜。
这里由于数组元素限定在数组长度的范围内,因此,我们可以通过一次遍历:
让数值 1 就放在索引位置 0 处;
让数值 2 就放在索引位置 1 处;
让数值 3 就放在索引位置 2 处;
……
一次遍历以后,那些“无处安放”的元素就是我们要找的“出现两次的元素”。
为了不使用额外的空间,这里使用到的一个技巧是“基于异或运算交换两个变量的值”:交换两个整数,除了引入一个新的变量,写出一个“轮换”的赋值表达式以外,还有两种比较 tricky 的做法,下面给出结论。
“异或运算”是不进位的二进制加法,它有如下性质:
如果 a ^ b = c ,那么 a ^ c = b 与 b ^ c = a 同时成立,利用这一条,可以用于交换两个变量的值。
于是,交换两个变量的值,例如 a 和 b,不使用第三个变量,有两种不同的方法:
class Solution {
public List<Integer> findDuplicates(int[] nums) {
List<Integer> res = new ArrayList<>();
int len = nums.length;
if (len == 0) {
return res;
}
for (int i = 0; i < len; i++) {
while (nums[i] <= len && nums[nums[i] - 1] != nums[i]) {
swap(nums, i, nums[i] - 1);
}
}
for (int i = 0; i < len; i++) {
if (nums[i] - 1 != i) {
res.add(nums[i]);
}
}
return res;
}
private void swap(int[] nums, int index1, int index2) {
if (index1 == index2) {
return;
}
nums[index1] = nums[index1] ^ nums[index2];
nums[index2] = nums[index1] ^ nums[index2];
nums[index1] = nums[index1] ^ nums[index2];
}
}
解法三:(c++)
遍历数组,元素作为索引对应位置加n
遍历数组,出现两次,则对应位置大于2n
class Solution {
public:
vector<int> findDuplicates(vector<int>& nums) {
vector<int> res;
if(nums.empty()) return res;
int n=nums.size();
for(int i=0;i<n;i++)
{
int index=(nums[i]-1)%n;//对n取余,防止数字越界
nums[index]+=n; //对应位置加n
}
for(int i=0;i<n;i++)
{
//出现两次,则对应位置大于2n
if(nums[i]>2*n)
res.push_back(i+1);
}
return res;
}
};