数组

1、写一个函数找出一个整数数组中第二大的数。

复制代码
// 时间复杂度O(n)
const int MINNUMBER = -32767;
int find_sec_max(int data[], int count)
{
    int maxnumber = data[0];
    int sec_max = MINNUMBER;
    for(int i = 1; i < count; i++)
    {
        if(data[i] > maxnumber)
        {
            sec_max = maxnumber;
            maxnumber = data[i];
        }
        else
        {
            if(data[i] > sec_max)
            sec_max = data[i];
        }
    }
    return sec_max;
}
复制代码

2、寻找平衡点问题

平衡点:比如int numbers[]={1,3,5,7,8,25,4,20}; 25前面的总和为24,25后面的总和也是24,25这个点就是平衡点。假如一个数组中的元素,其前面的部分等于后面的部分,那么这个点的位序就是平衡点,要求返回任何一个平衡点。

复制代码
int calcBalance(int arr[], int length)
{
    int *left = new int[length]; //left[i]为从第0个到第i-1个的和
    int *right = new int[length]; //right[i]为从第i+1个到第len-1个的和
    int b = length-1;
    for(int i=0; i<length; i++)
    {
    if(i == 0)   
            left[i] = 0;   
        else  
            left[i] = left[i-1]+arr[i-1];   
    }   
    for(; b>=0; b--)   
    {   
        if(b == length-1)   
            right[b] = 0;    
        else  
            right[b] = right[b+1]+arr[b+1];   
        if(left[b] == right[b])
        {
            delete[] left;
            delete[] right;
            return b;
        }
    }

    delete[] left;
    delete[] right;
    return -1;
}
复制代码

3、输入两个整数序列。其中一个序列表示栈的push顺序,判断另一个序列有没有可能是对应的pop顺序。为了简单起见,我们假设push序列的任意两个整数都是不相等的。 比如输入的push序列是1、2、3、4、5,那么4、5、3、2、1就有可能是一个pop系列。

复制代码
bool IsPossiblePopOrder(const int* pPush, const int* pPop, int nLen)
{
    stack<int> stackData;
    int i = 0;
    int j = 0;
    while(i < nLen || j < nLen)
    {
        if(i < nLen)
        {
            stackData.push(pPush[i]);
            ++ i;
        }
        while(!stackData.empty() && stackData.top() == pPop[j])
        {
            stackData.pop();
            ++ j;
        }
        if(j == nLen) break;
    }
    return (stackData.empty() && i == nLen);
}
复制代码

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

方法一:将数组a[]排序为数组b[],比较两个数组,如果a[i]==b[i],则a[i]即为一个这样的数。时间复杂度为O(NlogN)。

方法二:维护两个数组min[]和max[],max[i]为a[i]左边的数的最大值,min[i]为a[i]右边的最小值,扫描两遍数组就可得到,再比较,如果max[i]<=a[i]<=min[i],则a[i]是一个这样的数。

复制代码
void FindElements(int *pArray, int len)  
{  
    if(pArray == NULL || len <= 0 )  
        return ;  
  
    int *pMin = new int[len];  
    int *pMax = new int[len];  
    int i;  
  
    pMax[0] = pArray[0];  
    for(i = 1; i < len; i++)       //计算自i往前最大值的辅助数组   
        pMax[i] = (pMax[i-1] >= pArray[i])? pMax[i-1]: pArray[i];  

    pMin[len-1] = pArray[len-1];  
    for(i = len - 2; i >= 0; i--) //计算自i开始最小值的辅助数组   
        pMin[i] = (pMin[i+1] <= pArray[i])? pMin[i+1]: pArray[i];  
  
    if(pArray[0] <= pMin[0])     //检查第1个元素是否满足条件   
        cout<<pArray[0]<<' ';  
    for(i = 1; i < len - 1; i++)  
    {  
        if(pArray[i] >= pMax[i] && pArray[i] <= pMin[i]) //满足这个关系式的元素符合要求   
            cout<<pArray[i]<<' ';  
    }  
    if(pArray[len-1] >= pMax[len-1]) //检查第len个元素是否满足条件   
        cout<<pArray[i];  
    cout<<endl;  
  
    delete [] pMin;  
    delete [] pMax;  
    pMin = pMax = NULL;  
}  
复制代码

5、N个元素的数组循环右移K位,要求时间复杂度为O(N)

复制代码
//普通实现
void RightShift(int* arr, int N, int K)
{
    K %= N;
    while(K--)
    {
        int t = arr[N-1];
        for(int i = N-1; i > 0; i --)
            arr[i] = arr[i-1];
        arr[0] = t;
    }
}

//递归实现
void MoveCirce(int *data, int n, int k) 
{
    int temp = data[n-1];
    for(int i = n-1; i > 0; --i)
        data[i] = data[i-1];
    data[0] = temp;
    -- k;
    if(k > 0) MoveCirce(data,n,k);
}
复制代码

假设原数组序列为abcd1234,要求变换成的数组序列为1234abcd,即循环右移了4位。比较之后,不难看出,其中有两段的顺序是不变的:1234和abcd,可把这两段看成两个整体。右移K位的过程就是把数组的两部分交换一下。变换的过程通过以下步骤完成:

1.   逆序排列abcd:abcd1234 → dcba1234; 2.   逆序排列1234:dcba1234 → dcba4321; 3.   全部逆序:dcba4321 → 1234abcd。

复制代码
//翻转函数
void Reverse(int* arr, int b, int e)
{
    for(; b < e; b++, e--)
    {
        int temp = arr[e];
        arr[e] = arr[b];
        arr[b] = temp;
    }
}

//循环右移
void RightShift(int* arr, int N, int K)
{
    K %= N;
    Reverse(arr, 0, N-K-1);
    Reverse(arr, N-K, N-1);
    Reverse(arr, 0, N-1);
}

//循环左移
void LeftShift(int* arr, int N, int K)
{
    K %= N;
    Reverse(arr, 0, K-1);
    Reverse(arr, K, N-1);
    Reverse(arr, 0, N-1);
}
复制代码

6、发帖水王:“水王”发帖数目超过了帖子总数的一半

如果一个ID出现的次数超过总数N的一半。那么,无论水王的ID是什么,这个有序的ID列表中的第N/2项(从0开始编号)一定会是这个ID(读者可以试着证明一下)。省去重新扫描一遍列表,可以节省一点算法耗费的时间。如果能够迅速定位到列表的某一项(比如使用数组来存储列表),除去排序的时间复杂度,后处理需要的时间为O(1)。

如果每次删除两个不同的ID(不管是否包含“水王”的ID),那么,在剩下的ID列表中,“水王”ID出现的次数仍然超过总数的一半。看到这一点之后,就可以通过不断重复这个过程,把ID列表中的ID总数降低(转化为更小的问题),从而得到问题的答案。新的思路,避免了排序这个耗时的步骤,总的时间复杂度只有O(N),且只需要常数的额外内存。伪代码如下:

复制代码
int Find(int* ID, int N)
{
    int candidate, nTimes, i;
    for(i = nTimes = 0; i < N; i++)
    {
        if(nTimes == 0)
        {
            candidate = ID[i], nTimes = 1;
        }
        else
        {
            if(candidate == ID[i])
                nTimes++;
            else
                nTimes--;
        }
    }
    return candidate;
}
复制代码

扩展问题:统计结果表明,有3个发帖很多的ID,他们的发帖数目都超过了帖子总数目N的1/4。你能从发帖ID列表中快速找出他们的ID吗?

复制代码
void Find(int* ID, int N, int & candidate1, int & candidate2, int & candidate3)
{
    int nTimes1 = 0;
    int nTimes2 = 0;
    int nTimes3 = 0;

    for(int i = 0; i < N; i++)
    {
        if (nTimes1 == 0)
        {
            candidate1 = ID[i], nTimes1 = 1;
        }
        else
        {
            if (candidate1 == ID[i])
            {
                nTimes1++;
            }
            else if (nTimes2 == 0)
            {
                candidate2 = ID[i], nTimes2 = 1;
            }
            else
            {
                if (candidate2 == ID[i])
                {
                    nTimes2++;
                }
                else
                {
                    if (nTimes3 == 0)
                    {
                        candidate3 = ID[i], nTimes3 = 1;
                    }
                    else if (candidate3 == ID[i])
                    {
                        nTimes3++;
                    }
                    else
                    {
                        nTimes1--;
                        nTimes2--;
                        nTimes3--;
                    }
                }
            }
        }
    }
}
复制代码

7、寻找最大的K个数

寻找N个数中最大的K个数,本质上就是寻找最大的K个数中最小的那个,也就是第K大的数。
线性时间选择算法:RandomizedSelect()算法

复制代码
//随机划分函数
int random_partition(int a[],int l,int r)
{
    int i = l+rand()%(r-l+1);//生产随机数
    int temp = a[i];
    a[i] = a[l];
    a[l] = temp;
    return partition(a,l,r);//调用普通划分函数
}

//线性寻找第k大的数
int random_select(int a[],int l,int r,int k)
{
    int i,j;
    if(l == r) //递归结束
    {
        return a[l];
    }
    i = random_partition(a,l,r);//划分
    j = i-l+1;
    if(k == j) //递归结束,找到第K大的数
        return a[i];
    if(k < j)
    {
        return random_select(a,l,i-1,k);//递归调用,在前面部分查找第K大的数
    }
    else
        return random_select(a,i+1,r,k-j);//递归调用,在后面部分查找第K大的数
}
复制代码

具体实现:http://www.cnblogs.com/luxiaoxun/archive/2012/08/06/2624799.html

8、快速寻找和等于一个给定的数字的两个数

解法一:穷举法,从数组中取出任意两个数字,计算两者之和是否为给定的数字。时间复杂度为O(N^2)

解法二:假设和为Sum,对于数组中每个数字arr[i]都判断Sum-arr[i]是否在数组中,就变成一个查找问题。提高查找效率,先排序,再用二分查找法等方法进行查找,查找的时间复杂度从O(N)降到O(logN),总的时间复杂度为O(N*logN)。 更快的查找方法:hash表。给定的一个数字,根据hash映射查找另一个数字是否在数组中,只需O(1)的时间,这样总的时间复杂度降低到O(N),但这需要额外的O(N)的hash表存储空间。

解法三:先对数组排序sort(a,n),时间复杂度为O(N*logN),然后按下面的算法(O(N)的时间复杂度)查找,总的时间复杂度为O(N*logN)。用两个指针i和j指向第一和最后一个元素,如果a[j]+a[i]<sum时,++i,增大和值,否则--j,减小和值。

复制代码
void FindTwoNumbers(int *a, int n, int sum)
{
    sort(a,a+n); // use the STL sort algorithm
    int i = 0;
    int j = n-1;
    while(i < j)
    {
        if(a[i]+a[j] == sum)
        {
            cout<<a[i]<<" "<<a[j]<<endl;
            return;
        }
        else if(a[i]+a[j] < sum)
            ++ i;
        else
            -- j;
    }
    cout<<"No answer!"<<endl;
}
复制代码

类似的一道题目:求数组中差为给定数字的两个数,先对数组排序,然后用两个指针i和j指向第一和第二个元素,如果a[j]-a[i]<dif时,++j,增大差值,否则++i,减小差值。

复制代码
void FindTwoNumbers(int *a, int n, int dif)
{
    sort(a,a+n); // use the STL sort algorithm
    int i = 0;
    int j = 1;
    dif = abs(dif);
    while(i < j && j < n)
    {
        if(a[j]-a[i] == dif)
        {
            cout<<a[j]<<" "<<a[i]<<endl;
            return;
        }
        else if(a[j]-a[i] < dif)
            ++ j;
        else
            ++ i;
    }
    cout<<"No answer!"<<endl;
}
复制代码

9、子数组的最大乘积:给定一个长度为N的整数数组,只能用乘法,不能用除法,计算任意N-1个数组合中乘积最大的一组。

分析:如果可以用除法:那么用整个数组的乘积除以每个元素a[i],结果就是除了除数a[i]的剩下N-1个数的乘积。

方法一:不能用除法,数组为a[],s[i]表示数组前i个元素的乘积,s[0]=1(边界条件),s[i]=s[i-1]*a[i-1](1<=i<=N)。t[i]为数组后(N-i)个元素的乘积,t[N+1]=1(边界条件),t[i]=t[i+1]*a[i](1<=i<=N)。则除了第i个元素外,其他N-1个元素的乘积为:p[i]=s[i-1]*t[i+1]。 从头到尾扫描得到s[i],从尾到头扫描得到t[i],进而线性时间就可以得到p[i]。

方法二:利用N个数的正负分布情况。先扫描一遍,统计处数组中正数个数p,负数个数n,零的个数z,绝对值最小的正数a和负数 b。

如果 零的个数 z >= 2 结果为0
如果 零的个数 z = 1 
       如果负数个数n为奇数,结果为0
       如果负数个数n为偶数,结果为除0外的乘积
如果 零的个数 z =0
       如果负数个数n为奇数,结果为去掉绝对值最小负数后的乘积
       如果负数个数n为偶数,结果为去掉绝对值最小正数后的乘积

10、题目:输入一个正整数数组,将它们连接起来排成一个数,输出能排出的所有数字中最小的一个。例如输入数组{32,321},则输出这两个能排成的最小数字32132。请给出解决问题的算法,并证明该算法。

根据题目的要求,两个数字m和n排成的数字mn和nm,如果mn<nm,那么我们应该输出mn,也就是m应该排在n的前面。但是m不一定就小于n,如{32,  321},321>32,但是32132<32321。 这道题其实是希望我们能找到一个排序规则,根据这个规则排出来的数组能排成一个最小的数字。要确定排序规则,就得比较两个数字,也就是给出两个数字m和n,我们需要确定一个规则来比较m和n组合的数哪个更小。我们知道m和n组合出来的数位数是一定的,如果直接通过组合出来的数的数值去比较,有点麻烦,需要考虑到的一个潜在问题是m和n都在int能表达的范围内,但把它们拼起来的数字mn和nm就不一定能用int表示了。所以一个直观的方法就是把数字转换成字符串,把组合出来的数转化字符串后,直接比较字符串的大小就可以,通过这个来确定m和n的组合顺序。

来自:http://zhedahht.blog.163.com/blog/static/25411174200952174133707/

复制代码
#include<iostream>
using namespace std;

// 数字最大位数为10
const int g_MaxNumberLength = 10;

// 比较 strNumber1 和 strNumber2
// if [strNumber1][strNumber2] > [strNumber2][strNumber1], return value > 0
// if [strNumber1][strNumber2] = [strNumber2][strNumber1], return value = 0
// if [strNumber1][strNumber2] < [strNumber2][strNumber1], return value < 0
int compare(const void* strNumber1, const void* strNumber2)
{
    char g_StrCombine1[2*g_MaxNumberLength+1];
    char g_StrCombine2[2*g_MaxNumberLength+1];

    // [strNumber1][strNumber2]
    strcpy(g_StrCombine1, *(const char**)strNumber1);
    strcat(g_StrCombine1, *(const char**)strNumber2);

    // [strNumber2][strNumber1]
    strcpy(g_StrCombine2, *(const char**)strNumber2);
    strcat(g_StrCombine2, *(const char**)strNumber1);

    return strcmp(g_StrCombine1, g_StrCombine2);
}

// 输出正整数数组组合的最小数
void PrintMinNumber(int* numbers, int length)
{
    if(numbers == NULL || length <= 0)
        return;

    // 将数字转换为字符串
    char** strNumbers = new char*[length];

    for(int i = 0; i < length; ++i)
    {
        strNumbers[i] = new char[g_MaxNumberLength + 1];
        sprintf(strNumbers[i], "%d", numbers[i]);
    }

    // 以 compare() 函数为比较函数,对数字数组进行排序
    qsort(strNumbers, length, sizeof(char*), compare);

    for(int i = 0; i < length; ++i)
        printf("%s", strNumbers[i]);
    printf("\n");

    for(int i = 0; i < length; ++i)
        delete[] strNumbers[i];
    delete[] strNumbers;
}

int main()
{
    int a[] = {5,32,123};
    PrintMinNumber(a,3);
    return 0;
}
复制代码

11、给定如下的n*n的数字矩阵,每行从左到右是严格递增, 每列的数据也是严格递增
1 3 7 15 16
2 5 8 18 19
4 6 9 22 23
10 13 17 24 28
20 21 25 26 33

现在要求设计一个算法, 给定一个数k 判断出k是否在这个矩阵中。 描述算法并且给出时间复杂度。

复制代码
//从右上角开始(从左下角开始也是一样的),然后每步往左或往下走。时间复杂度O(N)。
bool stepWise(int mat[][N] , int key , int &row , int &col)  
{  
    if(key < mat[0][0] || key > mat[N-1][N-1])  
        return false;  
    row = 0;  
    col = N-1;  
    while(row < N && col >= 0)  
    {  
        if(mat[row][col] == key )  //查找成功   
            return true;  
        else if(mat[row][col] < key )  
            ++row;  
        else  
            --col;  
    }  
    return false;  
}
复制代码

12、给定一个整数数组a[],求最接近0的子数组和。

解法:构造从第一个元素开始的子数组和sum[i]=a[0]+..+a[i],那么sum[j]-sum[i]就是a[i+1]+...+a[j],即为一个子数组的和,原数组最接近0的子数组和就是sum[]中两数之差绝对值最小的值,两两比较,得到差值的最小值,就是最接近0的子数组和。

复制代码
int compare(const void* key1, const void* key2)
{
    return *(int*)(key1) - *(int*)(key2);
}

int SubArraySum(const int a[], int len)
{
    int* pCumuSum = new int[len];

    pCumuSum[0] = a[0];
    for(int i = 1; i < len; ++i)
    {
        pCumuSum[i] = pCumuSum[i-1] + a[i];
    }

    qsort(pCumuSum, len, sizeof(int), compare);

    int absDiff = abs(pCumuSum[0]);
    int tempAbsDiff;
    for(int i = 1; i < len; ++i)
    {
        tempAbsDiff = abs(pCumuSum[i] - pCumuSum[i-1]);
        if(tempAbsDiff < absDiff)
        {
            absDiff = tempAbsDiff;
        }
    }

    delete [] pCumuSum;
    return absDiff;
}
复制代码

13、小飞电梯调度问题:电梯从1层往上走,只允许电梯停在某一层,所有的乘客都从一楼上电梯,到达停下来的那层,所有的乘客从这里爬到自己的目的层。电梯停在哪一层,使得所有乘客爬的楼梯层数之和最少?

方法一:穷举所有的层,计算乘客爬的楼梯层数,得到最优的那一层。

方法二:假设停在第i层,所有爬的楼梯层数为Y。如果N1个乘客目的层在第i层楼以下,有N2个乘客在第i层楼,N3个乘客目的层在第i层楼以上。如果电梯改停在第i+1层,则爬层楼数为Y+(N1+N2-N3)。如果电梯改停在第i-1层,则爬层楼数为Y+(N2+N3-N1)。由此得出:当N1+N2<N3时,电梯改停在第i+1层好,当N2+N3<N1时,电梯改停在第i-1层好。

复制代码
int getTargetFloor1(int N, int person[])
{
    int targetFloor = -1;
    int minFloor = 65535;
    int nFloor;

    for(int i = 1; i <= N; ++i)
    {
        nFloor = 0;
        for(int j = 1; j < i; ++j)
            nFloor += person[j]*(i-j);
        for(int j = i+1; j <= N; ++j)
            nFloor += person[j]*(j-i);
        if(targetFloor == -1 || nFloor < minFloor)
        {
            targetFloor = i;
            minFloor = nFloor;
        }
    }

    return targetFloor;
}

int getTargetFloor2(int N, int person[])
{
    int targetFloor = -1;
    int minFloor;
    int N1, N2, N3, i;

    targetFloor = 1;
    minFloor = 0;
    for(N1 = 0, N2 = person[1], N3 = 0, i = 2; i <= N; ++i)
    {
        N3 += person[i];
        minFloor += person[i]*(i-1);
    }
    for(i = 2; i <= N; ++i)
    {
        if(N1 + N2 < N3)
        {
            targetFloor = i;
            minFloor += (N1+N2-N3);
            N1 += N2;
            N2 = person[i];
            N3 -= person[i];
        }
        else break;
    }

    return targetFloor;
}
posted @ 2012-12-06 00:36  曙光_用代码记录人生  阅读(201)  评论(0编辑  收藏  举报