「这是我参与2022首次更文挑战的第16天,活动详情查看:2022首次更文挑战」。
题目描述🌍
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例 1:
输入: [2,2,1]
输出: 1
示例 2:
输入: [4,1,2,1,2]
输出: 4
几类「浅尝辄止」的解法🚧
解题思路
如果不考虑时间复杂度和空间复杂度,该题有各式各样的解法:
- 将数组排序,利用
nums[i] != nums[i + 1]条件就可以找到落单元素,同时需要注意落单元素可能位于数组末尾,所以需要i == nums.length - 1判断。 - 使用集合存储元素,若数组中的元素在集合中不存在则添加,存在则移除,类似层叠消融的思想,最后返回集合中的唯一元素。
- 将数组中所有元素添加到哈希表中,此时哈希表中元素累加值的 2 倍与数组元素累加和的差值,就是只出现了一次的元素。
- 通过
HashMap统计每一个元素出现的次数,返回只出现一次的那个数字。
⭐以下 4 种方法分别对应如上 4 种解法,虽然结果都不出意外的糟糕,但有想法总归是好的。
代码
Java
// 不考虑时间复杂度与空间复杂度!
class Solution {
// 解法一
public int singleNumber1(int[] nums) {
Arrays.sort(nums);
int index = 0;
for (int i = 0; i < nums.length; i += 2) {
if (i == nums.length - 1 || nums[i] != nums[i + 1]) {
index = i;
break;
}
}
return nums[index];
}
// 解法二
public int singleNumber2(int[] nums) {
List<Integer> list = new ArrayList<>();
for (int num : nums) {
if (!list.contains(num)) {
list.add(num);
} else {
list.remove((Integer) num);
}
}
return list.get(0);
}
// 解法三
public int singleNumber3(int[] nums) {
Set<Integer> hashSet = new HashSet<>();
int arrSum = 0;
int hashSum = 0;
for (int num : nums) {
// 数组求和
arrSum += num;
// 哈希表: 过滤重复元素
hashSet.add(num);
}
// 哈希表求和
for (Integer integer : hashSet) {
hashSum += integer;
}
return hashSum * 2 - arrSum;
}
// 解法四
public int singleNumber4(int[] nums) {
Map<Integer, Integer> hashMap = new HashMap<>();
// 枚举每一个元素出现的次数
for (int num : nums) {
Integer count = hashMap.get(num);
hashMap.put(num, count == null ? 1 : count + 1);
}
// HashMap 并未实现 Iterator 接口
int singleNumber = 0;
for (int key : hashMap.keySet()) {
if (hashMap.get(key).equals(1)) {
singleNumber = key;
}
}
return singleNumber;
}
}
C++
class Solution {
public:
int singleNumber1(vector<int> &nums) {
sort(nums.begin(), nums.end());
int index = 0;
int length = nums.size();
for (int i = 0; i < length; i += 2) {
if (i == length - 1 || nums[i] != nums[i + 1]) {
index = i;
break;
}
}
return nums[index];
}
int singleNumber2(vector<int> &nums) {
vector<int> container{};
for (int num: nums) {
auto pos = find(container.begin(), container.end(), num);
if (pos != container.end()) {
container.erase(pos);
} else {
container.push_back(num);
}
}
return container.front();
}
int singleNumber3(vector<int> &nums) {
set<int> hash;
int numSum = 0;
int hashSum = 0;
for (int num: nums) {
numSum += num;
hash.insert(hash.begin(), num);
}
hashSum = std::accumulate(hash.begin(), hash.end(), hashSum);
return 2 * hashSum - numSum;
}
int singleNumber4(vector<int> &nums) {
unordered_map<int, int> times;
for (int num: nums) {
if (times.find(num) == times.end()) {
times[num] = 1;
} else {
times[num] += 1;
}
}
int singleNumber = 0;
for (auto i: times) {
if (i.second == 1) {
singleNumber = i.first;
}
}
return singleNumber;
}
};
时间复杂度:
空间复杂度:
这几类解法通俗易懂,不过都需要额外使用 的空间,如果要做到线性时间复杂度 且不使用额外空间即空间复杂度为 ,那么就需要使用 位运算 完成。
位运算:异或求解🚀
解题思路
使用异或运算求解本题前,先来科普两点异或操作:
x 为任意数字
x ^ 0 = xx ^ x = 0
数组中除了某一个元素 x 以外都是成对出现的,如果将数组中所有元素逐个进行异或运算,那么成对(相同)的元素都会被消除为 0,最后仅剩下 0 与 x,其异或结果必然为所求的 只出现一次的数字。
代码
Java
class Solution {
public int singleNumber(int[] nums) {
int result = 0;
for (int num : nums) {
result = result ^ num;
}
return result;
}
}
C++
class Solution {
public:
int singleNumber(vector<int> &nums) {
int ret = 0;
for (int num: nums) {
ret = ret ^ num;
}
return ret;
}
};
时间复杂度:
空间复杂度:
知识点🌓
所谓位运算就是将数字转化为二进制数字,然后按位逐个运算。
简单复习下 Java 中提供的 7 种位运算符号:
- 与
& - 或
| - 异或
^ - 取反
~ - 左移
<< - 右移
>> - 无符号右移
>>>
最后🌅
该篇文章为 「LeetCode」 系列的 No.15 篇,在这个系列文章中:
- 尽量给出多种解题思路
- 提供题解的多语言代码实现
- 记录该题涉及的知识点
👨💻争取做到 LeetCode 每日 1 题,所有的题解/代码均收录于 「LeetCode」 仓库,欢迎随时加入我们的刷题小分队!