找出数组中只出现1次的数(各种变形)

本文介绍如何利用位运算解决数组中存在特殊次数元素的问题,包括找到只出现一次的单个元素、两个元素及三个元素,同时介绍了如何寻找仅出现一次而其他元素均出现三次的情况。

位运算的应用。

一、数组中只有一个数出现1次,其他数字均出现两次。找出只出现1次的那一个数字。

任何数字和本身异或的结果为0。0和某数字的异或结果为其本身。所以数组中所有数字异或结果就是所求结果。代码如下。

int singleNumber1(vector<int>& nums) {
	int ans=nums[0];
	for(int i=1;i<nums.size();i++){
		ans^=nums[i];
	}
	return ans;
}

二、数组中有两个数字出现1次,其他数字均出现两次。找出只出现1次的那两个数字。

这里还是要借鉴第一节中的思路。如果把一个数组分成两个数组,每个数组中分别包含一个只出现1次的数字即可使用第一节中的思路。那么如何把数组分成两个子数组呢?如何两个只出现1次的数组为a和b,那么整个数组的异或结果为xor_res,那么xor_res=a^b。xor_res不等于0。取xor_res二进制表示中最后一位为1的位置(假设第k位,结果九为1<<k),说明a和b在第k位不相同。我们根据第k位是否为1将数组分成两个部分,对每部分分别求只出现1次的数字即可。

int lastBitOf1(int num){
	return num&(~(num-1));
}
vector<int> singleNumber2(vector<int>& nums) {
	int xor_res=0;
	for(int i=0;i<nums.size();i++){
		xor_res^=nums[i];
	}
	int pos=lastBitOf1(xor_res),one=0,two=0;
	for(int i=0;i<nums.size();i++){
		if(nums[i]&pos){
			one^=nums[i];
		}else{
			two^=nums[i];
		}
	}
	vector<int>result_vec;
	result_vec.push_back(one);result_vec.push_back(two);
	return result_vec;
}


三、数组中有三个数字出现1次,其他数字均出现两次。找出只出现1次的那三个数字。

这里的方法很难想出来。http://zhedahht.blog.163.com/blog/static/25411174201283084246412/里面讲的挺好。这里我的实现与他的实现有些不同。他本身的证明后面也有点麻烦。

大致思路为:假设要求数字为a、b和c,整个数组异或结果为xor_res,xor_res=a^b^c。可证xor_res和a、b、c均不相等。(反证法,如果xor_res=a,则a^a=a^b^c^a=0,则b^c=0,说明b和c相等,矛盾)。所以xor_res^a,xor_res^b,xor_res^c均不为0。

f(x)取x最后1位为1的位置。f(xor_res^a)^f(xor_res^b)^f(xor_res^c)不为0。(因为前两部分异或结果的二进制表示只可能有0个1或者2个1,而第三部分二进制表示只有1个1)

f(xor_res^a)^f(xor_res^b)^f(xor_res^c)既然不为0,假设最后第k位为1。f(xor_res^a)、f(xor_res^b)、f(xor_res^c)三部分中只能有一个第k位为1。(三部分也可以写成f(b^c)、f(a^c)和f(a^b),如果每部分第k位都为1,很容易推出矛盾)。

那么三部分中只能有一个第k位为1。铺垫了这么久,也就是为了根据这个先找出一个。然后使用第二节中的方法找出其他两个。

void singleNumber3(vector<int>&nums){
	int xor_res=0;
	for(int i=0;i<nums.size();i++){
		xor_res^=nums[i];
	}
	int temp=0;
	for(int i=0;i<nums.size();i++){
		temp^=lastBitOf1(xor_res^nums[i]);
	}
	int pos=lastBitOf1(temp),first=0;
	vector<int>temp_vec;
	for(int i=0;i<nums.size();i++){
		if(lastBitOf1(xor_res^nums[i])==pos){
			first^=nums[i];
		}else{
			temp_vec.push_back(nums[i]);
		}
	}
	vector<int>result_vec=singleNumber(temp_vec);
	cout<<first<<endl;
	cout<<result_vec[0]<<endl;
	cout<<result_vec[1]<<endl;
}

四、数组中有一个数字出现1次,其他数字均出现三次。找出只出现1次的那一个数字。

这里就无法再使用前面三节中异或运行了。我们换种思路来想,把每个数字用做二进制表示。int的话只需要32位即可,所以我们新建大小为32的数组,把每个数字的对应位加到数组的相应位置上。如果我们把只出现1次的数字(假设为a)去除,那么最后数组里面的每一位都应该是3的倍数。所以最后数组里任一个位模3之后就是a二进制表示中相应位的值。

int singleNumber(vector<int>& nums) {
	vector<int>vec(32,0);
	for(int i=0;i<nums.size();i++){
		for(int j=0;j<32;j++){
			vec[j]+=(nums[i]>>j)&1;
		}
	}
	int ans=0;
	for(int i=0;i<32;i++){
		ans+=((vec[i]%3)<<i);
	}
	return ans;
}
在有序不递减数组中查找某个值 `x` **第一次出现的位置**,是二分查找的一个经典变种问题 —— **查找左侧边界(lower bound)**。我们需要修改标准二分查找逻辑,使得当存在多个相同值时,找到最左边的那个。 --- ### ✅ 解题思路 1. 数组是**有序且非递减**的,适合使用二分查找。 2. 要找的是目标值 `x` 的**第一次出现位置(左边界)**,即最小的下标 `i` 满足 `arr[i] == x`。 3. 使用**二分查找变形**:即使找到了 `arr[mid] == x`,也不能立即返回,因为左边可能还有相同的值。需要继续向左收缩右边界。 4. 最终通过判断左指针是否指向目标值来确定结果。 --- ### 📌 算法步骤 - 初始化 `left = 0`, `right = n - 1` - 当 `left <= right`: - 计算中点 `mid = left + (right - left) // 2` - 如果 `arr[mid] < target`:说明目标在右半部分 → `left = mid + 1` - 否则(`arr[mid] >= target`):向左收缩 → `right = mid - 1` - 循环结束后,`left` 就是第一个大于等于 `target` 的位置 - 判断 `left < n and arr[left] == target` 成立,则返回 `left + 1`(题目样例输出从1开始) - 否则返回 `-1` > 注意:题目样例输出是从 **1 开始计** 的索引! --- ### ✅ Python 实现代码 ```python def binary_search_leftmost(arr, target): left, right = 0, len(arr) - 1 while left <= right: mid = left + (right - left) // 2 if arr[mid] < target: left = mid + 1 else: right = mid - 1 # 收缩右边界,寻找左边界 # 检查 left 是否为 target 的首出现 if left < len(arr) and arr[left] == target: return left + 1 # 题目要求从1开始编号 else: return -1 # 主程序 n = int(input()) arr = list(map(int, input().split())) q = int(input()) queries = list(map(int, input().split())) results = [] for x in queries: res = binary_search_leftmost(arr, x) results.append(str(res)) print(" ".join(results)) ``` --- ### 🔍 示例解析 输入: ``` 6 1 2 2 2 3 3 3 3 2 5 ``` 处理每个查询: - 查找 `3`:在索引 4 和 5 出现,第一次在索引 4 → 输出 `4+1 = 5` - 查找 `2`:在索引 1一次出现 → 输出 `1+1 = 2` - 查找 `5`:不存在 → 输出 `-1` 输出: ``` 5 2 -1 ``` ✅ 完全匹配样例! --- ### 💡 时间复杂度分析 - 单二分查找时间复杂度:O(log n) - 总共 q 查询:O(q log n) - 满足题目据范围(n, q ≤ 1e5) --- ### ❗注意事项 - 必须找“左侧边界”,不能用普通二分直接返回第一次命中。 - 返回前必须检查 `arr[left] == target`,防止越界或误判。 - 输出是 **1-indexed**,注意加 1。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值