有关于重复报错及异或位运算的分析

117 阅读8分钟

一,一个数组,n个元素,元素值的范围在0~n-1之间检测数据有无重复出现,如果重复出现,报错。

暴力:

使用第一个元素与后续所有元素比较,第二个元素与后续所有元素比较......时间消耗O(n^2)空间消耗O(1)。 ​

bool hasDuplicateBruteForce(const vector<int>& nums) {
    int n = nums.size();
    for (int i = 0; i < n - 1; ++i) {
        for (int j = i + 1; j < n; ++j) {
            if (nums[i] == nums[j]) {
                return true; // 发现重复,报错
            }
        }
    }
    return false; // 没有发现重复
}

​ ​

排序:

先排序,后检验相邻两个元素是否相同,遍历一遍即可,但排序所耗费的时间复杂度高。时间复杂度 O(n log2 n)空间复杂度O(log2 n)。

bool hasDuplicateSort(vector<int>& nums) {
    sort(nums.begin(), nums.end()); // 先排序
    for (int i = 0; i < nums.size() - 1; ++i) {
        if (nums[i] == nums[i + 1]) {
            return true; // 相邻元素相同,说明有重复
        }
    }
    return false; // 没有发现重复
}

计数:

定义n个计数器(长度为n数组,把数值当下标,数组基于下标访问,O(1)即可完成,n个O(1)为O(n)),记忆每个元素出现的次数,如果有一个元素出现两次,直接报错。时间复杂度O(n),空间复杂度O(n)

bool hasDuplicateCount(const vector<int>& nums) {
    int n = nums.size();
    vector<int> count(n, 0); // 初始化计数器数组
    for (int num : nums) {
        if (count[num] > 0) {
            return true; // 计数器已经记录过该值,说明有重复
        }
        count[num]++;
    }
    return false; // 没有发现重复
}

以上方法均用时间换空间或用空间换时间的置换,所以接下来将介绍一种兼得的方法。

让每个元素回到自己的下标上,如果每个都能塞回去,自然是没有重复的,这种方法源于下标范围与元素范围的高度重合。但是此方法修改了数组,此为一弊端。

代码逻辑

循环 遍历:0~n-1

{

   (1)检测当前元素与下标是否相同

    1.相同:直接处理下一个元素

    2.不相同:比较当前元素与当前元素作为下标处存放的元素

      1>相等:报错

      2>不相等:交换

}

bool HasCF(int arr[], int length)
{
	for (int i = 0; i < length; i++)//遍历数组每一元素
	{
		while (arr[i] != i)//如果相等 下一个元素 如果不等 比较当前元素与其下表
		{
			int target = arr[i];
			if (arr[target] = target)
			{
				return true;//相同就有重复
			}
			else 
			{ swap(arr[i], arr[target]);
			}//不相同用swap交换
		}
	}

注:这里为大家提供更多的交换方法

int tmp = arr[i];       // 用临时变量保存 arr[i] 的值
arr[i] = arr[target];   // 将 arr[target] 的值赋给 arr[i]
arr[target] = tmp;      // 将临时变量的值赋给 arr[target]

if (i != target) {               // 确保 i 和 target 不相等
    arr[i] ^= arr[target];       // 第一步:异或操作
    arr[target] ^= arr[i];       // 第二步:再次异或操作
    arr[i] ^= arr[target];       // 第三步:最终异或操作
}

int tmp = arr[arr[i]];      // 保存 arr[arr[i]] 的值
arr[arr[i]] = arr[i];       // 将 arr[i] 的值赋给 arr[arr[i]]
arr[i] = tmp;               // 将临时变量的值赋给 arr[i]

二,数组中有一个元素仅出现一次,其他元素均出现两次,如何快速找到只出现一次的元素。

暴力:

用第一个元素与之后元素比较,出现过,就不是该元素。时间复杂度O(n^2),空间复杂度O(1)

int findSingleElementBruteForce(const vector<int>& nums) {
    int n = nums.size();
    for (int i = 0; i < n; ++i) {
        bool foundDuplicate = false;
        for (int j = 0; j < n; ++j) {
            if (i != j && nums[i] == nums[j]) {
                foundDuplicate = true;
                break;
            }
        }
        if (!foundDuplicate) {
            return nums[i];
        }
    }
    return -1; // 如果没有找到,返回-1
}

排序:

排序后检查,一对一对检查,如若这一对不相同,则找到了。时间复杂度O(n log2 n)空间复杂度O(log2 n)

int findSingleElementBySorting(vector<int>& nums) {
    sort(nums.begin(), nums.end()); // 先对数组进行排序
    int n = nums.size();
    for (int i = 0; i < n; i += 2) { // 每次检查一对
        if (i == n - 1 || nums[i] != nums[i + 1]) {
            return nums[i]; // 找到只出现一次的元素
        }
    }
    return -1; // 如果没有找到,返回-1
}

map/哈希表: 一组无序数据反复搜素

注:这里补充一下位运算相关知识

  1. 按位与(&)

    • 规则:对应的两个二进制位都为1时,结果才为1;否则为0。

    • 示例:5 & 3

      • 5的二进制是0101,3的二进制是0011
      • 按位与运算:0101 & 0011 = 0001
      • 结果是1。
  2. 按位或(|)

    • 规则:对应的两个二进制位只要有一个为1,结果就为1;否则为0。

    • 示例:5 | 3

      • 5的二进制是0101,3的二进制是0011
      • 按位或运算:0101 | 0011 = 0111
      • 结果是7。
  3. 按位异或(^)

    • 规则:对应的两个二进制位相同则为0,不同则为1。

    • a ^ a = 0:相同的数异或结果为0。

    • a ^ 0 = a:任何数与0异或结果是它本身。

    • 异或满足交换律和结合律:a ^ b ^ c = (a ^ b) ^ c = a ^ (b ^ c)

    • 计算机底层理解 相等  !(x^y)

    • 计算机清零操作 x^=x 

    • 不使用中间变量实行交换(必须得是两块空间 两个值不同)

      a=a^b;
      b=a^b;
      a=a^b;
      

    • 加密算法

    • 格雷码

    • 示例:5 ^ 3

      • 5的二进制是0101,3的二进制是0011
      • 按位异或运算:0101 ^ 0011 = 0110
      • 结果是6。
  4. 按位取反(~)

    • 规则:对每个二进制位取反,0变1,1变0。需要注意的是,对于有符号数,还会涉及到补码表示。

    • 示例:~5

      • 5的二进制是0101,取反后为1010(这里假设在一个合适的位数范围内,如8位时前面补充符号位等)
      • 结果是一个负数(具体值取决于数据类型的位数和计算机中的补码规则)。
  5. 左移(<<)

    • 规则:将二进制数向左移动指定的位数,右边补0。相当于乘以2的若干次方(移动位数)。

    • 示例:5 << 1

      • 5的二进制是0101,左移1位后为1010
      • 结果是10。
  6. 右移(>>)

    • 规则:将二进制数向右移动指定的位数,对于无符号数左边补0,对于有符号数根据符号位补0或1(算术右移)。

    • 示例:10 >> 1

      • 10的二进制是1010,右移1位后为0101
      • 结果是5。

 异或:

  只要当前两个元素出现在同一个异或表达式中,无论二者距离多远,都能抵消掉,即为0。

例如:

13^6^13=6

三,数组中有两个元素仅出现一次,其他元素均出现两次,如何快速找到只出现一次的元素。

代码逻辑:

(1)整体异或^(用第一个和第二个异或,再用其结果与第三个异或,遍历)

(2)找到 xorResult 中的第一个非零位

  • xorResult 是两个唯一元素的异或结果。在这两个元素的二进制表示中,至少有一位是不同的(否则它们的异或结果会是0)。
  • 我们需要找到 xorResult 中第一个非零位(即该位上两个元素的值不同),并用它来区分这两个元素。
  • 使用一个变量 mask,初始值为1(二进制表示为 0001),通过不断左移(mask <<= 1)找到 xorResult 中第一个非零位。

      3> 与上1 之后左移(&1<<1)常用

res&a;
a<<=1;

(3)根据 mask 将数组分为两组,并分别异或

  • 根据 mask,我们可以将数组中的元素分为两组:

    1. 一组是与 mask 按位与结果为1的元素;
    2. 另一组是与 mask 按位与结果为0的元素。
  • 对每组分别进行异或操作,最终得到的两个结果就是仅出现一次的两个元素。

void findUniqueElements(const vector<int>& nums) {
    // Step 1: 对整个数组进行异或操作
    int xorResult = 0;
    for (int num : nums) {
        xorResult ^= num;
    }

    // Step 2: 找到xorResult中的第一个非零位
    int mask = 1;
    while ((xorResult & mask) == 0) {
        mask <<= 1; // 左移直到找到第一个非零位
    }

    // Step 3: 根据mask将数组分为两组,并分别异或
    int group1 = 0, group2 = 0;
    for (int num : nums) {
        if (num & mask) {
            group1 ^= num; // 属于第一组
        } else {
            group2 ^= num; // 属于第二组
        }
    }

    // Step 4: 输出结果
    cout << "The two unique elements are: " << group1 << " and " << group2 << endl;
}

四,有n个数的数组,无重复,后丢/多了一个,求丢掉的元素。

1.正常之和 S1 当前之和S2 用S1-s2 但是可能会越界。

2。S1^S2即可得到多/少出的元素

#include <iostream>
#include <vector>

int findMissingOrExtraElement(const std::vector<int>& original, const std::vector<int>& modified) {
    // 计算原数组的和 s1
    int s1 = 0;
    for (int num : original) {
        s1 += num;
    }

    // 计算当前数组的和 s2
    int s2 = 0;
    for (int num : modified) {
        s2 += num;
    }

    // 返回丢失或多余的元素
    return s1 - s2;
}

int main() {
    // 示例输入
    std::vector<int> original = {1, 2, 3, 4, 5}; // 原数组
    std::vector<int> modified = {1, 2, 3, 5};    // 当前数组(丢失了 4)

    // 找到丢失或多余的元素
    int result = findMissingOrExtraElement(original, modified);

    // 输出结果
    if (result > 0) {
        std::cout << "丢失的元素是: " << result << std::endl;
    } else {
        std::cout << "多出的元素是: " << -result << std::endl;
    }

    return 0;
}

本博客同步于CSDN,稀土掘金

承蒙厚爱