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

621 lines
16 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>进入。
#### [136. 只出现一次的数字](https://leetcode-cn.com/problems/single-number/)
> 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
示例 1:
> 输入: [2,2,1]
> 输出: 1
示例 2:
> 输入: [4,1,2,1,2]
> 输出: 4
这个题目非常容易理解,就是让我们找出那个只出现一次的数字,那么下面我们来看一下这几种解题方法吧~
### HashMap
#### 解析
用 HashMap 的这个方法是很容易实现的,题目要求不是让我们求次数嘛,那我们直接遍历数组将每个数字和其出现的次数存到哈希表里就可以了,然后我们再从哈希表里找出出现一次的那个数返回即可。
![哈希表解法](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/哈希表解法.1kefww8xsig0.png)
#### 题目代码
Java Code:
```java
class Solution {
public int singleNumber(int[] nums) {
//特殊情况
if (nums.length == 1) {
return nums[0];
}
//HashMap
HashMap<Integer,Integer> map = new HashMap<Integer,Integer>();
//将其存入哈希表中含义为若该元素不存在则存入表中并计数为1若已经存在获取次数并加1
for (int x : nums) {
map.put(x , map.getOrDefault(x,0) + 1);
}
//遍历出出现次数为1的情况
for (int y : map.keySet()) {
if(map.get(y) == 1){
return y;
}
}
}
}
```
C++ Code:
```cpp
class Solution {
public:
int singleNumber(vector<int>& nums) {
//特殊情况
if (nums.size() == 1) {
return nums[0];
}
//HashMap
unordered_map<int,int> map;
//将其存入哈希表中含义为若该元素不存在则存入表中并计数为1若已经存在获取次数并加1
for (int x : nums) {
if (map.find(x) == map.end()) {
map.insert({x, 0});
}
map[x] += 1;
}
//遍历出出现次数为1的情况
for (int y : nums) {
if(map.at(y) == 1){
return y;
}
}
return -1;
}
};
```
JS Code:
```javascript
var singleNumber = function (nums) {
//特殊情况
if (nums.length === 1) {
return nums[0];
}
//HashMap
map = {};
//将其存入哈希表中含义为若该元素不存在则存入表中并计数为1若已经存在获取次数并加1
for (let x of nums) {
if (!(x in map)) {
map[x] = 0;
}
map[x] += 1;
}
//遍历出出现次数为1的情况
for (let key in map) {
if (map[key] === 1) {
return key;
}
}
};
```
Python Code:
```python
class Solution:
def singleNumber(self, nums: List[int]) -> int:
# 特殊情况
if len(nums) == 1:
return nums[0]
# HashMap
map_ = {}
# 将其存入哈希表中含义为若该元素不存在则存入表中并计数为1若已经存在获取次数并加1
for x in nums:
map_.setdefault(x, 0)
map_[x] += 1
# 遍历出出现次数为1的情况
for y, count in map_.items():
if count == 1:
return y
```
Go Code:
```go
func singleNumber(nums []int) int {
if len(nums) == 1 {
return nums[0]
}
m := map[int]int{}
for _, x := range nums {
m[x]++
}
for k, v := range m {
if v == 1 {
return k
}
}
return 0
}
```
### 排序搜索法
#### 解析
这个方法也是特别容易想到的,我们首先对数组进行排序,然后遍历数组,因为数组中其他数字都出现两次,只有目标值出现一次,所以则让我们的指针每次跳两步,当发现当前值和前一位不一样的情况时,返回前一位即可,当然我们需要考虑这种情况,当我们的目标值出现在数组最后一位的情况,所以当数组遍历结束后没有返回值,则我们需要返回数组最后一位,下面我们看一下动图解析。
![排序](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/排序.6sp72k3iaqw0.gif)
#### 题目代码
Java Code:
```java
class Solution {
public int singleNumber(int[] nums) {
if (nums.length == 1){
return nums[0];
}
//排序
Arrays.sort(nums);
for (int i = 1; i < nums.length-1; i+=2){
if (nums[i] != nums[i-1]){
return nums[i-1];
}
}
return nums[nums.length-1];
}
}
```
C++ Code:
```cpp
class Solution {
public:
int singleNumber(vector<int>& nums) {
//排序
sort(nums.begin(), nums.end());
for (int i = 1; i < nums.size() - 1; i += 2){
if (nums[i] != nums[i - 1]){
return nums[i - 1];
}
}
return nums[nums.size() - 1];
}
};
```
JS Code:
```javascript
var singleNumber = function (nums) {
// 排序
nums.sort();
for (let i = 1; i < nums.length - 1; i += 2) {
if (nums[i] != nums[i - 1]) {
return nums[i - 1];
}
}
return nums[nums.length - 1];
};
```
Python Code:
```python
class Solution:
def singleNumber(self, nums: List[int]) -> int:
# 排序
nums.sort()
for i in range(1, len(nums), 2):
if nums[i] != nums[i - 1]:
return nums[i - 1]
return nums[len(nums) - 1]
```
Go Code:
```go
func singleNumber(nums []int) int {
if len(nums) == 1 {
return nums[0]
}
sort.Ints(nums)
for i := 1; i < len(nums) - 1; i+=2 {
if nums[i] == nums[i - 1] {
continue
} else {
return nums[i - 1]
}
}
return nums[len(nums) - 1]
}
```
### HashSet
#### 解析
这个方法也是比较容易实现的,我们利用 HashSet 来完成。HashSet 在我们刷题时出现频率是特别高的,它是基于 HashMap 来实现的,是一个不允许有重复元素的集合。那么在这个题解中,它起到什么作用呢?解题思路如下,我们依次遍历元素并与 HashSet 内的元素进行比较,如果 HashSet 内没有该元素(说明该元素第一次出现)则存入,若是 HashSet 已经存在该元素(第二次出现),则将其从 HashSet 中去除,并继续遍历下一个元素。最后 HashSet 内剩下的则为我们的目标数。思路和我们之前说过的括号匹配问题类似,我们一起来看一下动图解析吧。
![HashSet](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/HashSet.4b6dcxwj07c0.gif)
#### 题目代码
Java Code:
```java
class Solution {
public int singleNumber(int[] nums) {
HashSet<Integer> set = new HashSet<>();
//循环遍历
for (int x : nums){
//已经存在,则去除
if(set.contains(x)){
set.remove(x);
//否则存入
} else {
set.add(x);
}
}
//返回仅剩的一个元素
return set.iterator().next();
}
}
```
C++ Code:
```cpp
class Solution {
public:
int singleNumber(vector<int>& nums) {
unordered_set<int> set;
//循环遍历
for (int x : nums) {
//已经存在,则去除
if (set.find(x) == set.end()) {
set.insert(x);
//否则存入
} else {
set.erase(x);
}
}
//返回仅剩的一个元素
return *set.begin();
}
};
```
JS Code:
```javascript
var singleNumber = function (nums) {
let set = new Set();
//循环遍历
for (let x of nums) {
//已经存在,则去除
if (set.has(x)) {
set.delete(x);
//否则存入
} else {
set.add(x);
}
}
return set.values().next().value;
};
```
Python Code:
```python
class Solution:
def singleNumber(self, nums: List[int]) -> int:
set_ = set()
# 循环遍历
for x in nums:
# 已经存在,则去除
if x in set_:
set_.remove(x)
# 否则存入
else:
set_.add(x)
# 返回仅剩的一个元素
return set_.pop()
```
### 栈
#### 解析
该方法也很容易想到,我们首先将其排序,然后遍历数组,如果栈为空则将当前元素压入栈,如果栈不为空,若当前元素和栈顶元素相同则出栈,继续遍历下一元素,如果当前元素和栈顶元素不同的话,则说明栈顶元素是只出现一次的元素,我们将其返回即可。这个题目也可以使用队列做,思路一致,我们就不在这里说明啦。下面我们看下动图解析。
![](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/栈.6mzstgebww00.gif)
#### 题目代码
Java Code:
```java
class Solution {
public int singleNumber(int[] nums) {
Arrays.sort(nums);
Stack<Integer> stack = new Stack<>();
for (int x : nums){
if (stack.isEmpty()) {
stack.push(x);
continue;
}
//不同时直接跳出
if (stack.peek() != x) {
break;
}
//相同时出栈
stack.pop();
}
return stack.peek();
}
}
```
C++ Code:
```cpp
class Solution {
public:
int singleNumber(vector<int>& nums) {
sort(nums.begin(), nums.end());
stack<int> stack;
for (int x: nums) {
if (stack.empty()) {
stack.push(x);
continue;
}
//不同时直接跳出
if (stack.top() != x) {
break;
}
//相同时出栈
stack.pop();
}
return stack.top();
}
};
```
JS Code:
```javascript
var singleNumber = function (nums) {
nums.sort();
let stack = [];
for (let x of nums) {
if (!stack.length) {
stack.push(x);
continue;
}
//不同时直接跳出
if (stack[0] !== x) {
break;
}
//相同时出栈
stack.pop();
}
return stack[0];
};
```
Python Code:
```python
from collections import deque
class Solution:
def singleNumber(self, nums: List[int]) -> int:
if len(nums) == 1:
return nums[0]
nums.sort()
stack = deque()
for x in nums:
if not stack:
stack.append(x)
continue
# 不同时直接跳出
if stack[-1] != x:
break
# 相同时出栈
stack.pop()
return stack[-1]
```
### 求和法
#### 解析
这个方法也比较简单,也是借助咱们的 HashSet ,具体思路如下,我们通过 HashSet 保存数组内的元素然后进行求和setsum那么得到的这个和则为去除掉重复元素的和我们也可以得到所有元素和numsum。因为我们其他元素都出现两次仅有一个元素出现一次那我们通过 setsum \* 2 - numsum 得到的元素则为出现一次的数。
![求和解法](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/求和解法.2tds49a3vzq0.png)
上面我们的 SetSum \* 2 - NumSum = z 也就是我们所求的值, 是不是感觉很简单呀。
#### 题目代码
Java Code:
```java
class Solution {
public int singleNumber(int[] nums) {
HashSet<Integer> set = new HashSet<>();
int setsum = 0;
int numsum = 0;
for (int x : nums) {
//所有元素的和
numsum += x;
if (!set.contains(x)) {
//HashSet内元素的和
setsum += x;
}
set.add(x);
}
//返回值
return setsum * 2 - numsum;
}
}
```
C++ Code:
```cpp
class Solution {
public:
int singleNumber(vector<int>& nums) {
unordered_set<int> set;
int setsum = 0;
int numsum = 0;
for (int x : nums) {
//所有元素的和
numsum += x;
if (set.find(x) == set.end()) {
//HashSet内元素的和
setsum += x;
}
set.insert(x);
}
//返回值
return setsum * 2 - numsum;
}
};
```
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 2 * setsum - numsum;
};
```
Python Code:
```python
class Solution:
def singleNumber(self, nums: List[int]) -> int:
return 2 * sum(set(nums)) - sum(nums)
```
### 位运算
#### 解析
这个方法主要是借助咱们的位运算符 ^ 按位异或,我们先来了解一下这个位运算符。
> 按位异或XOR运算符“^”是双目运算符。 其功能是参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为 1。相同时为 0。
> 任何数和 0 异或仍为本身a⊕0 = a
> 任何数和本身异或,为 0a⊕a = 0
> 异或运算满足交换律和结合律a⊕b⊕a = (a⊕a)⊕b = 0⊕b = b
例:
![异或运算](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/异或运算.1myeo11xgqo0.png)
我们通过上面的例子了解了异或运算,对应位相异时得 1相同时得 0那么某个数跟本身异或时因为对应位都相同所以结果为 0 然后异或又满足交换律和结合律。则
![image-20201129120648802](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/image-20201129120648802.2ajgng6zzd7o.png)
#### 题目代码
Java Code:
```java
class Solution {
public int singleNumber(int[] nums) {
int num = 0;
//异或
for (int x : nums) {
num ^= x;
}
return num;
}
}
```
C++ Code:
```cpp
class Solution {
public:
int singleNumber(vector<int>& nums) {
int num = 0;
//异或
for (vector<int>::iterator x = nums.begin(); x != nums.end(); x++) {
num ^= *x;
}
return num;
}
};
```
JS Code:
```javascript
var singleNumber = function (nums) {
//异或
return nums.reduce((num, x) => (num ^= x));
};
```
Python Code:
```python
from functools import reduce
class Solution:
def singleNumber(self, nums: List[int]) -> int:
# 异或
return reduce(lambda num, x: num ^ x, nums, 0)
```
Go Code:
```go
func singleNumber(nums []int) int {
res := 0
for _, x := range nums {
res ^= x
}
return res
}
```
本题一共介绍了 6 种解题方法,肯定还有别的方法,欢迎大家讨论。大家可以在做题的时候一题多解。这样能大大提高自己解题能力。下面我们来看一下这些方法如何应用到其他题目上。