在一個數組中,每一個數左邊比當前數小的數累加起來,叫做這個數組的小和。求一個數組的小和。
例子:
[1,3,4,2,5]
1左邊比1小的數,沒有;
3左邊比3小的數,1;
4左邊比4小的數,1、3;
2左邊比2小的數,1;
5左邊比5小的數,1、3、4、2;
所以小和爲1+1+3+1+1+3+4+2=16
目錄
笨辦法
循環遍歷每個數的左邊的數與當前數進行比較,如果比當前數小,則累加起來,第一次遍歷1次,第二次遍歷2次,第n次遍歷n次,是一個等差數列,但是時間複雜度爲O(N^2)
代碼
public static int smallSum(int[] arr) {
//聲明累加變量
int res=0;
for (int i = 1; i < arr.length; i++) {
//遍歷索引小於i的元素,並進行判斷
for (int j = 0; j < i; j++) {
if(arr[j]<arr[i]){
res+=arr[j];
}
}
}
return res;
}
歸併排序解決
通過歸併排序可以將時間複雜度控制到O(N*logN)
(時間複雜度的詳細計算和歸併詳細圖解:https://blog.csdn.net/u010452388/article/details/81008727)
圖解流程
下圖爲歸併排序的過程,將數據分解後,再進行合併
原理:歸併的每一次合併都是將兩個有序組合併爲一個有序組,合併好後的有序組,再和另外的有序組繼續合併,最終可以得到一個完整的有序數組
累加的原理
這裏主要就是利用合併的過程中,兩個有序組都是有序的進行判斷累加,我們以上圖的數據爲3,5組和數據爲8,9組合並的過程爲例,來計算累加的結果
從上面的圖可以看出,如果p1索引的值小於p2索引的值,那麼這一次排序的過程可以計算右側數組比3大的數有2個(因爲每一組都是有序的),然後索引p1向右移動
從上面的圖可以看出,p1索引的值小於p2索引的值,那麼這一次排序過程可以計算出右邊比5大的數有2個
總結:上面兩個有序組合併爲一個有序組時,累加的小和的值爲: 3*2+5*2=16
代碼
public static int smallSum(int[] arr) {
if (arr == null || arr.length < 2) {
return 0;
}
return mergeSort(arr,0,arr.length-1);
}
public static int mergeSort(int[] arr, int L, int R) {
if (L == R) {
return 0;
}
int mid = (L + R) >>>1;//這裏是防止數據溢出
return mergeSort(arr, L, mid) + mergeSort(arr, mid + 1, R) + merge(arr, L, mid, R);
}
//合併的過程
public static int merge(int[] arr, int L, int mid, int R) {
//準備一個臨時數組,長度和傳進來的arr一樣
int[] temp = new int[R - L + 1];
int p1 = L;
int p2 = mid + 1;
//臨時數組temp的索引起始變量
int i = 0;
//小和結果的變量
int result = 0;
//合併數組的循環,並計算小和
while (p1 <= mid && p2 <= R) {
if (arr[p1] < arr[p2]) {
//計算小和的累加結果,(R-p2+1)爲比數arr[p1]大的數量
result += (R - p2 + 1) * arr[p1];
temp[i] = arr[p1];
p1++;
i++;
} else {
temp[i] = arr[p2];
p2++;
i++;
}
}
while (p1 <= mid) {
temp[i] = arr[p1];
i++;
p1++;
}
while (p2 <= R) {
temp[i] = arr[p2];
i++;
p2++;
}
//這裏是將臨時數組temp的元素重新賦值給傳入進來的arr
for (int j = 0; j < temp.length; j++) {
arr[L + j] = temp[j];
}
return result;
}