面試算法知識梳理(5) - 數組第二部分

面試算法代碼知識梳理系列

面試算法知識梳理(1) - 排序算法
面試算法知識梳理(2) - 字符串算法第一部分
面試算法知識梳理(3) - 字符串算法第二部分
面試算法知識梳理(4) - 數組第一部分
面試算法知識梳理(5) - 數組第二部分
面試算法知識梳理(6) - 數組第三部分
面試算法知識梳理(7) - 數組第四部分
面試算法知識梳理(8) - 二分查找算法及其變型
面試算法知識梳理(9) - 鏈表算法第一部分
面試算法知識梳理(10) - 二叉查找樹
面試算法知識梳理(11) - 二叉樹算法第一部分
面試算法知識梳理(12) - 二叉樹算法第二部分
面試算法知識梳理(13) - 二叉樹算法第三部分


一、概要

本文介紹了有關數組的算法第二部分的Java代碼實現,所有代碼均可通過 在線編譯器 直接運行,算法目錄:

  • 找到最小的k個數
  • 連續子數組的最大和
  • 連續子數組的最大和(二維)
  • 求數組當中的逆序對

二、代碼實現

2.1 找到最小的 k 個數

問題描述

即尋找一列數中最小的k個數

解決思路

利用最大堆的特點,加入我們對一個長度為N的數組p的前k個元素進行建堆操作,那么p[0]p[0,..,k-1]的最大值,之后再對p[k,..,N-1]依次遍歷:

  • 如果p[i]小于p[0],那么就說明它屬于p[0,..,i]最小的K個元素之一,而p[0]則一定不屬于p[0,..,N-1]的最小的k個元素,此時將p[i]p[0]交換,重新對[0,..,N-1]的部分進行建堆操作
  • 如果p[i]大于等于p[0],那么就說明p[i]一定不屬于p中最小的k個元素,因此忽略

直到i遍歷到N-1為止,此時p[0,..,k-1]就是數組p最小的K個元素。

代碼實現

class Untitled {
    
    static void maxHeapify(int p[], int di, int length){
        int li = (di<<1)+1;
        int t = p[di];
        while (li < length) {
            if (li+1 < length && p[li+1] > p[li])
                li++;
            if (p[di] >= p[li])
                break;
            p[di] = p[li];
            di = li;
            li = (di<<1)+1;
        }
        p[di] = t;
    }

    static void buildMaxHeap(int p[], int length){
        for(int i=(length>>1)-1; i >= 0; i--){
            maxHeapify(p, i, length);
        }
    }
    
    static void minKthNum(int p[] ,int k, int length) {
        buildMaxHeap(p,k);
        int t;
        for (int i=k; i < length; i++) {
            if (p[i] < p[0]) {
                t = p[0]; p[0] = p[i]; p[i] = t;
                maxHeapify(p, 0, k);
            }
        }
    }
        
    public static void main(String[] args) {
        int p[] = new int[]{2, 3, 10, 2, 5, 6, 7, 20, 1, -5};
        minKthNum(p, 3, p.length);
        for (int i=0; i < 3; i++) {
            System.out.println(String.valueOf(p[i]));
        }
    }
}

運行結果

>> 2
>> 1
>> -5

2.2 連續字數組的最大和

問題描述

數組中的元素有正有負,在該數組中找出一個連續子數組,要求該連續子數組中各元素的和最大,這個連續子數組便被稱作最大連續子數組。比如數組{2,4,-7,5,2,-1,2,-4,3}的最大連續子數組為{5,2,-1,2},最大連續子數組的和為5+2-1+2=8

解決思路

通過對數組中的元素進行線性的遍歷,并對每個元素進行累加,當發現目前為止累加的和maxendinghere小于0時,則說明最大的連續子數組不可能包含目前遍歷到的子數組,那么就從下一個元素tmaxbegin開始計算子數組。

在遍歷的過程中更新目前位置獲得的最大連續子數組的和maxsofar,以及起止位置maxbeginmaxend

代碼實現

class Untitled {

    static void maxSumSubArray(int p[], int length){
        int maxendinghere = 0;
        int maxsofar = 0;
        int maxbegin = 0;
        int maxend = 0;
        int tmaxbegin = 0;
        //從0開始遍歷數組。
        for(int i = 0; i < length; i++){
            //maxendinghere 記錄當前計算子數組的和。
            maxendinghere += p[i];
            //如果該和小于0,那么重新計算。
            if(maxendinghere < 0){
                maxendinghere = 0;
                tmaxbegin = i+1;
            }
            //更新目前為止計算到的最大值。
            if(maxsofar < maxendinghere){
                maxbegin = tmaxbegin;
                maxend = i;
                maxsofar = maxendinghere;
            }
        }
        //考慮數組全部是負數的情況
        if(maxsofar == 0){
            maxsofar = p[0];
            for(int i = 1; i < length; i++){
                if(p[i] > maxsofar){
                    maxsofar = p[i];
                    maxbegin = i;
                    maxend = i;
                }
            }
        }
        for (int i = maxbegin; i <= maxend; i++) {
            System.out.println("i=" + p[i]);
        }
    }

    public static void main(String[] args) {
        int p[] = new int[]{2,4,-7,5,2,-1,2,-4,3};
        maxSumSubArray(p, p.length);
    }
}

運行結果

> i=5
> i=2
> i=-1
> i=2

2.3 連續子數組的最大和(二維)

問題描述

這個問題其實是2.2的變種,這時候輸入是一個二維的矩陣,需要找到一個子矩陣,該子矩陣的和是這個二維的所有子矩陣中最大的。

解決思路

二維的問題和2.2中的一維問題的核心解決思路相同。

對于二維情況,我們將 同一列的多個元素合并成一個元素 來實現降維的效果,為了能實現在O(1)的時間內計算出同一列的多行元素之和,需要構建一個輔助數組,該輔助數組ps[i][j]的值,等于原輸入數組pp[0][0]為左上角到p[i][j]為右下角構成的子矩陣的所有元素之和,通過該輔助數組就能在O(1)的時間內計算出lRowhRow行之間第col列的所有元素之和,計算公式為:

ps[hRow][col] - ps[hRow][col-1] - ps[lRow-1][col] + ps[lRow-1][col-1]

代碼實現

class Untitled {
    
    //計算從lRow到hRow之間,位于第col列的所有元素之和。
    static int getColsum(int ps[][], int lRow, int hRow, int col) {
        return ps[hRow][col] - ps[hRow][col-1] - ps[lRow-1][col] + ps[lRow-1][col-1];
    }

    static void maxSumSubArray2(int p[][], int xlen, int ylen){
        int maxendinghere = 0;
        int maxsofar = 0;
        int tColbegin = 1;
        int sx = 0;
        int sy = 0;
        int ex = 0;
        int ey = 0;
        //初始化輔助數組,ps[i][j]為以其為右下角的子矩陣的所有元素之和。
        int psxLen = xlen+1;
        int psyLen = ylen+1;
        int[][] ps = new int[psxLen][psyLen];
        for (int j = 0; j < psyLen; j++)
            ps[0][j] = 0;
        for (int i = 0; i < psxLen; i++)
            ps[i][0] = 0;
        for (int i = 1; i < psxLen; i++) {
            for(int j = 1; j < psyLen; j++) {
                ps[i][j] = ps[i-1][j] + ps[i][j-1] - ps[i-1][j-1] + p[i-1][j-1];
            }
        }
        //求矩陣中的最大和,將位于同一個列的多行元素合并成一個元素,因此需要遍歷包含不同行的情況。
        for (int pStartRow = 1; pStartRow < psxLen; pStartRow++) { 
            for (int pEndRow = pStartRow; pEndRow < psxLen; pEndRow++) {
                for (int pCol = 1; pCol < psyLen; pCol++) {
                    maxendinghere += getColsum(ps, pStartRow, pEndRow, pCol);
                    if (maxendinghere < 0) {
                        maxendinghere = 0;
                        tColbegin = pCol+1;
                    }
                    if (maxsofar < maxendinghere) {
                        maxsofar = maxendinghere;
                        sx = pStartRow - 1;
                        sy = tColbegin - 1;
                        ex = pEndRow - 1;
                        ey = pCol - 1;
                    }
                }
                maxendinghere = 0;
                tColbegin = 1;
            }
        }
        System.out.println("最大和=" + maxsofar + ",起始行=" + sx + ",終止行=" + ex + ",起始列=" + sy + ",終止列=" + ey);
    }

    public static void main(String[] args) {
        int[][] p = {{1,-10,-11}, {4,5,6}, {7,8,9}};
        maxSumSubArray2(p, 3, 3);
    }
}

運行結果

>> 最大和=39,起始行=1,終止行=2,起始列=0,終止列=2

2.4 求數組中的逆序對

問題描述

在數組中的兩個數字,如果 前面一個數字大于后面的數字,則這兩個數字組成一個 逆序對。輸入一個數組,求出這個數組中的逆序對的總數。

解決思路

這里采用的是 歸并算法 的思想,歸并算法包含三個關鍵步驟:

  • 分解:把長度為n的待排序列分解成兩個長度為n/2的序列。
  • 治理:對每個子序列分別調用歸并排序,進行遞歸操作。當子序列長度為1時,序列本身有序,停止遞歸。
  • 合并:合并每個排序好的子序列。

對于上面的例子,我們將整個數組分解為A、B兩部分,則整個數組的逆序對個數就等于:

A部分組成的數組的逆序對 + B部分組成的數組的逆序對 + A與B之間的逆序對

這里有一個關鍵的點,就是需要保證在計算AB之間的逆序對時,AB內的元素都是有序的。

class Untitled {
    
    static int inversePairs(int p[], int startIndex, int endIndex) {
        if (endIndex == startIndex) {
            return 0;
        }
        if (endIndex-startIndex == 1) {
            if (p[endIndex] < p[startIndex]) {
                int temp = p[startIndex];
                p[startIndex] = p[endIndex];
                p[endIndex] = temp;
                return 1;
            } else {
                return 0;
            }
        }
        int midOffset = (endIndex-startIndex) >> 1;
        int l = inversePairs(p, startIndex, startIndex+midOffset);
        int r = inversePairs(p, startIndex+midOffset+1, endIndex);
        return l + r + inverseCore(p, startIndex, midOffset, endIndex);
    }
    
    static int inverseCore(int p[], int startIndex, int midOffset, int endIndex) {
        int totalLen = endIndex-startIndex+1;
        int lLen = midOffset+1;
        int rLen = totalLen-lLen;
        int l[] = new int[lLen+1];
        int r[] = new int[rLen+1];
        int i = 0;
        for (i=0; i<lLen; i++) {
            l[i] = p[startIndex+i];
        }
        l[i] = 1 << 30;
        for (i=0; i<rLen; i++) {
            r[i] = p[startIndex+lLen+i];
        }
        r[i] = 1 << 30;
        int c = 0;
        i = 0;
        int m = 0;
        int n = 0;
        while(i < totalLen) {
            if (r[n] <= l[m]) {
                p[startIndex+i] = r[n];
                c += (lLen - m);
                n++;
                i++;
            } else {
                p[startIndex+i] = l[m];
                m++;
                i++;
            }
        }
        return c;
    }
    public static void main(String[] args) {
        int[] p = {7,5,6,4};
        System.out.println("Inverse Count=" + inversePairs(p, 0, 3));
    }
}

運行結果

>> Inverse Count=5

更多文章,歡迎訪問我的 Android 知識梳理系列:

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 背景 一年多以前我在知乎上答了有關LeetCode的問題, 分享了一些自己做題目的經驗。 張土汪:刷leetcod...
    土汪閱讀 12,774評論 0 33
  • 1.把二元查找樹轉變成排序的雙向鏈表 題目: 輸入一棵二元查找樹,將該二元查找樹轉換成一個排序的雙向鏈表。 要求不...
    曲終人散Li閱讀 3,371評論 0 19
  • Java經典問題算法大全 /*【程序1】 題目:古典問題:有一對兔子,從出生后第3個月起每個月都生一對兔子,小兔子...
    趙宇_阿特奇閱讀 1,916評論 0 2
  • 數組在程序設計中,為了處理方便, 把具有相同類型的若干變量按有序的形式組織起來。這些按序排列的同類數據元素的集合稱...
    朱森閱讀 4,007評論 2 13
  • 六年前, 我遇見了你, 那時, 就單戀著你。 時間, 無情的流逝, 漸漸淡去的感情, 已不復存在。 單戀這點小事,...
    龍諭閱讀 320評論 9 6