题目:singleNumber
Given an array of integers, every element appears twice except for one. Find that single one.
Note:
Your algorithm should have a linear runtime complexity. Could you implement it without using extra memory?
给一个数组里面除了唯一的一个数只出现一次,其他每个数都重复了一次。
要求:线性的时间复杂度,和O(1)的空间复杂度
思路:
如果不要求线性的时间复杂度,则可以考虑用排序的方法,成熟稳定,又有很好的通用性。
但是,如果要保证线性的时间复杂度,则可以考虑使用hash_map来找单个出现的数字。
通常思路里面效率的瓶颈在于如何确定当前元素是前面已经出现过的元素,此时可以通过hash_map来确保在O(1)的时间复杂度里确定当前元素是否出现过。
我用删除的方法来降低冲突率,同时,方便最后找到最终的singleNumber。
/** *Given an array of integers, every element appears twice except for one. Find that single one. *Note: *Your algorithm should have a linear runtime complexity. Could you implement it without using extra memory? **/
/**空间复杂度O(n),时间复杂度O(n)但是依赖map的性能**/ int LeetCode::singleNumberMap(vector<int>& nums){ hash_map<int,int> hmp; auto it = nums.cbegin(); while (it != nums.cend()){ auto temp = hmp.find(*it); if (temp != hmp.cend()){//找得到当前元素,说明它在前面出现过 //(*temp).second = 2; hmp.erase(temp);//删除重复元素 } else{ hmp.insert(make_pair(*it,1));//插入首次出现的元素 } ++it; } if (hmp.size() == 1){//如果最终存在singleNumber auto ret = hmp.begin(); return (*ret).first; } return -1;//不存在,则返回-1 }
思路:
使用异或的方法;
因为元素是数字,且数组保证只重复两次;可以考虑使用异或的方法,来直接求出单一数字。
这个方法比上面的更高效,且空间复杂度O(1),而上面的空间复杂度O(n)。
/** 空间复杂度O(1),时间复杂度O(n) 使用异或来求单个数字 **/ int LeetCode::singleNumberXOR(vector<int>& nums){ int sum = 0; auto it = nums.cbegin(); while (it != nums.cend()){ sum = sum ^ (*it);//异或 ++it; } return sum; }
题目:singleNumberII
Given an array of integers, every element appears three times except for one, which appears exactly once. Find that single one.
Note:
Your algorithm should have a linear runtime complexity. Could you implement it without using extra memory?
给一个数组里面除了唯一的一个数只出现一次,其他每个数都重复了两次(即出现3次)。
要求:线性的时间复杂度,和O(1)的空间复杂度
思路:
使用hash_map同样可以求出来,思路跟第一个是一样的。
但是,如何用异或更高效的找到singleNumber呢?
首先还是对每个元素异或,保留奇数次出现的元素(once);
同时,增加一个变量accumulate |= (*it) & once;
上面的算式能够保留出现2次以上的元素;(it表示当前元素)
例如:once中出现过奇数次则表示保留了该数字,则必然出现2次以上,于是上面表达式的结果确实是保留了当前元素;
once出现了偶数次,表示未保留该数字,则取决于accumulate中是否保留该数字,没有则是0。
因此,上面表达式确实能够保留出现2次以上的元素。
然后出现3次则表示,上面两个值都是保留当前元素的状态,于是在异或一次就消除了出现3次的元素。
int LeetCode::singleNumber(vector<int>& nums){ //once标记出现奇数次的数字,accumulate标记出现2次以上的数字 int once = 0,accumulate = 0; auto it = nums.cbegin(); while (it != nums.cend()){ //once中出现过奇数次则表示保留了该数字,则必然出现2次以上; //once出现了偶数次,表示未保留该数字,则取决于accumulate中是否保留该数字,没有则是0 accumulate |= (*it) & once;// 只要第二次或者以上出现,就为1 once ^= (*it);// 出现奇数次保留,偶数次抛弃 int t = once & accumulate;// 第三次的时候one和accumulation都保留了该位的值 once &= ~t; // 清零出现三次的该位的值 accumulate &= ~t; //once = (once ^ (*it)) & ~accumulate; //accumulate = (accumulate ^ (*it)) & ~once; ++it; } return once; }
上面一连串的求解,还可以缩减成下面两个表达式:
once = (once ^ (*it)) & ~accumulate;
accumulate = (accumulate ^ (*it)) & ~once;
题目:Single Number III
Given an array of numbers nums
, in which exactly two elements appear only once and all the other elements appear exactly twice. Find the two elements that appear only once.
For example:
Given nums = [1, 2, 1, 3, 2, 5]
, return [3, 5]
.
Note:
- The order of the result is not important. So in the above example,
[5, 3]
is also correct. - Your algorithm should run in linear runtime complexity. Could you implement it using only constant space complexity?
给一个数组里面除了两个数只出现一次,其他每个数都出现了两次。
要求:线性的时间复杂度,和O(1)的空间复杂度。
思路:
设数组为A[],其中两个只出现一次的数字是A,B
根据第一个问题的思路,如果只有一个数字可以直接将所有数字异或得到。因此,该问题如果异或所有数字得到的值是A^B。
那么,如果能将A,B区分开就可以了;异或表示两个数字中不相同的位是1,即如果A^B的某一位是1,表示A和B中对应的该为分别为0和1,或1和0。
反之,只要将该位为1的所有数从新异或一遍,就能的到A或B中的一个数字,于是就区分开了。
vector<int> LeetCode::singleNumber3(vector<int>& nums){ int xorall = 0, first1 = 0;//第一个1 vector<int>result(2, 0); for (auto i : nums){//求A^B xorall ^= i; } first1 = xorall & (~(xorall - 1));//求A^B中第一个1的值,例如A = 4,B = 8,A^B = 12,first1 = (0100 ^ 1000) & (0100) = 4 for (auto i : nums){ if (first1 & i)result[0] ^= i;//所有A^B第一个1相同位置为1的元素异或 else result[1] ^= i;; } return result; }