zoukankan      html  css  js  c++  java
  • 【整理】数组面试题集锦

    参考文献地址:

    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
    ",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
    ",
                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
    ",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");
    }
  • 相关阅读:
    Xcode 构建规则
    创建带阴影,有圆角的UIView的正确方式
    EffectiveC#12,13,14--成员初始化
    EffectiveC#11--选择foreach循环
    EffectiveC#9--明白几个相等运算之间的关系
    EffectiveC#8--确保0对于值类型数据是有效的(初始化问题)
    NET基础课--泛型(NET之美)
    NET基础课-- 类型基础(NET之美)
    EffectiveC#7--选择恒定的原子值类型数据
    EffectiveC#6--区别值类型数据和引用类型数据
  • 原文地址:https://www.cnblogs.com/xmuliushuo/p/3343957.html
Copyright © 2011-2022 走看看