编程语言
首页 > 编程语言> > C算法--数组中消失的数字和单身狗问题-异或

C算法--数组中消失的数字和单身狗问题-异或

作者:互联网

第一:消失的数字

对于一个int数组其特点为:
1. 其元素个数为n
2. 每个元素的值为[0,n]之间(前闭后闭区间)
3. 元素的值没有重复,即0-n,这n+1个数字中的n个数字组成的数组

1.1 问题

在时间复杂度为O(n)和空间复杂度为O(1)的条件下,找到缺失的数字

1.2 解法

利用异或的特点:
    1. 0 ^ A = A
    2. 0 ^ A ^ A = 0
将0 与数组中每个元素以及0-n分别进行异或计算 ;
由于性质2,可得结果 = 0 ^ 缺失数字 = 缺失数字(性质1)

1.3 代码实现

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

int miss_number(int* arr, int len)
{
    // 利用异或算法进行求解
    // 异或的特点
    // 1. 异或计算的数对应的补码的二进制的异或,相同则为0,相异则为0
    // 2. 两个相同数的异或为0
    // 3. 0与任意正数(A)的异或均为任意正数其本身, 即0 ^ A = A
    // 4. 0与任意正数的异或两次,结果为0,即0 ^ A ^ A = 0;
    // 5. 且异或满足交换律,即 A ^ B ^ C = A ^ C ^ B = C ^ A ^ B
    // 因此,当0与数组中的每个元素均进行异或,在于[0,len]之间的整数分别异或
    // 即0余2*len - 1个数进行异或,由于只有缺失的数字出现一起,其余的均出现两次
    // 根据性质4,结果为0 ^ 缺失的数字 = 缺失的数字
    int ret = 0;
    int i;
    for (i = 0; i < len; i++)
    {
        ret ^= arr[i];
    }
    for (i = 0; i <= len; i++)
    {
        ret ^= i;
    }
    return ret;
}
int main()
{
    // 用于查找的数组
    int arr[] = {9,8,4,2,3,5,7,0,1};
    // 获得数组的长度
    int len = sizeof(arr) / sizeof(arr[0]);
    // 查找并返回数组中缺失的数字
    int ret = miss_number(arr, len);
    printf("the miss number is %d\n", ret);
    return 0;
}

1.4 结果展示

C算法--数组中消失的数字和单身狗问题-异或

第二:找单身狗问题

数组的特点
1. 数组的元素个数为2n + 1个
2. 其中n个数字出现两次
3. 1个数字出现1次

2.1 问题

找出只出现一次的那个数字
解法:对所有的元素进行异或操作,其结果为只出现一次的的那个数字。
因为两个相同数字的异或结果为0.
0余任意数的异或结果为其自身

2.2 代码部分

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

int fine_single(int* arr, int len)
{
    int i;
    int ret = 0;
    for (i = 0; i < len; i++)
    {
        ret ^= arr[i];
    }
    return ret;
}
int main()
{
    // 用于查找的数组
    int arr[] = {9,8,9,0,1,5,8,0,1};
    // 获得数组的长度
    int len = sizeof(arr) / sizeof(arr[0]);
    // 查找并返回数组中单独的数字
    int ret = fine_single(arr, len);
    printf("the single number is %d\n", ret);
    return 0;
}

2.3 结果

C算法--数组中消失的数字和单身狗问题-异或

第三:两个单身狗

数组描述:
1. 数组的长度为2*n
2. 数组中有n-1的元素出现两次,或4次,或2k次,k > 0
3. 数组中有2个元素值出现一次

3.1 问题

找出该数组中只出现一次的那两个数字。
要求:时间和空间复杂度分别为O(n)和O(1)

3.2 解法

1. 对数组中所有的元素进行异或操作,根据异或的性质(A ^ A = 0)得到其结果为两个出现一次数字的异或结果ret。
2. 对数组中的元素分为两组
    2.1 按照ret中位为1的任意一个位对数组进行分组。
    2.2 将该位的值为1的元素分到一组,为1的元素分到另外一组。
3. 分组的结果:
    3.1 将两个出现一次的数字分到不同的组,因为他们在该位的异或结果为1,故他们在该位的值为0和1,故将他们分到不同的组。
    3.2 将出现2次的数字分到相同的组,因为相同的数字,在该位的值相同(0或1),过分组时,分到同一组。
    3.3 每组的元素中个包含一个只出现一次的数字,其余数字均出现两次。
4. 转化为单身狗的问题。

3.3 代码实现1

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
void fine_double_single(int* arr, int len, int* sig) 
{
    int i;
    int ret = 0; // 保存每个元素与0的异或的结果
    int oa = 1;  // 找到ret中位为1的位,用于按位与进行分组
    int idx = 0;  // 用于记录分组的元素个数
    int index = 0; // 用于记录组的索引
    int oav;       // 用于分组

    int* arr1 = (int*)malloc(sizeof(int) * len); // 动态开辟内存空间,用于存储分组的元素
    if (NULL == arr1) { return; }

    // 对所有元素进行异或处理,得到两个只出现一次的数字的异或的结果
    for (i = 0; i < len; i++)
    {
        ret ^= arr[i];
    }
    // 找到一个ret中位为1的位,让该位为1,其余位为0的数
    for (i = 0; i < sizeof(int) * 8; i++)
    {
        if (0 != (ret & oa))
            break;
        oa <<= 1; 
    }
    for (index = 0; index < 2; index++)
    {
        idx = 0; 
        if (0 == index)
            oav = 0;
        else
            oav = oa;
        for (i = 0; i < len; i++)
        {
            if (oav == (arr[i] & oa))
            {
                arr1[idx] = arr[i];
                idx++;
            }
        }
        ret = 0;
        for (i = 0; i < idx; i++)
        {
            ret ^= arr1[i];
        }
        sig[index] = ret; 
    }
    free(arr1); 
    arr1 = NULL;
}
int main()
{
    int sig[2] = { 0 };
    // 用于查找的数组
    int arr[] = { 9, 8, 9, 0, 1, 5, 8, 0, 1, 7 };
    // 获得数组的长度
    int len = sizeof(arr) / sizeof(arr[0]);
    // 查找并返回数组中两个单独的数字
    fine_double_single(arr, len, sig);
    printf("the double single number is %d and %d\n", sig[0], sig[1]);
    return 0;
}

C算法--数组中消失的数字和单身狗问题-异或

3.4 代码实现2(简洁不用动态分配内存)

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>

fine_double_single(int* nums, int numSize, int* retSigs)
{
    // 假设两个单独出现的数字分别为x1和x2;
    int x1, x2;
    // 第一步用0对数组中的所有元素进行异或操作
    // 因为0 ^ A = A and A ^ A = 0
    // 因此得到的结果为两个单独出现数字的异或结果,即ret = x1 ^ x2
    int ret = 0;
    int i;
    for (i = 0; i < numSize; i++)
    {
        ret ^= nums[i];
    }

    // 第二步 找到ret比特位中任意一个为1的位(M),因为位相同时,异或为0,不同时,异或为1.
    // 这样根据M位为1,其余位为0的值进行按位与计算 可以把x1和x2分到不同的组中
    // 从最低位开始搜索,如果按位与为0,则进行左移操作,直到找到为1的位,这样按位与结果不为0
    int m = 0;
    for (m = 0; m < sizeof(int) * 8; m++)
        if (ret & (1<<m)) break;

    // 进行计算,由于分组,需要分配空间来存储指定的元素,比较麻烦
    // 因此将依次得到的指定组的元素直接进行计算,即直接与0依次进行异或操作
    // 因为分组后,接下来要做的无非就是让每个元素与0分别进行异或操作
    // 因此设x1和x2均为0
    x1 = 0;
    x2 = 0;
    for (i = 0; i < numSize; i++)
    {
        if (nums[i] & (1 << m))
        {
            x1 ^= nums[i];
        }
        else
        {
            x2 ^= nums[i];
        }
    }

    // 将结果保存到retSigs数组中
    retSigs[0] = x1;
    retSigs[1] = x2;
}

int main()
{
    int sig[2] = { 0 };
    // 用于查找的数组
    int arr[] = { 9, 8, 9, 0, 1, 5, 8, 0, 1, 7 };
    // 获得数组的长度
    int len = sizeof(arr) / sizeof(arr[0]);
    // 查找并返回数组中两个单独的数字
    fine_double_single(arr, len, sig);
    printf("the double single number is %d and %d\n", sig[0], sig[1]);
    return 0;
}

C算法--数组中消失的数字和单身狗问题-异或

标签:数字,--,ret,int,异或,len,单身,数组
来源: https://blog.51cto.com/u_15132389/2847665