「这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战」
题目:
剑指 Offer II 004. 只出现一次的数字
给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次 。 请你找出并返回那个只出现了一次的元素。
示例 1:
输入: nums = [2,2,3,2]
输出: 3
示例 2:
输入: nums = [0,1,0,1,0,1,100]
输出: 100
提示:
1 <= nums.length <= 3 * 104-231 <= nums[i] <= 231 - 1nums中,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次
进阶: 你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
方法1思路:
- 用
Map记录每个元素出现的次数 - 遍历
Map找到value值为1的元素
方法1实现:
var singleNumber = function (nums) {
let map = new Map();
// 遍历数组,记录每个元素出现的次数
for (let i = 0; i < nums.length; i++) {
map.set(nums[i], (map.get(nums[i]) || 0) + 1);
}
// 从我们的map中找到value值为1的元素
for (let [key] of map) {
if (map.get(key) === 1) {
return key;
}
}
};
方法2思路
- 采用二进制,这样子在32位中每个位置的值只有0和1
- 遍历求每个位置的和, 能被3整除的说明出现了3次,不能被整除的说明出现了1次
- 定义一个变量
result,记录每个位置被3整除后的结果0 || 1 - 从最后一位开始,每一轮求解完更新结果的当前位置
- 遍历结束,拿到的结果就是我们要找的出现一次的值
方法2实现
var singleNumber = function (nums) {
let result = 0;
const n = nums.length;
// 二进制是32位的, 判断每一位的值
for (let i = 0; i < 32; i++) {
// 累加每一位的值总和
let total = 0;
for (let j = 0; j < n; j++) {
// (nums[j] >> i) & 1 => 拿到当前位的值
total += (nums[j] >> i) & 1;
}
// 如果当前位不能被3整除,说明出现1次的元素在当前位有值
if (total % 3 !== 0) {
// 把当前位推到结果中去
result += (total % 3) << i;
}
}
return result;
};
总结
方法1是求解最简单的方式,当然这道题目中进阶要求我们使用常量空间去解决问题。像数组特别长的时候我们的map空间会溢出。所以这时候我们采取位运算来解决问题。其中位运算里面有几个知识点:
-
nums[j] >> i把当前的元素右移i位, 可以理解为Math.floor(nums[j] / Math.pow(2, i))。 简单来讲就是当前值除以2的i次方,向下取整。 -
(nums[j] >> i) & 1左边部分上一步已经解释过了,这一步把左边部分称为n,n & 1可以求出n是奇数还是偶数, 当n为奇数时n & 1 = 1, n为偶数时n & 1 = 0. -
result += (total % 3) << i我们的遍历是从最后一位开始的,相当于二进制中每次朝原本的数组中unshift一个数字。比如10 => 110 => 1110; 只不过这道题我们要返回的结果是10进制的,所以直接用+=;相当于2 + 4 + 8 + 16。
看懂了的小伙伴可以点个关注、咱们下道题目见。如无意外以后文章都会以这种形式,有好的建议欢迎评论区留言。