algorithm-base/animation-simulation/求次数问题/只出现一次的数2.md

290 lines
9.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

> 如果阅读时,发现错误,或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ,备注 github + 题目 + 问题 向我反馈
>
> 感谢支持,该仓库会一直维护,希望对各位有一丢丢帮助。
>
> 另外希望手机阅读的同学可以来我的 <u>[**公众号:袁厨的算法小屋**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步,想要和题友一起刷题,互相监督的同学,可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。
#### [137. 只出现一次的数字 II](https://leetcode-cn.com/problems/single-number-ii/)
> 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素。
示例 1:
> 输入: [2,2,3,2]
> 输出: 3
示例 2:
> 输入: [0,1,0,1,0,1,99]
> 输出: 99
题目很容易理解,刚才的题目是其他元素出现两次,目标元素出现一次,该题是其他元素出现三次,目标元素出现一次,所以我们完全可以借助上题的一些做法解决该题。
### 求和法
#### 解析
我们在上题中介绍了求和法的解题步骤,现在该题中其他元素都出现三次,我们的目标元素出现一次,所以我们利用求和法也是完全 OK 的。下面我们来看具体步骤吧。
1.通过遍历数组获取所有元素的和以及 HashSet 内元素的和。
2.SumSet \* 3 - SumNum/ 2 即可,除以 2 是因为我们减去之后得到的是 2 倍的目标元素。
注:这个题目中需要注意溢出的情况 。
#### 题目代码
Java Code:
```java
class Solution {
public int singleNumber(int[] nums) {
HashSet<Integer> set = new HashSet<>();
long setsum = 0;
long numsum = 0;
for (int x : nums) {
//所有元素的和
numsum += x;
if (!set.contains(x)) {
//HashSet元素和
setsum += x;
}
set.add(x);
}
//返回只出现一次的数
return (int)((3 * setsum - numsum) / 2);
}
}
```
C++ Code:
```cpp
class Solution {
public:
int singleNumber(vector<int>& nums) {
unordered_set<int> set;
long setsum = 0;
long numsum = 0;
for (int x : nums) {
//所有元素的和
numsum += x;
if (set.find(x) == set.end()) {
//HashSet内元素的和
setsum += x;
}
set.insert(x);
}
//返回值
return (3 * setsum - numsum) / 2;
}
};
```
JS Code:
```javascript
var singleNumber = function (nums) {
let set = new Set();
let setsum = 0;
let numsum = 0;
for (let x of nums) {
//所有元素的和
numsum += x;
if (!set.has(x)) {
setsum += x;
}
//HashSet内元素的和
set.add(x);
}
//返回值
return (3 * setsum - numsum) / 2;
};
```
Python Code:
```python
class Solution:
def singleNumber(self, nums: List[int]) -> int:
return (3 * sum(set(nums)) - sum(nums)) // 2
```
这个题目用 HashMap 和排序查找肯定也是可以的,大家可以自己写一下,另外我们在第一题中有个利用异或求解的方法,但是这个题目是出现三次,我们则不能利用直接异或来求解,那还有其他方法吗?
### 位运算
#### 解析
这个方法主要做法是将我们的数的二进制位每一位相加,然后对其每一位的和与 3 取余 ,我们看下面的例子。
![只出现一次的数字2](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/只出现一次的数字2.5p4wxbiegxc0.png)
那么我们为什么要这样做呢?大家想一下,如果其他数都出现 3 次,只有目标数出现 1 次,那么每一位的 1 的个数无非有这两种情况,为 3 的倍数(全为出现三次的数)或 3 的倍数 +1包含出现一次的数。这个 3 的倍数 +1 的情况也就是我们的目标数的那一位。
#### 题目代码
Java Code:
```java
class Solution {
public int singleNumber(int[] nums) {
int res = 0;
for(int i = 0; i < 32; i++){
int count = 0;
for (int num: nums) {
//检查第 i 位是否为 1
if ((num >> i & 1) == 1) {
count++;
}
}
if (count % 3 != 0) {
// 将第 i 位设为 1
res = res | 1 << i;
}
}
return res;
}
}
```
C++ Code:
```cpp
class Solution {
public:
int singleNumber(vector<int>& nums) {
int res = 0;
for(int i = 0; i < 32; i++){
int count = 0;
for (int num: nums) {
//检查第 i 位是否为 1
if ((num >> i & 1) == 1) {
count++;
}
}
if (count % 3 != 0) {
// 将第 i 位设为 1
res = res | 1 << i;
}
}
return res;
}
};
```
JS Code:
```javascript
var singleNumber = function (nums) {
let res = 0;
for (let i = 0; i < 32; i++) {
let count = 0;
for (let num of nums) {
//检查第 i 位是否为 1
if (((num >> i) & 1) == 1) {
count++;
}
}
if (count % 3 != 0) {
// 将第 i 位设为 1
res = res | (1 << i);
}
}
return res;
};
```
Python Code:
```python
class Solution:
def singleNumber(self, nums: List[int]) -> int:
res = 0
for i in range(32):
count = 0
for num in nums:
# 检查第 i 位是否为 1
if (num >> i & 1) == 1:
count += 1
if count % 3 != 0:
# 将第 i 位设为 1
res = res | 1 << i
# 这里的做法稍有不同,见下方解释
if (res >> 31 & 1) == 1:
res = ~(res ^ 4294967295)
return res
```
Go Code:
```go
func singleNumber(nums []int) int {
res := 0
// Go语言中int占32位以上
for i := 0; i < 64; i++ {
cnt := 0
for j := 0; j < len(nums); j++ {
if (nums[j] >> i & 1) == 1 {
cnt++
}
}
if cnt % 3 != 0{
res = res | 1 << i
}
}
return res
}
```
我们来解析一下我们的代码:
> **<<** 左移运算符:运算数的各二进位全部左移若干位,由 **<<** 右边的数字指定了移动的位数,高位丢弃,低位补 0。
>
> **>>** 右移运算符:**>> ** 左边的运算数的各二进位全部右移若干位,**>>** 右边的数字指定了移动的位数。
另外我们的代码中还包含了 a & 1 和 a | 1 这有什么作用呢?继续看下图。
> **&** 按位与运算符:参与运算的两个值,如果两个相应位都为 1,则该位的结果为 1否则为 0。
![只出现一次的数位运算且](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/只出现一次的数位运算且.vq3lcgv0rbk.png)
因为我们 a & 1 中 1 只有最后一位为 1其余位皆为 0 ,所以我们发现 **a & 1 的作用就是判断 a 的最后一位是否为 1** ,如果 a 的最后一位为 1 a & 1 = 1否则为 0 。所以我们还可以通过这个公式来判断 a 的奇偶性。
> **|** 按位或运算符:只要对应的二个二进位有一个为 1 时,结果位就为 1。
![或运算](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/或运算.6orep3gsrxc0.png)
这个公式的作用就是**将我们移位后的 res 的最后一位 0 变为 1**。这个 1 也就代表着我们只出现一次元素的某一位。
> 贡献者[@jaredliw](https://github.com/jaredliw)注:
>
> 这里我想解释一下 python 里的这两行:
>
> ```python
> if (res >> 31 & 1) == 1:
> res = ~(res ^ 4294967295)
> ```
>
> int 的的符号是由最高位(这题用的是 32 位的值决定1 就是负数0 就是正数。由于 python 的 int 类型理论上是无限大的,这题里的 res 都会被认定为是正数。举个例子32 位的 -4 是这样的:
>
> > 11111111111111111111111111111100 (最高位是 1 = -4
>
> python 里的则是这样的:
>
> > ...000000000000 11111111111111111111111111111100 (前面跟着无限个 0最高位是 0 = 4294967292
>
> 怎么办呢?
>
> 我们可以先将 res 的后 32 位取反(与 4294967295 异或4294967295 的二进制是 32 个 1得到
>
> > ...000000000000 00000000000000000000000000000011最高位是 0= 3
>
> 之后再用波浪号按位取反,得到:
>
> > ...111111111111 11111111111111111111111111111100 (前面跟着无限个 1最高位是 1= -4
>
> 大家可以自行验证看看:`(res >> n & 1) == 1` n 随便填个大于 31 的数字,之前是 false之后就变成 true (代表第 33 位,第 34 位,……都转成 1 了)。
>
> 虽然说这种方法有一种脱裤子放屁的感觉 ,而且`res -= 2 ** 32` 也能办到,但由于涉及到 int 存储的问题(我省略了许多,大家自行度娘哈),我觉得还是有必要知道的。