【整理】数组面试题集锦

参考文献地址:

http://blog.csdn.net/hackbuteer1/article/details/8035261

http://happyprince.iteye.com/blog/1849152

http://blog.csdn.net/zhanglei8893/article/details/6230233

http://www.ahathinking.com/archives/135.html

http://blog.csdn.net/hackbuteer1/article/details/7542774

http://blog.csdn.net/v_JULY_v/article/details/6444021

微软面试100系列答案V0.4版[第41-60题答案]:http://blog.csdn.net/v_july_v/article/details/6171539

1、快速找出一个数组中的最大数、第二大数

2、试着用最小的比较次数去寻找数组中的最大值和最小值

3、重排问题

4、找出绝对值最小的元素

5、从长度为n的数组(元素互不相同)中任意选择m个数的所有组合。

6、一个int数组,里面数据无任何限制,要求求出所有这样的数a[i],其左边的数都小于等于它,右边的数都大于等于它

7、求数组中两个元素差的最大值

8、求数组中重复出现次数大于数组总个数一半的数

9、求逆序对

10、O(1)空间复杂度合并两个排好序的数组

11、找出重复的数字

12、求子数组的最大和

13、和为n连续正数序列 

 

1、快速找出一个数组中的最大数、第二大数

如果当前元素大于最大数 max,则让第二大数等于原来的最大数 max,再把当前元素的值赋给 max。如果当前的元素大于等于第二大数secondMax的值而小于最大数max的值,则要把当前元素的值赋给 secondMax。

void GetSecondMaxNumber(int *arr , int n)
{
    int i , max , second_max;
    max = arr[0];
    second_max = 0x80000000;
    for(i = 1 ; i < n ; ++i)
    {
        if(arr[i] > max)
        {
            second_max = max;
            max = arr[i];
        }
        else
        {
            if(arr[i] > second_max)
                second_max = arr[i];
        }
    }
    cout<<max<<"  "<<second_max<<endl;
}

2、试着用最小的比较次数去寻找数组中的最大值和最小值

每次比较相邻两个数,较大者与MAX比较,较小者与MIN比较,找出最大值和最小值。方法如下:先将一对元素互相进行比较,然后把最小值跟当前最小值进行比较,把最大值跟当前最大值进行比较。因此每两个元素需要3次比较。如果n为奇数,那么比较的次数是3*(n/2)次比较。如果n为偶数,那么比较的次数是3n/2-2次比较。因此,不管是n是奇数还是偶数,比较的次数至多是3*(n/2),具体的代码如下:

void GetMaxAndMin(int *arr , int n , int &max , int &min)
{
    int i = 0 ;
    if(n & 1)     // 奇数
    {
        max = min = arr[i++];
    }
    else
    {
        if(arr[0] > arr[1])
        {
            max = arr[0];
            min = arr[1];
        }
        else
        {
            max = arr[1];
            min = arr[0];
        }
        i += 2;
    }
    
    for( ; i < n ; i += 2)
    {
        if(arr[i] > arr[i+1])
        {
            if(arr[i] > max)
                max = arr[i];
            if(arr[i+1] < min)
                min = arr[i+1];
        }
        else
        {
            if(arr[i+1] > max)
                max = arr[i+1];
            if(arr[i] < min)
                min = arr[i];
        }
    }
}

3、重排问题

给定含有n个元素的整型数组a,其中包括0元素和非0元素,对数组进行排序,要求:
1、排序后所有0元素在前,所有非零元素在后,且非零元素排序前后相对位置不变
2、不能使用额外存储空间
例子如下
输入 0、3、0、2、1、0、0
输出 0、0、0、0、3、2、1
分析
此排序非传统意义上的排序,因为它要求排序前后非0元素的相对位置不变,或许叫做整理会更恰当一些。我们可以从后向前遍历整个数组,遇到某个位置i上的元素是非0元素时,如果arr[k]为0,则将arr[i]赋值给arr[k],arr[i]赋值为0。实际上i是非0元素的下标,而k是0元素的下标。

void Arrange(int *arr , int n)
{
    int i , k = n-1;
    for(i = n-1 ; i >=0 ; --i)
    {
        if(arr[i] != 0)
        {
            if(arr[k] == 0)
            {
                arr[k] = arr[i];
                arr[i] = 0;
            }
            --k;
        }
    }
}

4、找出绝对值最小的元素

给定一个有序整数序列(非递减序),可能包含负数,找出其中绝对值最小的元素,比如给定序列 -5、-3、-1、2、8 则返回1。
分析:由于给定序列是有序的,而这又是搜索问题,所以首先想到二分搜索法,只不过这个二分法比普通的二分法稍微麻烦点,可以分为下面几种情况
如果给定的序列中所有的数都是正数,那么数组的第一个元素即是结果。
如果给定的序列中所有的数都是负数,那么数组的最后一个元素即是结果。
如果给定的序列中既有正数又有负数,那么绝对值的最小值一定出现在正数和负数的分界处。
为什么?因为对于负数序列来说,右侧的数字比左侧的数字绝对值小,如上面的-5、-3、-1,而对于整整数来说,左边的数字绝对值小,比如上面的2、8,将这个思想用于二分搜索,可先判断中间元素和两侧元素的符号,然后根据符号决定搜索区间,逐步缩小搜索区间,直到只剩下两个元素。
单独设置一个函数用来判断两个整数的符号是否相同

bool SameSign(int m , int n)
{
    if((m>>31) == (n>>31))
        return true;
    else
        return false;
}

// 找出一个非递减序整数序列中绝对值最小的数
int MiniNumAbsoluteValue(int *arr , int n)
{
    if(n == 1)
        return arr[0];
    if( SameSign(arr[0] , arr[n-1]) )
        return arr[0] >= 0 ? arr[0] : abs(arr[n-1]);

    // 二分搜索
    int mid , low = 0 , high = n - 1;
    while(low < high)
    {
        if(low + 1 == high)
            return abs(arr[low]) < abs(arr[high]) ? abs(arr[low]) : abs(arr[high]);
        mid = (low + high)>>1;
        if( SameSign(arr[low] , arr[mid]) )
        {
            low = mid ;     // 有可能分界点就在mid处
            continue;
        }
        if( SameSign(arr[high] , arr[mid]) )
        {
            high = mid;
            continue;
        }
    }
    return abs(arr[low]);
}

5、从长度为n的数组(元素互不相同)中任意选择m个数的所有组合。

递归算法:

int arr[] = {1,2,3,4,5,8,5,8,9,10};
int len = 10 , m = 3;

void dfs(int index , int num , vector<int> &vec)
{
    int i ;
    if(index == len+1)
        return ;
    if(num == 0)
    {
        static int k = 1 ;
        cout<<k++<<": ";
        for( i = 0; i < vec.size() ; ++i)
            cout<<vec[i]<<" ";
        cout<<endl;
        return;
    }
    vec.push_back(arr[index]);    // 选取第index个元素
    dfs(index + 1 , num - 1 , vec);
    vec.pop_back();               // 不选取第index个元素
    dfs(index + 1 , num , vec);
}

int main(void)
{
    vector<int>vec;
    dfs(0 , m , vec);
    return 0;
}

6、一个int数组,里面数据无任何限制,要求求出所有这样的数a[i],其左边的数都小于等于它,右边的数都大于等于它

能否只用一个额外数组和少量其它空间实现。
分析:最原始的方法是检查每一个数 array[i] ,看是否左边的数都小于等于它,右边的数都大于等于它。这样做的话,要找出所有这样的数,时间复杂度为O(N^2)。
其实可以有更简单的方法,我们使用额外数组,比如rightMin[],来帮我们记录原始数组array[i]右边(包括自己)的最小值。假如原始数组为: array[] = {7, 10, 2, 6, 19, 22, 32}, 那么rightMin[] = {2, 2, 2, 6, 19, 22, 32}. 也就是说,7右边的最小值为2, 2右边的最小值也是2。
有了这样一个额外数组,当我们从头开始遍历原始数组时,我们保存一个当前最大值 max, 如果当前最大值刚好等于rightMin[i], 那么这个最大值一定满足条件,还是刚才的例子。
第一个值是7,最大值也是7,因为7 不等于 2, 继续,
第二个值是10,最大值变成了10,但是10也不等于2,继续,
第三个值是2,最大值是10,但是10也不等于2,继续,
第四个值是6,最大值是10,但是10不等于6,继续,
第五个值是19,最大值变成了19,而且19也等于当前rightMin[4] = 19, 所以,满足条件。如此继续下去,后面的几个都满足。

int arr[100] , rightMin[100];
void smallLarge(int *arr , int *rightMin , int n)
{
    int i , leftMax;
    rightMin[n - 1] = arr[n - 1];
    for(i = n - 2 ; i >= 0 ; --i)
    {
        if(arr[i] < arr[i+1])
            rightMin[i] = arr[i];
        else
            rightMin[i] = rightMin[i + 1];
    }
    leftMax = 0x80000000;
    for(i = 0 ; i < n ; ++i)
    {
        if(arr[i] >= leftMax)
        {
            leftMax = arr[i];
            if(leftMax == rightMin[i])
                printf("%d\n",leftMax);
        }
    }
}

7、求数组中两个元素差的最大值

后面的元素减去前面的元素(你可以认为你在炒股票,买入价格和卖出价格就是你的盈利),要求:O(N)时间复杂度,O(1)空间复杂度
思路:首先从包含两个元素的数组开始考虑问题,当这个包含两个元素的问题解决了,那么加一个新的元素会造成什么影响?要改变哪些值?每次都添加一个元素,每次都将这些可能的改变考虑进来,这样就能做到遍历整个数组的时候,问题就解决了。

// 后面的元素减去前面的元素 差的最大值
int max_difference(int *arr , int n)
{
    if(arr == NULL || n < 2)    // 非法输入
        return 0;
    int min = arr[0];
    int maxDiff = arr[1] - arr[0];
    for(int i = 2 ; i < n ; ++i)
    {
        if(arr[i-1] < min)
            min = arr[i-1];
        if(arr[i] - min > maxDiff)
            maxDiff = arr[i] - min;
    }
    return maxDiff;
}

8、求数组中重复出现次数大于数组总个数一半的数

int MoreThanHalfNum(int *a , int n )
{
        int i , k , num = a[0];
        int times = 1;
        for(i = 1 ; i < n ; ++i) {
            if(times == 0) {
                num = a[i];
                times = 1;
            }
            else if(a[i] != num)
                --times;
            else
                ++times;
        }
        k = 0;
        for(i = 0 ; i < n ; ++i) {
            if(a[i] == num)
                ++k;
        }
        if(k*2 <= n)
            return -1;    //没有找到
        else
            return num;   //找到
}

 9、求逆序对

设A[1...n]是一个包含n个不同数的数组,如果在i<j的情况下,有A[i]>A[j],则(i,j)就称为A中的一个逆序对(inversion)。

给出一个算法,它能用Θ(nlgn)的最坏情况运行时间,确定n个元素的任何排列中逆序对的个数。

合并排序是采用分治法的思想,若需要排序A[p...q],则可以对半分成A[p...r]和A[r...q],然后将这有序的两部分Merge。而Merge的过程为Θ(n)的时间复杂度,根据主定率T(n)=2(Tn/2)+Θ(n),时间复杂度为T(n)=Θ(nlgn)。而求整个序列中的逆序对,也可以利用分治法的思想,即

   逆序对(A[p...q])= 逆序对(A[p...r])+逆序对(A[r...q])+逆序对(A[p...r], A[r...q]之间的)。

   结合合并排序,关键是求如何在Θ(n)的时间有效的求出A[p...r], A[r...q]之间的逆序对。

   因为在合并排序的Merge过程中,A[p...r]和A[r...q]已经有序,假设此时已经Merge到A[s...r]和A[t...q]。考虑接下来的一步:若从前者取出A[s],说明A[s]比后面的序列A[t...q]中的元素都小,不存在逆序对;若从后者取出A[t],则说明A[t]比前面的序列A[s...r]都小,即以t结尾的逆序对的数量为前者的剩余序列A[s...r]中元素的数量。

   Merge的过程中即可得到A[p...r], A[r...q]之间的逆序对的数量,时间复杂度亦为Θ(n), 由主定律总的时间复杂为 Θ(nlgn)。

#include<iostream>
using namespace std;
 
/* 归并求逆序对数, arr 存储最终有序结果
 * 在函数外申请一个临时数组作为参数传入
 * 避免递归不断创建临时数组的开销
 */
int Merge(int * arr, int beg, int mid, int end, int * tmp_arr)
{
    memcpy(tmp_arr+beg,arr+beg,sizeof(int)*(end-beg+1));
    int i = beg;
    int j = mid + 1;
    int k = beg;
    int inversion = 0;  // 合并过程中的逆序数
    while(i <= mid && j <= end)
    {
        if(tmp_arr[i] <= tmp_arr[j])
        {
            arr[k++] = tmp_arr[i++];
        }else
        {
            arr[k++] = tmp_arr[j++];
            inversion += (mid - i + 1);
        }
    }
    while(i <= mid)
    {
        arr[k++] = tmp_arr[i++];
    }
    while(j <= end)
    {
        arr[k++] = tmp_arr[j++];
    }
    return inversion;
}
 
int MergeInversion(int * arr, int beg, int end, int * tmp_arr)
{
    int inversions = 0;    // 记录倒序数
    if(beg < end)
    {
        int mid = (beg + end) >> 1;
        inversions += MergeInversion(arr, beg, mid, tmp_arr);
        inversions += MergeInversion(arr, mid+1, end, tmp_arr);
        inversions += Merge(arr, beg, mid, end, tmp_arr);
    }
    return inversions;
}
 
/* 测试序列 :answer: 10 */
int testPoint[10] = {
    1, 4, 2, 9, 48,
   15, 13, 44, 6, 90
};
 
void main()
{
    int arrcopy[10];      // 临时数组
    memcpy(arrcopy,testPoint,sizeof testPoint);
 
    printf("the num of inversions is: %d\n",
            MergeInversion(testPoint,0,9,arrcopy));
}

 10、O(1)空间复杂度合并两个排好序的数组

数组al[0,mid-1]和al[mid,num-1]是各自有序的,对数组al[0,num-1]的两个子有序段进行merge,得到al[0,num-1]整体有序。要求空间复杂度为O(1)。注:al[i]元素是支持'<'运算符的。

/*
数组a[begin, mid] 和 a[mid+1, end]是各自有序的,对两个子段进行Merge得到a[begin , end]的有序数组。 要求空间复杂度为O(1)
方案:
1、两个有序段位A和B,A在前,B紧接在A后面,找到A的第一个大于B[0]的数A[i], A[0...i-1]相当于merge后的有效段,在B中找到第一个大于A[i]的数B[j],
对数组A[i...j-1]循环右移j-k位,使A[i...j-1]数组的前面部分有序
2、如此循环merge
3、循环右移通过先子段反转再整体反转的方式进行,复杂度是O(L), L是需要循环移动的子段的长度
*/
#include<iostream>
using namespace std;

void Reverse(int *a , int begin , int end )   //反转
{
    for(; begin < end; begin++ , end--)
        swap(a[begin] , a[end]);
}
void RotateRight(int *a , int begin , int end , int k)    //循环右移
{
    int len = end - begin + 1;  //数组的长度
    k %= len;
    Reverse(a , begin , end - k);
    Reverse(a , end - k + 1 , end);
    Reverse(a , begin , end);
}

// 将有序数组a[begin...mid] 和 a[mid+1...end] 进行归并排序
void Merge(int *a , int begin , int end )
{
    int i , j , k;
    i = begin;
    j = 1 + ((begin + end)>>1);    //位运算的优先级比较低,外面需要加一个括号,刚开始忘记添加括号,导致错了很多次
    while(i <= end && j <= end && i<j)
    {
        while(i <= end && a[i] < a[j])
            i++;
        k = j;   //暂时保存指针j的位置
        while(j <= end && a[j] < a[i])
            j++;
        if(j > k)
            RotateRight(a , i , j-1 , j-k);   //数组a[i...j-1]循环右移j-k次
        i += (j-k+1);  //第一个指针往后移动,因为循环右移后,数组a[i....i+j-k]是有序的
    }
}

11、找出重复的数字

假设你有一个用1001个整数组成的数组,这些整数是任意排列的,但是你知道所有的整数都在1到1000(包括1000)之间。此外,除一个数字出现两次外,其他所有数字只出现一次。假设你只能对这个数组做一次处理,用一种算法找出重复的那个数字。

可以使用异或的办法,即先用1001个数异或,然后更1到1000的这1000个数再异或,结果即重复的数字。

/*
  N为数组元素个数减一,即为数组中最大的数,即数的范围为1-N
  数组长度为N+1
 */
void xor_findDup(int * a,int N)
{
    int i;
    int result=0;
    for(i=0;i<N+1;i++) {
        result ^= a[i];
    }
    for (i=1;i<=N;i++) {
        result ^= i;
    }
    printf("%d\n",result);
}

12、求子数组的最大和

输入一个整形数组,数组里有正数也有负数。数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和。求所有子数组的和的最大值。要求时间复杂度为O(n)。

#include <iostream.h>  
#define n 4           //多定义了一个变量  
  
int maxsum(int a[n]) 
{  
    int max=a[0];       //全负情况,返回最大数  
    int sum=0;  
    for(int j=0;j<n;j++) {  
        if(sum>=0)     //如果加上某个元素,sum>=0的话,就加  
            sum+=a[j];  
        else     
            sum=a[j];  //如果加上某个元素,sum<0了,就不加  
        if(sum>max)  
            max=sum;  
    }  
    return max;  
}  

13、和为n连续正数序列 

输入一个正数n,输出所有和为n连续正数序列。例如输入15,由于1+2+3+4+5=4+5+6=7+8=15,所以输出3个连续序列1-5、4-6和7-8。

我们可用两个数small和big分别表示序列的最小值和最大值。首先把small初始化为1,big初始化为2。如果从small到big的序列的和大于n的话,我们向右移动small,相当于从序列中去掉较小的数字。如果从small到big的序列的和小于n的话,我们向右移动big,相当于向序列中添加big的下一个数字。一直到small等于(1+n)/2,因为序列至少要有两个数字。

void PrintContinuousSequence(int small, int big);
// Find continuous sequence, whose sum is n
void FindContinuousSequence(int n)
{
    if(n < 3)
        return;
    int small = 1;
    int big = 2;
    int middle = (1 + n) / 2;
    int sum = small + big;
    while(small < middle)
    {
        // we are lucky and find the sequence
        if(sum == n)
            PrintContinuousSequence(small, big);
        // if the current sum is greater than n,
        // move small forward
        while(sum > n)
        {
            sum -= small;
            small ++;
            // we are lucky and find the sequence
            if(sum == n)
                PrintContinuousSequence(small, big);
        }
        // move big forward
        big ++;
        sum += big;
    }
}
// Print continuous sequence between small and big
void PrintContinuousSequence(int small, int big)
{
    for(int i = small; i <= big; ++ i)
        printf("%d ", i);
    printf("/n");
}

 

posted @ 2013-09-28 12:03  xmuliushuo  阅读(520)  评论(1编辑  收藏  举报