數組常見算法題


 數組常見算法題

 

 

連續子數組的最大和

問題描述:

輸入一個整型數組,數組中連續的一個或多個整數組成一個子數組,每個子數組都有一個和,求所有子數組和的最大值。
例如輸入的數組為1,-2,3,10,-4,7,2,-5,和最大的子數組為3,10,-4,7,2,因此輸出為該子數組的和18。

int max_sub(int a[], int size) 
{ 
    int i;
    int max=0;
    int temp_sum=0; 

    for (i=0; i<size; i++) {

        temp_sum += a[i]; 

        if (temp_sum>max) {   
            max = temp_sum; 

        }  else if (temp_sum<0) {
            temp_sum = 0;  
        }   
    }   

    // if all data are negative
    if (max == 0) {
        max = a[0];

        for (i=1; i<size; i++) {
            if (a[i] > max) max = a[i]; 
        }   
    }   

    return max; 
}  

 

 


 

數對之差的最大值

問題描述:
在數組中,數字減去它右邊的數字得到一個數對之差,求所有數對之差的最大值。
例如在數組{2,4,1,16,7,5,11,9}中,數對之差的最大值是11,是16-5的結果。

這個問題用暴力法破解的話,時間復雜度是O(n^2),我們可以換下思路。

構建一個輔助數組diff,並且diff[i]=number[i]-number[i+1] (0<=i<N-1),那么 diff[i]+diff[i+1]+...+diff[j]=numbers[i]-number[j+1],

可見最大的數對之差numbers[i]-number[j+1],同時也是 diff[i]+diff[i+1]+...+diff[j]的最大值,也就是連續子數組的最大和,這就回到了上面那個問題了。

C代碼實現如下:

#define N 8
int main()
{        
    int array[N] = {2,4,1,16,7,5,11,9};
    int diff[N-1];

    int j = 0;
    for (; j<N-1; j++) diff[j] = array[j] - array[j+1];

    j = max_sub(diff, N-1);

    printf("the max sub is: %d\n", j);  // 11

    return 0;
}

 

上面這種方法比較巧妙,一般不容易想到,我們再用一種比較常規的解法。

構建數組 diff[i] = max(number[h] - number[i]),(0<=h<i),現在問題就變成了求數組diff[i] (0<=i<N-1)的最大值。

假設我們已經知道了diff[i],那么該怎么求diff[i+1]呢?對於diff[i],肯定存在一個h(h<i),滿足number[h]-number[i]最大,也就是number[h]應該是number[i]之前的所有數字的最大值。當我們求diff[i+1]的時候,需要找到第i+1個數字之前的最大值,這個值只有兩種可能,一個是number[h],另一個是number[i]。

C代碼實現如下:

#define N 8
#define MAX(a,b) (a>b?a:b)
int main()
{        
    int i;
    int array[N] = {2,4,1,16,7,5,11,9};

    int diff;
    int last_max = array[0];
    int max_diff = array[0] - array[1];

    for (i=2; i<N; i++) {
    
        last_max = MAX(last_max, array[i-1]);

        diff = last_max - array[i];

        if (diff > max_diff) 
            max_diff = diff;
    }
    

    printf("the max sub is: %d\n", max_diff);  // 11

    return 0;
}

 


把數組排成最小的數

問題描述:

輸入一個正整數數組,將它們連接起來排成一個數,輸出能排出的所有數字中最小的一個。
例如輸入數組{32,321},則輸出這兩個能排成的最小數字32132.

思路:

由於數組中的所有數字都是正整數,我們只要將它們的所有連接組合比較一下,找到最大值即可,但要考慮一個問題,就是很多數字連接起來可能會超出int表示范圍,因此更好的做法就是將數字數組先轉成字符串數組,然后對字符串數組排序(按字面值),再從頭至尾依次連接所有字符串元素即可。

 

#define MAX_LEN 30
int str_comp(const void *a, const void *b)
{
    static char composite1[2*MAX_LEN+1];
    static char composite2[2*MAX_LEN+1];

    //memset(composite1, 0, 2*MAX_LEN+1);
    //memset(composite2, 0, 2*MAX_LEN+1);

    strcpy(composite1, (const char*)a);
    strcat(composite1, (const char*)b);

    strcpy(composite2, (const char*)b);
    strcat(composite2, (const char*)a);

    return strcmp(composite1, composite2);

}

void GetMinNumber(int a[], int N)
{
    int n;
    char strArray[N][MAX_LEN+1];


    for (n=0; n<N; n++) {
        sprintf(strArray[n], "%d", a[n]);   
    }

    qsort(strArray, N, sizeof(strArray[0]), str_comp);

    printf("min num = ");

    for (n=0; n<N; n++) {
        printf("%s", strArray[n]); 
    }

    printf("\n");
}

int main()
{
    int array[2] = {32,321};
    GetMinNumber(array, 2);

    return 0;
}

 


有序數組中和為給定值的兩個數字

問題描述:

輸入一個已經按升序排序過的數組和一個數字,在數組中查找兩個數,使得它們的和正好是輸入的那個數字。要求時間復雜度是O(n)。如果有多對數字的和等於輸入的數字,輸出任意一對即可。例如輸入數組1、2、4、7、11、15和數字15。由於4+11=15,因此輸出4和11。

思路:

由於數組已經有序,我們不妨先從數組的兩端開始計算,用small指向第一個元素、用big指向最后一個元素,如果small+big恰好就是給定值m,那就找到了;如果(small+big)<m,就把small往后移動一次,如果(small+big)>m,就把big往后移動一次。

 

void FindTwoNum(int array[], int N, int m)
{
    int *small = array;
    int *big = array + N - 1;

    while (small < big) {

        if (*small + *big == m) {
            printf("small=%d\tbig=%d\n", *small, *big);
            break;

        } else if (*small + *big < m) {
            small++;

        } else {
            big--;
        }   
    }   
}


int main()
{
    int a[6] = {1,2,4,7,11,15};
    FindTwoNum(a, 6, 15);
    return 0;
}

 

問題擴展:

1、輸入一個數組,判斷這個數組中是不是存在三個數字i, j, k,滿足i+j+k等於0。

2、如果輸入的數組是沒有排序的,但知道里面數字的范圍,其他條件不變,如何在O(n)時間里找到這兩個數字?

 

 


撲克牌的順子

問題描述:

從撲克牌中隨機抽5張牌,判斷是不是一個順子,即這5張牌是不是連續的。2-10為數字本身,A為1,J為11,Q為12,K為13,而大小王可以看成任意數字。

思路:

我們不妨把大小王都當成0,這樣和其他撲克牌代表的數字就不重復了,也就是任意一張撲克牌的數字位於0-13之間。接下來我們來分析怎樣判斷5個數字是不是連續的:首先可以把數組排序。但值得注意的是,由於0可以當成任意數字,我們可以用0去補滿數組中的空缺。也就是排序之后的數組不是連續的,即相鄰的兩個數字相隔若干個數字,但如果我們有足夠的0可以補滿這兩個數字的空缺,這個數組實際上還是連續的。舉個例子,數組排序之后為{0,1,3,4,5}。在1和3之間空缺了一個2,剛好我們有一個0,也就是我們可以它當成2去填補這個空缺。

於是我們需要做三件事情:把數組排序,統計數組中0的個數,統計排序之后的數組相鄰數字之間的空缺總數。如果空缺的總數小於或者等於0的個數,那么這個數組就是連續的;反之則不連續。最后,我們還需要注意的是,如果數組中的非0數字重復出現,則該數組不是連續的。換成撲克牌的描述方式,就是如果一副牌里含有對子,則不可能是順子。

 

C++代碼實現如下:

bool IsContinuous(vector<int> numbers, int maxNumber)
{
    if(numbers.size() == 0 || maxNumber <=0)
        return false;

    // Sort the array numbers.
    sort(numbers.begin(), numbers.end());

    int numberOfZero = 0;
    int numberOfGap = 0;

    // how many 0s in the array?
    vector<int>::iterator smallerNumber = numbers.begin();

    for (; smallerNumber != numbers.end(); ++smallerNumber) {

        if (*smallerNumber == 0) {
            numberOfZero++;

        } else {
            break;
        }   
    }   

    // get the total gaps between all adjacent two numbers
    vector<int>::iterator biggerNumber = smallerNumber + 1;

    while (biggerNumber < numbers.end()) {

        // if any non-zero number appears more than once in the array,
        // the array can't be continuous
        if (*biggerNumber == *smallerNumber)
            return false;

        numberOfGap += *biggerNumber - *smallerNumber - 1;
        smallerNumber = biggerNumber;
        ++biggerNumber;
    }   

    return (numberOfGap > numberOfZero) ? false : true;
}

int main()
{
    vector<int> cards;
    cards.push_back(3);
    cards.push_back(0);
    cards.push_back(4);
    cards.push_back(1);
    cards.push_back(5);

    cout<< "result="<< IsContinuous(cards, 13)<<endl;
}

 

 

 

 


 

子數組換位問題

問題描述:

設a[0:n-1]是一個有n個元素的數組,k(0<=k<=n-1)是一個非負整數。 試設計一個算法將子數組a[0:k]與a[k+1,n-1]換位。

PS:要求算法在最壞情況下耗時O(n),且只用到O(1)的輔助空間。

 

例如,數組 {a0, a1, a2, a3, a4, a5, a6, a7, a8, a9},

1、若k=4(兩個子數組等長),即需要將數組變成:{a5, a6, a7, a8, a9a0, a1, a2, a3, a4},兩個子數組的長度一樣,直接兩兩交換a[i]與a[i+k]即可;

2、若k=1(后面的子數組更長),即需要將數組變成:{a2, a3, a4, a5, a6, a7, a8, a9, a0, a1},可以先把第一個子數組交換到整個數組的最后,得到:

{a8, a9, a2, a3, a4, a5, a6, a7,  a0, a1},然后對前面的子數組再交換一次,得到:

{a2, a3, a4, a5, a6, a7, a8, a9,  a0, a1}

3、若k=6(前面的子數組更長),即需要將數組變成:{a8, a9, a0, a1, a2, a3, a4, a5, a6, a7},可以先把第二個子數組交換到整個數組的前面,得到:

{a8, a9, a2, a3, a4, a5, a6, a7, a0, a1},然后問題就變成了怎么把{a2, a3, a4, a5, a6, a7}與{a0, a1}交換了,遞歸處理即可。

 

//交換數組的兩段大小相等的范圍的對應數據
//a[low1] <->a[low2]  a[low1+1]<->a[low2+1]  ... a[high1] <-> a[high2]
void swap(int a[],int low1,int high1,int low2,int high2)
{
    int temp;

    while (low1 <= high1) {

        temp = a[low1];
        a[low1] = a[low2];
        a[low2] = temp;

        low1++;
        low2++;
    }   
}

//利用分治算法, 每次選擇最小的數組進行換位
void patition(int a[], int low, int k, int high)
{
    if (low < high) {

        if ((k-low+1) == (high-k)) {
            swap(a,low,k,k+1,high);

        } else if ((k-low+1)<(high-k)){
            swap(a,low,k,low+high-k,high);
            patition(a,low,k,low+high-k-1);

        } else {
            swap(a,low,high+low-k-1,k+1,high);
            patition(a,high+low-k,k,high);
        }   
    }   

}

//測試
int main()
{
    int i;

    int a[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13};

    patition(a, 0, 4, 13);

    for (i=0; i<14; i++) {
        printf("%d  ",a[i]);
    }
    printf("\n");
    return 0;
}

 


 

求數組的主元素

問題描述:
大小為N的數組A,其主元素是一個出現超過N/2次的元素(從而這樣的元素最多有一個),怎么用線性時間算法得到一個數組的主元素(如果有的話)。

 

算法思想:

先將數組排序,如果數組的主元素存在,那么主元素一定位於數組中間位置,也就是說主元素一定是數組的中位數。那么問題就變成了怎么求中位數?

借鑒快排算法的思想,可以使用一個支點元素,將小於它的元素放在一邊,大於它的元素放在另一邊,然后看支點所在的位置k:

若k<N/2,那么中位數位於支點右邊,遞歸處理右邊的元素;

若k>N/2,那么中位數位於支點左邊,遞歸處理左邊的元素;

若k=N/2,則支點即中位數;

以上,根據快速排序的思想,可以在平均時間復雜度為O(lgn)的時間內找出一個數組的中位數,然后再用O(n)的時間檢查它是否是主元素。

 

#include <stdlib.h>
#include <stdio.h>

void swap(int *a, int *b) 
{
    if (a != b) {
        *a ^= *b;    
        *b ^= *a;    
        *a ^= *b;    
    }   
}

int Partition(int *array, int s, int e)
{
    int n;
    int m = s;
    int pivot = array[e];

    for (n=s; n<e; n++) {
        if (array[n] <= pivot) {
            swap(&array[n], &array[m++]);
        }   
    }   

    swap(&array[e], &array[m]);
    return m;
}


int GetMiddleElement(int *array, int s, int e, int N)
{
    int q;

    q = Partition(array, s, e); 

    if (q == N/2) {
        return array[q]; 

    } else if (q > N/2) {
        GetMiddleElement(array, s, q-1, N); 

    } else {
        GetMiddleElement(array, q+1, e, N); 
    }   
}

void GetMainElement(int *a, int N)
{
    int n, count=0;
    int m = GetMiddleElement(a, 0, N-1, N); 

    for (n=0; n<N; n++) {
        if (a[n] == m) count++;
    }

    if (count > N/2) {
        printf("There is main element: %d\n", m);

    } else {
        printf("There is no main element");
    }
}


int main()
{
    int a[15] = {4,4,3,3,4,11,9,4,4,2,4,5,4,9,4 };

    GetMainElement(a, 15);

}

 

另外一種思路:

若數組中主元素存在,且其個數為m,則有 m > N/2,兩邊同時減1,既有 m-1 > (N-2)/2。

也就是說在數組中隨機找出兩個元素e1、e2,如果e1 != e2,並且其中一個是主元素,則刪除這兩個元素,主元素在剩下的數組中仍是主元素。注意:如果e1、e2沒有主元素,那么可能導致某個不是主元素的元素在剩下的數組中變成主元素。

int main()
{ 
    int i;
    int pArr[6] = {4,4,4,4,6,6};  
    int arrLength = 6;  //數組長度  
    int element = pArr[0];  
    int value = 1;  //記錄剪裁過程中遇到相同元素的個數  
    int delNum = 0; //記錄裁剪數組的元素個數  

    int *dArr = (int *)malloc(arrLength * sizeof(int)); //記錄被剪裁的數組元素  

    int dTop = 0; //當前剪裁數組的索引位置  

    for (i=1; i<arrLength; i++) {    
    
        if (value == 0) {
            element = pArr[i];  
        }    

        if (pArr[i] == element) { //如果當前數組相鄰的元素相等    
            value++;    

        } else if (value > 0) {   //如果當前數組相鄰的元素不等,則需要裁剪得到新的數組    
            dArr[dTop++] = element;
            dArr[dTop++] = pArr[i];
            delNum += 2;  
            value--;    
        }    
    }    

    //如果裁剪之后出現了主元素,那么這個主元素有可能是個偽主元素  
    if (value > (arrLength - delNum)/2) { 

        for (i=0; i<delNum; i++)
            if(element == dArr[i]) value++;           

        if(value > arrLength/2)
            printf("主元素為:%d\n",element);    
    }    

    return 0;
}

 

 


 

連續數打亂判斷少了哪個數

問題描述:

N個連續的數(比如0~999)打亂 之后,隨機取出1個數 ,問如何最快速的判斷出少了哪一個?

算法描述:

由於數組順序被打亂,最可行的辦法就是建立一個bitmap,然后掃描一遍數組,並在bitmap中相應位置1,比如數組元素7就在bitmap第7位置1。最后再掃描一遍bitmap就知道缺失哪個數了,這樣做的時間復雜度是O(2n)。

也可以用異或運算來分析下從0到N的異或結果,並將該結果與數組進行異或:

 

int main()
{    
    int n;
    int m = 34;
    int N = 999;
    int XOR = 0;

    for (n=0; n<N; n++) {
        XOR ^= n; 
    }
    
    for (n=0; n<N; n++) {
        if (n != m)
            XOR ^= n;
    }

    printf("missing=%d\n", XOR);

    return 0;
}

輸出結果正是34,這是因為一個數和自身的異或結果是0,我們將0~(N-1)全部異或,再與數組所有元素異或,那么除了缺失的那個數字以外,其他所有元素都出現2次,因此這些數字最終都歸為0。

異或算法的思路有點類似於將0~(N-1)相加,然后再減去數組所有元素之和,就剛好得到了缺失的那個數!

 

這個問題還可以進行一些拓展,比如:

1、一個連續數組,所有元素位於0~(N-1)之間,但有一個元素出現了2次,怎么快速找到多出來的這個數字?同樣使用異或就可以搞定它!

2、一個連續數組,一個元素缺失,另一個元素重復了,怎么快速找到這兩個數字?

3、在一個整型數組中,除了1個數字之外,其他的數字都出現了兩次,怎么找到這1個只出現一次的數字?

4、在一個整型數組中,除了2個數字之外,其他的數字都出現了兩次,怎么找到這2個只出現一次的數字?

5、在一個整型數組中,除了3個數字之外,其他的數字都出現了兩次,怎么找到這3個只出現一次的數字?

 

以上面的第4個問題為例,假設數組中只出現一次的兩個數字分別為a、b,且a != b,那么對數組所有元素進行異或,最后的結果x=a^b。由於a != b,故x != 0,其二進制表示中至少就有一位1,我們在x中找到第一個為1的位的位置,記為N位。現在以第N位是否為1將原數組中的數字分成兩個子數組,那么a和b就分別位於這兩個子數組中,然后分別對兩個子數組進行異或,就能算到a、b的值了。代碼如下:

#define N 10
int main()
{    
    int n, x, a, b, f;  
    a = b = x = n = f = 0;

    int array[N] = {1,2,3,10,4,7,1,10,2,4};

    for (n=0; n<N; n++) {
        x ^= array[n];  // x=a^b
    }   

    for (n=0; ;n++) {

        f = x & (0x1<<n);

        if (f) break;
    }   

    for (n=0; n<N; n++) {
        if (array[n] & f) {
            a ^= array[n];

        } else {
            b ^= array[n];
        }   
    }   

    printf("the two numbers only appear once time: %d, %d\n", a, b); 

    return 0;
}

 

再來看上面的第5題,有3個數字(設為a、b、c)僅出現一次,其它數字均出現了兩次。如果我們能找到其中一個只出現一次的數字,那剩下2個數字就可以用問題4的解法來求了。

同樣,對數組中所有數字進行異或,最后的結果x=a^b^c,由於a、b、c三個數字各不相等,那么x與a、b、c也都不想等。這點可以反證,如果x和a、b、c其中一個相等,比如x=a,那么a=a^b^c,兩邊同時異或a,那么有a^a=a^a^b^c,即b^c=0,與b!=c矛盾,可見x不會與a、b、c任意一個數字相等,也就是說x^a、x^b、x^c都不會等於0。

我們設置一個函數f(n),可以只保留參數n的二進制中的最后一位1,比如f(6)=2、f(16)=16。接下來考慮f(x^a)^f(x^b)^ f(x^c)的結果,這個值肯定非0,假設最后一位是1的位是第m位,那么x^a、x^b、x^c的結果中,其中有且只有一個數字的第m位是1,我們可以把這個數字找到。然后剩下2個數字就可以用第4題的解法來求了。

 

 


 

一個數組先遞增后遞減,要求找到最大值

使用二分法:

int get_max(int a[], int N)
{
    if (N <= 0) {
        return -1; 
    } else if (N == 1) {
        return a[0];
    } else if (N == 2) {
        return a[0] > a[1] ? a[0] : a[1];
    }   

    int low = 0;
    int high = N-1;
    int mid = low + (high-low)/2;

    while (mid>0 && mid<N-1) {

        mid = low + (high-low)/2;

        if (a[mid] > a[mid+1] && a[mid] > a[mid-1]) {
            return a[mid];

        } else if (a[mid] < a[mid+1]) {
            low = mid + 1;                

        } else {
            high = mid - 1;    
        }   
    }   
}


int main()
{
    int a[3] = {-3, 1, 10};
    //int a[13] = {-3, -1, 0, 1, 5, 8, 11, 30, 29, 28, 27, -4, -100};

    printf("max=%d\n", get_max(a, 3));
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM