阿里面试:妙解三道唯一数题目

我们来看看阿里面试中的三道题目,都跟唯一数有关,层层递进,非常有趣。

小学生也能读懂题目的意思,但并不好做。话不多说,早晨起来,手绘一幅。

图片

涛哥手绘

There is only you in my heart

题目一(简单)

在n个整数中,仅有1个整数出现1次,其余的整数都出现了偶数次,求这个仅出现1次的整数。要求时空复杂度尽可能低。

分析1:用记录次数的方法,空间复杂度不过关,无法通过阿里的面试。

分析2:用排序的方法,时间复杂度不过关,自然无法通过阿里的面试。

分析3:采用异或的方法,直接得出结果,可以通过阿里的面试,程序:

#include <iostream>
using namespace std;

int main()
{
  int a[] = {3, 4, 6, 5, 5, 6, 4};
  int n = sizeof(a) / sizeof(a[0]);
  int result = 0;
  for(int i = 0; i < n; i++)
  {
    result ^= a[i];
  }

  cout << result << endl; // 3
  return 0;
}

 上述程序的结果是3,其背后的逻辑是:


result = 3 ^ 4 ^ 6 ^ 5 ^ 5 ^ 6 ^ 4
       = 3 ^ (4 ^ 4) ^ (5 ^ 5) ^ (6 ^ 6)
       = 3 ^ 0 ^ 0 ^ 0
       = 3

题目之二(不难)

在n个整数中,有1个奇数仅出现一次,有1个偶数仅出现一次,其余的整数都出现了偶数次,求奇数和偶数的值。要求时空复杂度尽可能低。

分析:计数法和排序法都无法通过阿里面试,考虑用异或法。设所有整数为:3,8,4,6,5,5,6,4,把所有整数分为奇数派和偶数派,如下:

奇数派偶数派
3,5,58,4,6,6,4

很显然,可以分别对奇数派和偶数派进行求解,程序如下:


#include <iostream>
using namespace std;

int main()
{
  int a[] = {3, 8, 4, 6, 5, 5, 6, 4};
  int n = sizeof(a) / sizeof(a[0]);
  int result1 = 0;
  int result2 = 0;
  for(int i = 0; i < n; i++)
  {
    if (a[i] % 2 != 0) // 奇数
    {
      result1 ^= a[i];
    }
    else // 偶数
    {
      result2 ^= a[i];
    }

  }

  cout << result1 << endl;  // 3
  cout << result2 << endl;  // 8
  return 0;
}

经自测,程序结果正确。结果为3和8.

题目之三(困难)

在n个整数中,有2个不同的整数分别出现1次,其余的整数都出现了偶数次,求仅出现1次的2个整数。要求时空复杂度尽可能低。

分析:这道题的难度更大了。计数法和排序法都无法通过阿里面试,不予考虑。以3,7,4,6,2,2,4,6为例,该怎样把3和7区分开来呢?通过奇偶肯定不行。我们来看下3和7的二进制:

37
二进制00110111

可以看到,在3和7的二进制中,倒数第三位不同。而且,需要说明的是,由于3和7不同,所以它们的二进制数中,必然存在不同的位。根据这个特征,可以把原来的整数进行划分,分别定义为蓝系和红系,如下:

整数二进制类别
30011蓝系
70111红系
40100红系
60110红系
20010蓝系
20010蓝系
40100红系
60110红系

然后,分别对蓝系和红系进行异或,就求出了3和7,程序如下:

#include <iostream>
using namespace std;

int getOXR(int a[], int n)
{
    int result = 0;
    for(int i = 0; i < n; i++)
    {
        result ^= a[i];
    } 

    return result;
}

int getMask(int r)
{
    int mask = 1;
    while((mask & r) == 0) 
    {
        mask <<= 1;
    } 

    return mask;
}

void getResult(int a[], int n, int &A, int &B)
{
    int result = getOXR(a, n);
    int mask = getMask(result);

    for (int i = 0; i < n; i++)
    {
        if (mask & a[i])
        {
            A ^= a[i];
        }
    }

    B = A ^ result;
}    

int main()
{
    int a[] = {3, 7, 4, 6, 2, 2, 4, 6};
    int n = sizeof(a) / sizeof(a[0]);
    int A = 0, B = 0;
    getResult(a, n, A, B);
    cout << A << endl;  // 7
    cout << B << endl;  // 3

    return 0;
}

经自测,程序结果正确。结果为7和3.

异或逻辑简介

异或,是一种很巧妙的思维,在实际开发中,也会偶尔用到异或,而且,这类按位运算是非常快的。

我们来看如下电路图的动图,它实现的是一位二进制的异或逻辑,其实就是不带进位的二进制加法。

图片

可见:当D1和D2都亮或者都熄灭时,D3熄灭;  而当D1和D2有且仅有一个亮时,D3亮。具体逻辑如下:

D1

操作

D2

D3

0

异或

0

0

0

异或

1

1

1

异或

0

1

1

异或

1

0

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值