力扣137. 只出现一次的数字 II+260. 只出现一次的数字 III

第一百零四天 --- 力扣137. 只出现一次的数字 II+260. 只出现一次的数字 III

题目一

力扣:137. 只出现一次的数字 II

在这里插入图片描述

思路

Hash算法

我们什么都不考虑,直接统计各个数字出现次数,然后找出现一次的,用哈希表实现,最基础,也最好想。

位运算(遍历统计)

那么,我们现在有了一个新的需求,由于某种原因,现在空间严重不足,让我们在O(1)的空间中,算出来,所以我们想到了位运算。
1、因为大部分出现了m次,目标(仅一个)出现了n次,m,n同为奇数或偶数且m>n。因为m,n特点,所以用不了异或(只有目标出现奇数次,大部分数出现偶数次,直接异或可以保留目标),所以直接十进制方法作废,那就潜入二进制一位一位的看。
2、拿{3,3,3,5}作为例子,具体如下:
在这里插入图片描述
<1> 通过上图,我们不难发现,比如就对于第一位,因为3连续出现3次,也就是会连续出现3个1,我们目标是保住最后的1,怎么办才能干掉连续的3个1?
<2> 很简单,把第一位全相加,出现3次的元素,相加后都是3的倍数,那么目标出现了n次(n<m)就自然成为了余数,所以%就会得到目标。
(如果n=1,那么直接%m就行,但n>1&&n<m的时候,最后得%m,后/n才行)

综上:以上方法适用于任何"大部分出现了m次,目标(仅一个)出现了n次,m,n同为奇数或偶数且m>n"问题的求解

自动机解法

(本方法效率更为高,是由上面的位运算问题演化而来,较难)
(分析办法我画图说明)
我们观察上面位运算方法,针对某一个位,每接收到一个1,状态会变 当接收到3个1后就会回到原点,所以我们可以用有限状态自动机来模拟这个过程。

1、因为int共32位,每一位都一样的算法,最后直接全部整合即可,后面的分析都是基于某一位的。

2、
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
以上,就是完整的分析过程。

代码

1、Hash

class Solution {
public:
	int singleNumber(vector<int>& nums) {
		unordered_map<int, int> item;
		int ans = 0;
		for (int i = 0; i < nums.size(); i++) {
			item[nums[i]]++;//统计
		}
		for (int i = 0; i < nums.size(); i++) {
			if (item[nums[i]] == 1) {//寻找
				ans = nums[i];
			}
		}
		return ans;
	}
};

所有代码均以通过力扣测试
(经过多次测试最短时间为):
在这里插入图片描述
时间复杂度:O(N)
空间复杂度:O(N)

2、位运算

class Solution {
public:
	int find_single_num(vector<int> nums, int remainder) {//传入余数
		int bits[32] = {};
		for (int i = 0; i < nums.size(); i++) {
			for (int j = 0; j < 32; j++) {//统计各个位的和
				bits[j] += nums[i] & 1;
				nums[i] >>= 1;
			}
		}
		for (int i = 0; i < 32; i++) {//取模
			bits[i] %= remainder;
		}
		int ans = 0;
		for (int i = 0; i < 32; i++) {//还原

			ans |= bits[31 - i];
			ans <<= 1;
		}
		return ans;
	}
	int singleNumber(vector<int>& nums) {
		return find_single_num(nums, 3);
	}
};

所有代码均以通过力扣测试
(经过多次测试最短时间为):
在这里插入图片描述

时间复杂度:O(N)
空间复杂度:O(1)

3、自动机

class Solution {
public:
	int singleNumber(vector<int>& nums) {
		int ones = 0, twos = 0;//分别由32个one、two组成
		for (int tmp : nums) {//相当于32位并发运算
			ones = ones ^ tmp&~twos;
			twos = twos ^ tmp&~ones;
		}
		return ones;
	}
};

所有代码均以通过力扣测试
(经过多次测试最短时间为):
在这里插入图片描述

时间复杂度:O(N)
空间复杂度:O(1)

题目二

力扣:260. 只出现一次的数字 III
在这里插入图片描述
在这里插入图片描述

思路:位运算

本题类型:大部分出现了m次,目标出现了n次,m为偶数,n为奇数类型!
1、先回顾 n^n=0、 0 ^ n =n.所以偶数个数相互异或为0,奇数个数相互异或得到自己。
2、所以这样的话就不用上一题中的位运算法了,有一种更为方便的方法,根据上面"2",就是把所有的数放在一起异或,非目标的直接全干掉了,只会留下目标。
3、如果目标唯一,直接得到答案。
4、像本题,有两个目标,那么就得面对一个问题,就是将两个数分离。分离就要找不同点,但是现在得到的是两个目标异或结果,怎么判断不同?
<1> 所以找异或结果中第一个是1的位,它代表了两个目标数在该位不一致,一个是0一个是1
<2> 既然知道了第k位不一致,所以再次全体异或,但是我现在知道了咋分开目标,就是第k位,一个是0,一个是1,所以是0的放在一堆,是1的放在一堆,就分开了

代码

注意:一定要手动为优先级先后加好括号,防止出错!!!!!

class Solution {
public:
	vector<int> singleNumber(vector<int>& nums) {
		vector<int> answers(2);
		int item = 0;
		for (int tmp : nums) {
			item ^= tmp;
		}
		int ans1 = 0, ans2 = 0;
		int point = 1;//寻找第几位,两个目标的二进制数不一致,即为1
		while (1) {
			if ((item & point) != 0) {//& 运算优先级低,一定加好括号
				break;
			}
			point <<= 1;//继续向左走
		}
		for (int tmp : nums) {//分组异或,进而分开两个目标
			if ((tmp & point) == 0) {
				ans1 ^= tmp;
			}
			else {
				ans2 ^= tmp;
			}
		}
		answers[0] = ans1;
		answers[1] = ans2;
		return answers;
	}
};

所有代码均以通过力扣测试
(经过多次测试最短时间为):
在这里插入图片描述

时间复杂度:O(N)
空间复杂度:O(1)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JLU_LYM

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值