一、概要
本文介紹了有關陣列的演算法第二部分的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
,以及起止位置maxbegin
和maxend
。
程式碼實現
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]
的值,等於原輸入陣列p
以p[0][0]
為左上角到p[i][j]
為右下角構成的子矩陣的所有元素之和,通過該輔助陣列就能在O(1)
的時間內計算出lRow
到hRow
行之間第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之間的逆序對
複製程式碼
這裡有一個關鍵的點,就是需要保證在計算A
與B
之間的逆序對時,A
和B
內的元素都是有序的。
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 知識梳理系列:
- Android 知識梳理目錄:www.jianshu.com/p/fd82d1899…
- Android 面試文件分享:www.jianshu.com/p/8456fe6b2…