题目描述
136. 只出现一次的数字
难度:简单
相关标签:位运算、数组
给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。
运行示例
示例 1 :
输入:nums = [2,2,1]
输出:1
示例 2 :
输入:nums = [4,1,2,1,2]
输出:4
示例 3 :
输入:nums = [1]
输出:1
提示
1 <= nums.length <= 3 * 104
-3 * 104 <= nums[i] <= 3 * 104
除了某个元素只出现一次以外,其余每个元素均出现两次。
题目分析与实现
看到这道题,我首先想到的是记录每个元素出现的次数,再找出只出现一次的元素。因为题目给出的数组长度永远为奇数,我们可以通过开辟一个长度为nums.size()/2+1的二维数组。
咱们来看看这个方式实现的算法具体长什么样,我们再来分析分析:
class Solution {
public:
int singleNumber(vector<int>& nums) {
int n = nums.size();
//创建用于存储各个数字出现次数的二维数组
vector<vector<int>>newNums;
for(int i = 0; i < n; i++)
{
//haveSearch用于记录nums[i]是否出现在二维数组中
int haveSearch = 0;
for(int j = 0; j < newNums.size(); j++)
{
if(nums[i] == newNums[j][0])
{
newNums[j][1]++;
haveSearch = 1;
break;
}
}
//没在二维数组中找到,则存入nums[i]
if(haveSearch == 0)
{
vector<int>temp(2);
temp[0] = nums[i];
temp[1] = 1;
newNums.push_back(temp);
}
}
int ret = -1;
//遍历寻找只出现一次的数字
for(int i = 0; i < newNums.size(); i++)
{
if(newNums[i][1] == 1)
{
ret = newNums[i][0];
break;
}
}
return ret;
}
};
上面的方法开辟了一个二维数组,因而空间复杂度为O(n)。在遍历nums时,每次都要遍历newNums,因而时间复杂度为O(n^2)。这显然效率不高。
首先让我们解决一下时间复杂度的问题。用于存储数字并可快速存取,就会考虑到哈希表。哈希表时间复杂度为O(1)。再来看看哈希表实现的代码:
class Solution {
public:
int singleNumber(vector<int>& nums) {
unordered_map<int,int>hashMap;
int n = nums.size();
for(int i = 0; i < n; i++)
{
auto fRet = hashMap.find(nums[i]);
if(fRet == hashMap.end())//没找到,则插入nums[i]
{
hashMap.insert(pair(nums[i], 1));
}
else//找到,则将出现次数+1
{
fRet->second++;
}
}
int ret = -1;
for(auto kv : hashMap)
{
if(kv.second == 1)
{
ret = kv.first;
break;
}
}
return ret;
}
};
上面这种方法仅仅优化了时间复杂度,将时间复杂度将为O(n),但空间复杂度仍为O(n)。类似的方法还有哈希集合结合数学的方法。思路大概是这样的:将nums中出现的元素存储进哈希集合中(重复元素哈希集合只会存储一次),使用sum存储出现在哈希集合中的元素的2倍,再用sum减去nums中的各个元素,剩下的就是只出现一次的元素。
class Solution {
public:
int singleNumber(vector<int>& nums) {
set<int>numsSet;
int n = nums.size();
for(int i = 0; i < n; i++)
{
numsSet.insert(nums[i]);
}
int sum = 0;
for(int i : numsSet)
{
sum += 2 * i;
}
for(int i = 0; i < n; i ++)
{
sum -= nums[i];
}
return sum;
}
};
当然,上面这三种方法都不满足线性时间复杂度和常数阶空间复杂度的要求。接下来,介绍一下我们的大牛算法——位运算。
这一题和我们之间讨论过的面试题 17.04. 消失的数字很像。这里我们再来巩固巩固位运算:
如果两个相同的数字做异或运算,则其结果为0(如下图所示)。
如果一个数与0做异或运算,则其结果为其本身(如下图所示)。
利用上面异或的性质。我们以nums = [4,1,2,1,2]为例。我们将storage初始化为0,因为storage若与1异或后得storage为1,在与另一个1异或得到storage为0。同理,storage与两个2异或得到的也是0。最终只会剩下storage与4异或的结果,即4。
因此,我们可以使用一个初始化为0的遍历,将其与nums中的各个元素异或,最终结果将是只出现一次的数字。
class Solution {
public:
int singleNumber(vector<int>& nums) {
int storage = 0;
int n = nums.size();
for(int i = 0; i < n; i++)
{
storage ^= nums[i];
}
return storage;
}
};
这不就是满足题目要求的算法嘛!线性时间复杂度O(n)和常数阶空间复杂度O(1)。此时我的快乐只能用这个表情形容了👇
上面就是今天分享的所有内容啦!下一文章见!
🎇有更优秀的算法,欢迎在评论区讨论!!