本次总结在数组中查找特定的元素。
碰到的面试题目主要包括:
1.假如你有一个用1001个整数组成的数组,这些整数是任意排列的,但是你知道所有的整数都在1到1000之间(包括1000)、此外,除了一个数字出现两次外,其他的数字只出现了一次。假设你对数组做一次处理,用一种算法找出重复的那个数字,要求不使用大量额外的存储空间(即要求辅助空间为O(1))
题目的意思很明显,1001个数字中只有一个数字出现了两次,其余的都是一次。并且不缺少任何数字。那么找到这个数字,我们可以有很多思路:
思路a:求和,最简单也最容易想出来的方法 1+2+3+4+。。。+1000的和与数组的和求差,差的结果就是这个数字,缺点是,求和的话,结果可能会溢出。不妨看思路2
思路b:异或,位操作总有让你惊喜的地方,原理是:
@ 如果两个相同的数求异或,那么结果为0。
@ 0与一个数异或的结果为这个数
@ a^b ^a = b ;a^b^b = a;
据此,可以对数组的元素与1,2,34,56,7,8...1000依次异或,最后的结果就是出现两次的数字。
相应的代码如下:
- #include <stdio.h>
- #include <stdlib.h>
- int findTheNum(int *a){
- int k = a[0];
- for(int i=1;i<=1000;i++){
- k ^= (a[i]^i);
- }
- return k;
- }
- main(){
- int a[1001];
- for(int i = 0;i<=999;i++ ){
- a[i] = i+1;
- }
- a[1000] = rand()%1000;
- printf("the num : %d %d \n",a[1000],findTheNum(a));
- return 0;
- }
2.腾讯的一道面试题:在一堆数字中,有两个数字出现了奇数次,其余的数字出现了偶数次,设计一种算法找出这两个数字。要求时间复杂度为O(n),空间复杂度为O(1)
假设这两个数为a,b,将数组中所有元素异或结果x=a^b,判断x中位为1的位数(注:因为a!=b,所以x!=0,我们只需知道某一个位为1的位数k,例如0010 1100,我们可取k=2或者3,或者5),然后将x与数组中第k位为1的数进行异或,异或结果就是a,b中一个,然后用x异或,就可以求出另外一个。
据此,不难写出代码:
- #include <stdio.h>
- void getNum(int a[],int len,int &one,int &two){
- int res = 0;
- for(int i = 0;i<len;i++){
- res = res^a[i];
- }
- int temp = res;
- int k = 0;
- while(!(temp&1)){
- temp = temp>>1;
- k++;
- }
- temp = 0;
- for(int i = 0;i < len;i++){
- if((a[i]>>k)&1){
- temp = temp^a[i];
- }
- }
- one = temp ;
- two = temp^res;
- }
- int main(){
- int a[] = {2,3,4,4,5,6,6,5};
- int one ,two;
- getNum(a,8,one,two);
- printf("%d --- %d \n",one,two);
- return 0;
- }
以该题为例,算法的扫描过程如下图所示:
3。数组中有多个数字只出现了一次,且这些数字都在1000之间,用一种算法找出第一次只出现一次的数字,要求线性时间。(类似的有,在一个字符串中找到第一个只出现一次的字符)
类似这种找“第一次出现一次的数字(字符)”的题目有一个共同的特点:数字或字符的范围是确定的。因此我们可以考虑计数。具体思路是:首先扫描一次原数组,记录每个数字的出现次数,然后再一次扫描原数组,并查询其出现的次数,一旦遇到出现次数为1的数字,那么就返回这个数字即可。相应的代码如下:
- #include <stdio.h>
- #include <string.h>
- #define N 1000
- int getFirstOne(int *a,int n){
- int b[N+1];
- memset(b,0,N+1);
- for(int i = 0;i < n;i++){
- b[a[i]]++;
- }
- for(int i = 0;i < n;i++){
- if(b[a[i]] == 1){
- return a[i];
- }
- }
- }
- int main(){
- int a[] = {1,2,1,2,3,4,5,4,5};
- printf("%d \n",getFirstOne(a,9));
- }
4.一个输入流,很大,大到没有存储器可以将其存储下来,从这个输入流中随机取出m个元素,要求各个元素被取出的概率相同。输入流只能扫描一次。
这个题目有一种算法叫做蓄水池抽样,思路大致是这样的,先选中前m个, 从第m+1个元素到最后一个元素为止, 以m/i (i=m+1, m+2,...,N) 的概率选中第i个元素, 并且随机替换掉一个原先选中的元素, 这样遍历一次得到m个元素, 可以保证完全随机选取。
相应的代码如下:(这里只说明原理,注意rand()函数并不是真正意义上的随机数,具体可见:http://wenku.baidu.com/view/9bee261eb7360b4c2e3f6418.html)
- #include <stdio.h>
- #include <stdlib.h>
- #define N 10000
- #define M 20
- int getRandM(int *a,int n,int *result,int m){
- int i,j;
- for(i = 0;i < m;i++){
- result[i] = a[i];
- }
- for(j = i; j < n; j++){
- int randN = rand()%j;
- if(randN >= 0 && randN < m){
- int randChange = rand()%m;
- result[randChange] = a[j];
- }
- }
- }
- int main(){
- int inStream[N];
- for( int i = 0;i < N;i++){
- inStream[i] = i;
- }
- int outStream[M];
- getRandM(inStream,N,outStream,M);
- for(int i = 0;i<M;i++){
- printf("%d ",outStream[i]);
- }
- printf("\n");
- }
另:在有序数组中查找符合条件的两个数字的解法见:双指针法的应用: http://blog.youkuaiyun.com/ohmygirl/article/details/7850068
在数组中找k个数的和为m的算法。
其他有关数组中选取特定元素的题目待补充。