找出数组中只出现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;
}

<think>好的,用户的问题是关于如何在不改变数组元素符号的情况下(即不对偶乘以-1),判断是否存在特定条件的子数组,以及如何找出这样的子数组。用户提到,虽然可能可以判断是否存在这样的子数组,但似乎难以具体定位子数组的位置。我需要仔细分析这个问题,并结合之前的对话历史来提供有效的解答。 首先,回顾用户之前的提问,他们之前询问的是如何确定两个索引i和j,使得子数组的和满足特定条件,比如分割等和子集的问题。当时的解答中使用了前缀和和哈希表的方法,通过记录前缀和及其索引,快速定位满足条件的子数组。现在用户的新问题是在不修改数组元素的情况下,如何实现类似的判断和定位,尤其是当条件改变时,比如可能需要子数组的和为总和的一半,或者其他特定值。 用户提到“不对偶进行乘以-1的操作”,这可能是指之前的某个具体问题中的操作,比如在分割数组时是否需要对元素进行某种变换。假设用户的问题场景可能类似于LeetCode中的“分割等和子集”或“连续子数组和”等问题,但在此场景中,用户希望在不修改原数组的情况下解决问题,并疑惑为何在这种情况下难以找到子数组的具体位置。 我需要先明确用户的问题背景。假设用户的问题是关于寻找一个子数组,其和等于总和的一半,或者满足其他特定条件,但用户没有对数组元素进行任何修改(比如将偶乘以-1),而直接使用前缀和和哈希表的方法来判断是否存在这样的子数组,但发现虽然可以判断存在性,却无法准确定位子数组的起始和结束位置。 接下来,我需要分析为什么在不修改数组的情况下,可能存在这种情况。可能的原因是,当数组中有多个子数组满足相同的前缀和条件时,哈希表中存储的索引可能被覆盖,导致只能找到最后一个符合条件的索引,而无法找到所有可能的子数组。或者,可能存在其他逻辑上的问题,比如哈希表的更新策略导致无法正确回溯起始位置。 需要进一步拆解问题:假设用户的方法是在计算前缀和的同时,将每个前缀和存入哈希表,并在每一步检查是否存在当前前缀和减去目标值的键存在于哈希表中。如果存在,则说明存在一个子数组的和为目标值。但用户发现即使存在这样的子数组,却无法正确获取其起始和结束索引,或者在某些情况下无法找到。 可能的原因包括: 1. **哈希表覆盖问题**:当多个位置的前缀和相同时,哈希表只保存最后一个出现的索引,导致无法找到所有可能的子数组,特别是当子数组可能存在于多个位置时,用户只能得到一个可能的解,而不是全部。 2. **初始条件设置不当**:比如哈希表的初始化可能缺少虚拟头节点(如{0: -1}),导致无法正确处理从数组起始位置开始的子数组。 3. **目标值的计算错误**:比如在分割等和子集问题中,总和必须是偶才能分割,否则直接返回False。如果用户没有正确计算目标值,可能导致后续查找失败。 4. **遍历顺序或更新哈希表的时机问题**:比如在更新哈希表时,是否在检查之后才添加当前前缀和,以避免子数组长度为0的情况(即i和j相同的情况)。 现在需要针对这些可能的问题点,逐步解释用户可能遇到的困难,并提供解决方案,特别是如何在不修改数组的情况下正确找到子数组的起始和结束位置。 另外,用户提到的“不对偶进行乘以-1的操作”可能是指某个特定问题的变形,比如原问题可能需要处理正负交替的情况,而用户想尝试不进行符号变换,直接处理原数组。需要确认这一点,但假设用户的问题更普遍,即如何在未修改原数组的情况下正确使用前缀和和哈希表方法定位子数组。 接下来,需要构建一个清晰的回答结构,首先确认用户的问题场景,然后分析可能的问题所在,再逐步解释如何正确实现,包括哈希表的正确使用、初始条件的设置、如何回溯索引等。同时,通过示例来说明问题,帮助用户理解。 可能的步骤包括: 1. 明确问题:用户希望在不修改数组元素的情况下,使用前缀和和哈希表方法判断是否存在子数组和为特定值,并找出其位置,但遇到困难。 2. 分析问题:检查哈希表的初始化、更新时机、目标值是否正确、覆盖问题等。 3. 解决方案:确保正确初始化哈希表,合理更新哈希表(例如,仅在检查之后才添加当前前缀和,以避免覆盖),正确处理多个相同前缀和的情况。 4. 示例演示:通过具体例子展示正确和错误的做法,帮助用户理解差异。 5. 总结注意事项:强调关键点,如哈希表的维护时机、虚拟头节点的必要性等。 在回答过程中,需要确保符合用户之前设定的系统级指令,如使用正确的中文、LaTeX格式,结构清晰,分步骤解释,使用示例等。 现在需要将这些思考整理成自然的中文回答,确保逻辑连贯,解决用户的疑问,并帮助用户理解如何正确应用前缀和和哈希表方法来定位子数组。</think>--- ### **问题分析** 假设你希望在不修改数组元素(如对偶乘以 `-1`)的情况下,找到和为特定值(例如总和的一半)的子数组。虽然可以通过前缀和与哈希表判断子数组是否存在,但直接沿用之前的代码可能无法精确定位子数组的起止位置。以下是关键原因和解决方法: --- ### **为什么无法定位子数组?** 1. **哈希表的覆盖问题** 哈希表通常记录每个前缀和**最后一次出现的索引**。如果多个位置的前缀和相同,旧的索引会被覆盖,导致只能找到最后一个符合条件的子数组。 **例如**:数组 `[1, -1, 1, -1]`,目标和为 `0`。前缀和序列为 `[0, 1, 0, 1, 0]`。哈希表中 `0` 的索引会被多更新,最终只能记录最后一次出现的位置。 2. **初始条件缺失** 若未正确初始化虚拟起点(如 `hashmap = {0: -1}`),无法处理子数组从索引 `0` 开始的情况。例如,子数组 `nums[0..j-1]` 的和需满足 `preSum[j] - preSum[0] = target`,但若哈希表中未初始化 `preSum[0] = 0`,则无法匹配。 3. **更新哈希表的时机错误** 若在检查之前就将当前前缀和存入哈希表,可能导致错误匹配(如子数组长度为 `0` 的情况)。 --- ### **正确解决方法** #### **步骤 1:明确目标值** 假设目标是找到和为 `target` 的子数组。例如,在分割等和子集问题中,若总和为 `total`,则需: ```python total = sum(nums) if total % 2 != 0: return False # 无法分割 target = total // 2 ``` #### **步骤 2:初始化哈希表** 哈希表需包含虚拟起点 `{0: -1}`,以处理子数组从索引 `0` 开始的情况: ```python hashmap = {0: -1} preSum = 0 ``` #### **步骤 3:遍历时先检查,再更新哈希表** **关键逻辑**:在计算当前前缀和后,先检查是否存在 `preSum - target`,再记录当前前缀和,避免覆盖: ```python for idx in range(len(nums)): preSum += nums[idx] # 先检查是否满足条件 if (preSum - target) in hashmap: start = hashmap[preSum - target] + 1 end = idx return [start, end] # 返回子数组的起止索引 # 若不存在,再记录当前前缀和(避免覆盖) if preSum not in hashmap: hashmap[preSum] = idx return False ``` --- ### **示例演示** 以数组 `[1, 2, 3, 4]` 和目标值 `5` 为例: 1. **前缀和序列**:`[0, 1, 3, 6, 10]` 2. **遍历过程**: - `idx=0`:`preSum=1` → 检查 `1-5=-4` 不在哈希表 → 记录 `1:0` - `idx=1`:`preSum=3` → 检查 `3-5=-2` 不在哈希表 → 记录 `3:1` - `idx=2`:`preSum=6` → 检查 `6-5=1` 存在 → 子数组为 `nums[0+1..2] = [2, 3]` --- ### **关键注意事项** 1. **避免覆盖哈希表** 只在首出现某个前缀和时记录其索引,确保后续匹配时能定位到最早的子数组起点。例如: ```python if preSum not in hashmap: hashmap[preSum] = idx ``` 2. **处理多个解的情况** 若需找到所有可能的子数组,可将哈希表的值改为索引列表: ```python hashmap = defaultdict(list) hashmap[0].append(-1) for idx in range(len(nums)): preSum += nums[idx] if (preSum - target) in hashmap: for start in hashmap[preSum - target]: print("子数组范围:", start+1, idx) hashmap[preSum].append(idx) ``` --- ### **总结** 通过以下改进可以准确定位子数组1. **正确初始化哈希表**:包含虚拟起点 `{0: -1}`。 2. **控制哈希表更新时机**:先检查后记录,避免覆盖和错误匹配。 3. **处理覆盖问题**:仅在首出现前缀和时记录索引,或使用列表存储所有索引。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值