mirror of
https://github.com/chefyuan/algorithm-base.git
synced 2024-11-16 17:13:39 +00:00
231 lines
7.8 KiB
Markdown
231 lines
7.8 KiB
Markdown
## 只出现一次的数
|
||
|
||
> 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
|
||
|
||
示例 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
|
||
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;
|
||
}
|
||
}
|
||
return 0;
|
||
|
||
}
|
||
}
|
||
```
|
||
|
||
### 排序搜索法
|
||
|
||
#### 解析
|
||
|
||
这个方法也是特别容易想到的,我们首先对数组进行排序,然后遍历数组,因为数组中其他数字都出现两次,只有目标值出现一次,所以则让我们的指针每次跳两步,当发现当前值和前一位不一样的情况时,返回前一位即可,当然我们需要考虑这种情况,当我们的目标值出现在数组最后一位的情况,所以当数组遍历结束后没有返回值,则我们需要返回数组最后一位,下面我们看一下动图解析。
|
||
|
||
![排序](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/排序.6sp72k3iaqw0.gif)
|
||
|
||
#### 题目代码
|
||
|
||
```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]){
|
||
continue;
|
||
}else{
|
||
return nums[i-1];
|
||
}
|
||
}
|
||
return nums[nums.length-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
|
||
class Solution {
|
||
public int singleNumber(int[] nums) {
|
||
if (nums.length == 1){
|
||
return nums[0];
|
||
}
|
||
HashSet<Integer> set = new HashSet<>();
|
||
//循环遍历
|
||
for (int x : nums){
|
||
//已经存在,则去除
|
||
if(set.contains(x)){
|
||
set.remove(x);
|
||
}
|
||
//否则存入
|
||
else{
|
||
set.add(x);
|
||
}
|
||
}
|
||
//返回仅剩的一个元素
|
||
return set.iterator().next();
|
||
}
|
||
}
|
||
```
|
||
|
||
|
||
|
||
### 栈
|
||
|
||
#### 解析
|
||
|
||
该方法也很容易想到,我们首先将其排序,然后遍历数组,如果栈为空则将当前元素压入栈,如果栈不为空,若当前元素和栈顶元素相同则出栈,继续遍历下一元素,如果当前元素和栈顶元素不同的话,则说明栈顶元素是只出现一次的元素,我们将其返回即可。这个题目也可以使用队列做,思路一致,我们就不在这里说明啦。下面我们看下动图解析。
|
||
|
||
![栈](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/栈.6mzstgebww00.gif)
|
||
|
||
#### 题目代码
|
||
|
||
```java
|
||
class Solution {
|
||
public int singleNumber(int[] nums) {
|
||
if (nums.length == 1) {
|
||
return nums[0];
|
||
}
|
||
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();
|
||
}
|
||
}
|
||
```
|
||
|
||
|
||
|
||
### 求和法
|
||
|
||
#### 解析
|
||
|
||
这个方法也比较简单,也是借助咱们的 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
|
||
class Solution {
|
||
public int singleNumber(int[] nums) {
|
||
if (nums.length == 1){
|
||
return nums[0];
|
||
}
|
||
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;
|
||
}
|
||
}
|
||
```
|
||
|
||
|
||
|
||
### 位运算
|
||
|
||
#### 解析
|
||
|
||
这个方法主要是借助咱们的位运算符 ^ 按位异或,我们先来了解一下这个位运算符。
|
||
|
||
> 按位异或(XOR)运算符“^”是双目运算符。 其功能是参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1。相同时为0。
|
||
|
||
> 任何数和0异或,仍为本身:a⊕0 = a
|
||
> 任何数和本身异或,为0:a⊕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
|
||
class Solution {
|
||
public int singleNumber(int[] nums) {
|
||
int num = 0;
|
||
//异或
|
||
for (int x : nums) {
|
||
num ^= x;
|
||
}
|
||
return num;
|
||
}
|
||
}
|
||
```
|
||
|
||
本题一共介绍了6种解题方法,肯定还有别的方法,欢迎大家讨论。大家可以在做题的时候一题多解。这样能大大提高自己解题能力。下面我们来看一下这些方法如何应用到其他题目上。 |