找出唯一出现一次的数

1.问题描述:找出数列中唯一一个出现一次的数,其余得数都出现两次。

分析:

  1)最笨的方法当然是穷举了:

#include <stdio.h>

int array[] = { 1 , 3, 3, 1, 5, 5 , 6};

int 
get_only()
{
    int *ptmp1, *ptmp2;
    int *arrend = array + sizeof(array) / sizeof(int);

    for(ptmp1 = array; ptmp1 != arrend; ptmp1 ++)
    {
        for (ptmp2 = array; ptmp2 != arrend; ptmp2++)
        {
            if (ptmp2 != ptmp1 && *ptmp2 == *ptmp1)
            {
                break;
            }
        }

        if (ptmp2 == arrend)
        {
            return ptmp1 - array;
        }
    } 

    if (ptmp1 == arrend)
    {
        return -1;
    }
}

int
main(int argc, char *argv[])
{
    int rst = get_only();
    if (rst == -1)
        printf("no such number\n");
    else
        printf("only num is : %d\n", array[rst]);

    return 0;
}

  2)异或操作 (^):

  性质: 0 ^ 0 = 0, 1 ^ 1 = 0, 0 ^ 1 = 1, 0 ^ A = A;

  总结起来就是按位异或 相同为0,不同为1。

  这样一来明显得出,相同的俩个数异或得0,不同的两个数异或一定不为0(多少另算),0和任和数异或都得其本身(0 ^ A = A)。

  由此得出另一种方法,将数组中的所有元素进行异或操作,则剩下的一个必定是唯一一个只出现一次的数:

#include <stdio.h>

int array[] = { 1 , 3, 3, 1, 5, 5 , 6 };

int get_only2()
{
    int ret = array[0];
    int i;
    printf("len is %d\n", len);
    for (i = 1; i < sizeof(array) / sizeof(int); i++)
    {
        printf("ret is %d\n", ret);
        ret = ret ^ array[i];
    }

    return ret;
}

int 
main(void)
{
    int ret = get_only2();
    printf("only number is %d\n", ret);

    return 0;
}

  btw:运用异或操作还可完成不借助其它参数,交换两个数的值: (用 g++编译,gcc编译不过)

#include <stdio.h>

void
swap(int &a, int &b)
{
    a = a ^ b;
    b = a ^ b;
    a = a ^ b;
}

int 
main(void)
{
    int a = 1;
    int b = 2;

    printf("before swap: a %d, b %d\n", a, b);

    swap(a, b);

    printf("after swap:  a %d, b %d\n", a, b);

    return 0;
}

 

-----------------------------------------------------------------------分割线----------------------------------------------------------

2.问题描述:数列中,只有两个整数只出现一次,其余的出现两次,找出这两个整数.

  有了上边的铺垫,很容易想到用 异或 操作,通常回想到将所有数异或。

  但是发现,若这两个数分别为A, B,则异或后 只能消除其它存在两次的数,但是结果为A ^ B,仍然找不出A, B。

  此时我们希望A和B, 分别出现在两个数组中,并且这两个数组中的各自的其它成员,是两两相同的。然后这两个数组分别各自异或操作,则数组各自只剩下A和B,这样便找出来了。

  但是我们如何分出这样的两个数组呢?

  根据之前分析得出,A和B不相等则 A ^ B 的结果必然不为0,那么这个结果必定可以用16进制表示,并且至少某一位上为1.我们假设从右向左的第x位不为0,即为1.(0x001001...,1的位置为x)

  那么肯定A的第x位为1,B的为0;或者相反。同理,除A,B以外的这些数的第x位上或者为0,或者为1. 此时便可根据第x位为0或者为1,将整个数列分成两个数组且分别包含A和B,姑且叫arrA, arrB。

  这样一分,我们还发现,arrA中除A,以外是两两相同的,arrB除B以外也是两两相同的。最极端的情况是arrA只包含A或者arrB只包含B。但是这不重要,重要的是我们得到了两个数组分别包含A和B且其余两两相同。

  于是,我们分别将arrA中元素异或操作得到A,将arrB中元素异或操作得到B。

  步骤:(g++编译)

  1)先得到A ^ B (get_oxr_rst)

  2)找到第x位上的1,这里的技巧是得到一个mask,只处理这一位 (get_mask)。

  3)用mask & arr[idx], 找到这一位为1的所有元素,并异或操作。

  4)B = orx_rst ^ A.

#include <stdio.h>

int
get_oxr_rst(int *arr, int len)
{
    int rst = 0;
    int idx;
    
    for(idx = 0; idx < len; idx ++)
    {
        rst ^= arr[idx];
    } 

    return rst;
}

int 
get_mask(int oxr_rst)
{
    int mask = 1;
    while((mask & oxr_rst) == 0) 
    {
        mask <<= 1;
    } 

    return mask;
}

int
get_rst(int *arr, int len, int &A, int &B)
{
    if (len < 2)
    {
        return 0;
    }

    int oxr_rst = get_oxr_rst(arr, len);
    int mask = get_mask(oxr_rst);

    int idx;

    for (idx = 0; idx < len; idx++)
    {
        if (mask & arr[idx])
        {
            A ^= arr[idx];
        }
    }

    B = A ^ oxr_rst;

    return 1;
}    

int
main(int argc, char *argv[])
{
    int arr[] = { -4, 2, 2, 4 , 3, 5, 5, 3};
    int A = 0, B = 0;

    int rst = get_rst(arr, sizeof(arr) / sizeof(int), A, B);

    if (rst)
        printf("different number is A %d and B %d\n", A, B);
    else
        printf("wrong args\n");

    return 0;
}
View Code

 3.问题描述:数列中,有三个整数只出现一次,其余的出现两次,找出这三个整数.

  思路1):同样如果按照上边思路来,假设三个不同的值是A, B, C。首先得到 rst = A^B^C

  找到rst的十六进制从右到左第一个1。如果A,B,C在这个位上的值为分别001或010或100。则这种情况下仍可以求出一个mask,

  进而可以将数列分成两组,其中一个必定包括在该位为1的数,假设为A,然后遍历数组,将 arr[idx] & mask != 0的所有值异或,这样便得到了A,然后就简化成了第二个问题。

  但是rst的十六进制从右到左第一个1,还可能是111这个情况,这样一来便无法用思路1来解,因为我们不能知道A,B,C的第x位是否是111,还是001或010或100,只有是后者才能用常规思路。

  到这里得到结论 思路1是行不通的。那么看思路2.

  

  思路2):

  (1)另x = (A ^ B), y = (B ^ C), z = (C ^ A)

  (2)我们有 (A^B)^(B^C)^(C^A) = x ^ y ^ z = (rst ^ C) ^(rst ^ A) ^(rst ^ B) = 0

  (3)x != 0, y != 0, z != 0 , 且 x != y != z, 因为两个相等的数 异或 才等于零,而A, B, C是互不相等的。

  (4)x ^ y ^ z = 0 且 x != y != z != 0, 那么x, y, z每位上的组合只能是000或110,这样才能保证x ^ y ^ z = 0,

     同时又不可能所有位组合全为 000,那样 x = y = z = 0与已知矛盾.由此得  x, y, z中十六进制从右向左数第一次肯定会出现110。

    假设首次出现110组合是从右向左数第m位,且两个1来自 x和y,则可以得到z的m位是0,并且,z第一次出现1的位置在m的左边,即可能是

    从右向左数(m + k = n, k > 0)的位置。举例 若x = 0x1001, y = 0x0001, 则一定有z = 0x1000。

    好了,在继续解释之前,我们要知道 rst ^ arr[i] 的结果集中,肯定包含x, y, z.因为x = rst ^ C, y = rst ^ A, z = rst ^ B,而A, B, C是arr中元素,

    同时,如果我们将 rst ^ arr[i]得到结果集中的每个新元素,按照其从右向左数第一次出现的1是否在第n位上进行分组,则可以得到两组数v1,v2,其中一组包含

    z,另外一组包含x,y。假设包含z的为v1, 包含x,y的为v2. 那么同时这样又将arr数组元素分为两组arr1, arr2,其中arr1中包含B, arr2中包含A,C.我们将 arr1中的所有

    元素异或操作,从而可以得到B。

  (5)找到一个了,另外两个就好找了。

#include <stdio.h>

struct diff_values
{
    int first;
    int second;
    int third;
};

static int
get_oxr_rst(int *arr, int len)
{
    int rst = 0;
    int idx;
    
    for(idx = 0; idx < len; idx ++)
    {
        rst ^= arr[idx];
    } 

    return rst;
}

//a new method to get the mask without using a loop
static int
get_first_onebit_mask(int value)
{
    return value & ~(value - 1);
}

static int
get_mask(int *arr, int len, int oxr_rst)
{
    int idx;
    int mask = 0;

    /*
         if arr[i] = arr[j] then 
             oxr_rst ^ arr[i] = oxr_rst ^ arr[i] then
             get_first_onebit_mask(oxr_rst ^ arr[i]) = get_first_onebit_mask(oxr_rst ^ arr[j]) then
             this operation cross out all arr[i] = arr[j].
         then we get:
             mask = 
             get_first_onebit_mask(oxr_rst ^ (arr[i])  //(arr[i] = A)
             ^ get_first_onebit_mask(oxr_rst ^ arr[j]) //(arr[j] = B)
             ^ get_first_onebit_mask(oxr_rst ^ arr[k]) //(stt[k] = C)
        then mask is what we want whose first "1" is just on postion n
    */
    for (idx = 0; idx < len; idx++)
    {
        //mask ^= get_first_onebit_mask(oxr_rst ^ arr[idx]);

        int tmpmask = get_first_onebit_mask(oxr_rst ^ arr[idx]);
        mask ^= tmpmask;
    }

    return mask;
}

static int 
get_rst2(int *arr, int len, struct diff_values &dv)
{
    if (len < 2)
    {
        return 0;
    }

    int oxr_rst = get_oxr_rst(arr, len);
    int mask = get_first_onebit_mask(oxr_rst);

    int idx;
    int first = 0;

    for (idx = 0; idx < len; idx++)
    {
        if (mask & arr[idx])
        {
            first ^= arr[idx];
        }
    }

    dv.second = first;
    dv.third = first ^ oxr_rst;

    return 1;
}

int
get_rst(int *arr, int len, struct diff_values &dv)
{
    if (len < 3)
    {
        return 0;
    }

    int oxr_rst = get_oxr_rst(arr, len);
    int mask = get_mask(arr, len, oxr_rst);

    int idx;
    int first = 0;
    for (idx = 0; idx < len; idx++)
    {
        if (get_first_onebit_mask(oxr_rst ^ arr[idx]) == mask) //this operation equals to get v1.
        {
            first ^= arr[idx]; //this equals to the oxr operation in arr1 which contains B as the example
                              // and the result is one of value of A, B , C.  
        }
    }
    
    //when we get there, it means we get one of the three unique numbers;
    //then we get the left two numbers;

    //exchange first with arr[len - 1], 
    for (idx = 0; idx < len; idx++)
    {
        if (first == arr[idx])
        {
            arr[idx] = arr[idx] ^ arr[len - 1];
            arr[len - 1] = arr[idx] ^ arr[len - 1];
            arr[idx] = arr[idx] ^ arr[len - 1];
            break;
        }
    }

    dv.first = first;

    //only pass len - 1 values into arr, so that we can delete value first
    get_rst2(arr, len - 1, dv);

    return 1;
}    

int
main(int argc, char *argv[])
{
    int arr[] = { 3, 3, -4,5, 5, 8, 9 };
    struct diff_values dv;

    int rst = get_rst(arr, sizeof(arr) / sizeof(int), dv);

    if (rst)
        printf("different number is A %d , B %d , C %d\n", dv.first, dv.second, dv.third);
    else
        printf("wrong args\n");

    return 0;
}

 参考:

  http://www.cnblogs.com/luxiaoxun/archive/2012/09/08/2676610.html

  https://www.lijinma.com/blog/2014/05/29/amazing-xor/

  http://baike.baidu.com/link?url=SeBrbp2jNqe1N-hF_j4UWt953O7ra73Seo__jW2uEbXeRuo-wLTKkROZZAV_jCTLICKw7oSQQRQmZovRCaFYkcC3MIS2jSd7wR9kVVIbjKm

  

以后碰到类似的题,继续补充,欢迎指正与指点。

  

  

  

   

 

  

 

posted @ 2016-09-23 02:11  mr_yu  阅读(1326)  评论(0编辑  收藏  举报