1 归并排序
01 递归实现
由估计递归时间复杂度
public static void mergeSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
process(arr, 0, arr.length - 1);
}
public static void process(int[] arr, int left, int right) {
if (left == right) {
return;
}
int mid = left + ((right - left) >> 1);
process(arr, left, mid);
process(arr, mid + 1, right);
merge(arr, left, mid, right);
}
public static void merge(int[] arr, int left, int mid, int right) {
int[] help = new int[right - left + 1];
int p1 = left;
int p2 = mid + 1;
int i = 0;
while (p1 <= mid && p2 <= right) {
help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
}
while (p1 <= mid) {
help[i++] = arr[p1++];
}
while (p2 <= right) {
help[i++] = arr[p2++];
}
for (int j = 0; j < help.length; j++) {
arr[left + j] = help[j];
}
}
02 迭代实现
public static void mergeSort2(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
int mergeSize = 1;
int length = arr.length;
while (mergeSize < length) {
int left = 0;
while (left < length) {
if (mergeSize > length - left) {
break;
}
int mid = left + mergeSize - 1;
int right = mid + Math.min(mergeSize, length - mid - 1);
merge(arr, left, mid, right);
left = right + 1;
}
if (mergeSize > (left >> 1)) {
break;
}
mergeSize <<= 1;
}
}
public static void merge(int[] arr, int left, int mid, int right) {
int[] help = new int[right - left + 1];
int p1 = left;
int p2 = mid + 1;
int i = 0;
while (p1 <= mid && p2 <= right) {
help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
}
while (p1 <= mid) {
help[i++] = arr[p1++];
}
while (p2 <= right) {
help[i++] = arr[p2++];
}
for (int j = 0; j < help.length; j++) {
arr[left + j] = help[j];
}
}
2 应用归并的几道算法题
题目1:小和问题
在一个数组中,一个数左边比它小的数的总和,叫数的小和,所有数的小和累加起来,叫数组小和。求数组小和。
例子: [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
//在一个数组中,一个数左边比它小的数的总和,叫数的小和,
// 所有数的小和累加起来,叫数组小和。求数组小和
public static int smallSum(int[] arr) {
if (arr == null || arr.length < 1) {
return 0;
}
return process(arr, 0, arr.length - 1);
}
public static int process(int[] arr, int left, int right) {
if (left == right) {
return 0;
}
int mid = left + ((right - left) >> 1);
return process(arr, left, mid) + process(arr, mid + 1, right) + merge(arr, left, mid, right);
}
public static int merge(int[] arr, int left, int mid, int right) {
int[] help = new int[right - left + 1];
int ans = 0;
int p1 = left;
int p2 = mid + 1;
int i = 0;
while (p1 <= mid && p2 <= right) {
ans += arr[p1] < arr[p2] ? (right - p2 + 1) * arr[p1] : 0;
help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
}
while (p1 <= mid) {
help[i++] = arr[p1++];
}
while (p2 <= right) {
help[i++] = arr[p2++];
}
for (int j = 0; j < help.length; j++) {
arr[left + j] = help[j];
}
return ans;
}
题目2:逆序对
在一个数组中,任何一个前面的数a,和任何一个后面的数b,如果(a,b)是降序的,就称为逆序对
返回数组中所有的逆序对数量
//在一个数组中,任何一个前面的数a,和任何一个后面的数b,
//如果(a,b)是降序的,就称为逆序对;返回数组中所有的逆序对个数
public static int reversePair(int[] arr) {
if (arr == null || arr.length < 2) {
return 0;
}
return process(arr, 0, arr.length - 1);
}
public static int process(int[] arr, int left, int right) {
if (left == right) {
return 0;
}
int mid = left + ((right - left) >> 1);
return process(arr, left, mid) + process(arr, mid + 1, right) + merge(arr, left, mid, right);
}
public static int merge(int[] arr, int left, int mid, int right) {
int[] help = new int[right - left + 1];
int p1 = mid;
int p2 = right;
int i = help.length - 1;
int ans = 0;
while (p1 >= left && p2 >= mid + 1) {
ans += arr[p1] > arr[p2] ? p2 - mid : 0;
help[i--] = arr[p1] > arr[p2] ? arr[p1--] : arr[p2--];
}
while (p1 >= left) {
help[i--] = arr[p1--];
}
while (p2 >= mid + 1) {
help[i--] = arr[p2--];
}
for (int j = 0; j < help.length; j++) {
arr[left + j] = help[j];
}
return ans;
}
题目3:在一个数组中,对于每个数num,求有多少个后面的数 * 2 依然<num,求总个数
比如:[3,1,7,0,2]
3的后面有:1,0
1的后面有:0
7的后面有:0,2
0的后面没有
2的后面没有
所以总共有5个
01 算法1
//在一个数组中,对于每个数num,求有多少个后面的数 * 2 依然<num,求总个数
public static int biggerThanRightTwice(int[] arr) {
if (arr == null || arr.length < 2) {
return 0;
}
return process(arr, 0, arr.length - 1);
}
public static int process(int[] arr, int left, int right) {
if (left == right) {
return 0;
}
int mid = left + ((right - left) >> 1);
return process(arr, left, mid) + process(arr, mid + 1, right) + merge(arr, left, mid, right);
}
public static int merge(int[] arr, int left, int mid, int right) {
int[] help = new int[right - left + 1];
int p1 = mid;
int p2 = right;
int i = help.length - 1;
int ans = 0;
while (p1 >= left && p2 >= mid + 1) {
ans += arr[p1] > arr[p2] * 2 ? p2 - mid : 0;
help[i--] = arr[p1] > arr[p2] ? arr[p1--] : arr[p2--];
}
while (p1 >= left) {
help[i--] = arr[p1--];
}
while (p2 >= mid + 1) {
help[i--] = arr[p2--];
}
for (int j = 0; j < help.length; j++) {
arr[left + j] = help[j];
}
return ans;
}
02 算法2
public static int merge2(int[] arr, int left, int mid, int right) {
int ans = 0;
int windR = mid + 1;
for (int i = left; i <= mid; i++) {
while (windR <= right && arr[i] > (arr[windR] * 2)) {
windR++;
}
ans += (windR - mid - 1);
}
int[] help = new int[right - left + 1];
int i = 0;
int p1 = left;
int p2 = mid + 1;
while (p1 <= mid && p2 <= right) {
help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
}
while (p1 <= mid) {
help[i++] = arr[p1++];
}
while (p2 <= right) {
help[i++] = arr[p2++];
}
for (i = 0; i < help.length; i++) {
arr[left + i] = help[i];
}
return ans;
}
题目4:给定一个数组arr,两个整数lower和upper,返回arr中有多少个子数组的累加和在[lower,upper]范围上
leetcoed:https://leetcode.com/problems/count-of-range-sum/
//给定一个数组arr,两个整数lower和upper,返回arr中有多少个子数组的累加和在[lower,upper]范围上
public static int countRangeSum(int[] nums, int lower, int upper) {
if (nums == null || nums.length < 1) {
return 0;
}
//sum数组为arr数组前缀和数组,即sum[i]=arr[0]+arr[1]+...+arr[i]
long[] sum = new long[nums.length];
sum[0] = nums[0];
for (int i = 1; i < sum.length; i++) {
sum[i] = sum[i - 1] + nums[i];
}
return process(sum, 0, sum.length - 1, lower, upper);
}
public static int process(long[] sum, int left, int right, int lower, int upper) {
if (left == right) {
return sum[left] >= lower && sum[left] <= upper ? 1 : 0;
}
int mid = left + ((right-left) >> 1);
return process(sum, left, mid, lower, upper) + process(sum, mid + 1, right, lower, upper) + merge(sum, left, mid, right, lower, upper);
}
public static int merge(long[] sum, int left, int mid, int right, int lower, int upper) {
//求以下标x结尾的满足条件的数组,即时0~x-1的有多少前缀和sum[i]在(sum[x]-upper,sum[x]-lower]区间
//满足条件的范围
int ans = 0;
//[l,r)
int l = left;
int r = left;
for (int i = mid + 1; i <= right; i++) {
long newLower = sum[i] - upper;
long newUpper = sum[i] - lower;
while (r <= mid && sum[r] <= newUpper) {
r++;
}
while (l <= mid && sum[l] < newLower) {
l++;
}
ans += (r - l);
}
int p1 = left;
int p2 = mid + 1;
long[] help = new long[right-left+1];
int i = 0;
while (p1 <= mid && p2 <= right) {
help[i++] = sum[p1] <= sum[p2] ? sum[p1++] : sum[p2++];
}
while (p1 <= mid) {
help[i++] = sum[p1++];
}
while (p2 <= right) {
help[i++] = sum[p2++];
}
for (i = 0; i < help.length; i++) {
sum[left + i] = help[i];
}
return ans;
}