归并排序
应用1:统计逆序对个数
采用经典的基于分治策略的归并排序来实现。
- 设 m i d = n u m s . l e n g t h 2 mid = \frac{nums.length}{2} mid=2nums.length
- 在归并排序的过程中统计逆序对的个数
- 设序列nums[0:mid]和nums[mid+1:]两者都按升序排好了
- 则原问题逆序对的个数为:nums[0:mid]中逆序对的个数(子问题1),nums[mid+1:]中逆序对的个数(子问题2),以及两个序列之间的逆序对个数的总和。
- 如何计算之间的逆序对个数?按归并排序的思路!设l初始时指向nums[0:mid]的第一个元素,r初始时指向nums[mid+1:]的第一个元素
- 如果nums[l] > nums[r],则由于nums[0:mid]已经升序排好了,nums[0:mid]中从l开始的所有元素都与nums[r]构成一个逆序对
- 否则nums[l]不会与nums[r]及其后面的任何一个元素构成逆序对(因为nums[mid+1:]也已经按升序排好了),可以不予考虑。
class Solution {
int ans = 0;
public void mergesort(int l,int r,int[] nums)
{
if(l>=r)
return;
int mid = (l + r)/2;
mergesort(l,mid,nums);
mergesort(mid+1,r,nums);
int[] left = new int[mid - l + 1];
System.arraycopy(nums,l,left,0,left.length);
int p2 = mid+1; //指向right
mid = 0; //指向left
int i = l; //计数变量
while(mid<left.length && p2 <= r)
{
if(left[mid] > nums[p2])
{
ans+=(left.length - mid);
nums[i++] = nums[p2++];
}
else
{
nums[i++] = left[mid++];
}
}
while(mid<left.length)
nums[i++] = left[mid++];
while(p2 <= r)
nums[i++] = nums[p2++];
}
public int reversePairs(int[] nums) {
mergesort(0,nums.length - 1,nums);
return ans;
}
public static void main(String[] args)
{
int[] nums = {7,5,6,4};
new Solution().reversePairs(nums);
}
}
优化后的代码:不每次新生成一个left数组
- 注意left长度最多为nums长度的一般再加1
class Solution {
int ans = 0;
public void mergesort(int l,int r,int[] nums,int[] left)
{
if(l>=r)
return;
int mid = (l + r)/2;
mergesort(l,mid,nums,left);
mergesort(mid+1,r,nums,left);
System.arraycopy(nums,l,left,0,mid - l + 1);
int p2 = mid+1; //指向right
int length = mid - l + 1;
mid = 0; //指向left
int i = l; //计数变量
while(mid<length && p2 <= r)
{
if(left[mid] > nums[p2])
{
ans+=(length - mid);
nums[i++] = nums[p2++];
}
else
{
nums[i++] = left[mid++];
}
}
while(mid<length)
nums[i++] = left[mid++];
while(p2 <= r)
nums[i++] = nums[p2++];
}
public int reversePairs(int[] nums) {
int[] left = new int[nums.length / 2 + 1];
mergesort(0,nums.length - 1,nums,left);
return ans;
}
}
应用2:统计数组中每个元素右侧小于其自身的元素个数
- 注意precount的使用
- 合并时nums[0:mid]处元素已经按升序排好,所以nums[0:mid]中(nums[0:mid]在前面的元素后面小于其自身的元素)一定是(nums[0:mid]在后面的元素小于其自身的元素 )
- 第一次做时总是超时:
- 原因1时没有使用precount充分利用前后元素的关系
- 原因2是不需要每次都copy所有数组cursor的值到cursor_pre中
- 只需要copy从l区间到r区间的即可
class Solution {
public void mergesort(int[] count,int l,int r,int[] nums,int[] left,int[] cursor,int[] cursor_pre)
{
if(l>=r)
return;
int mid = (l + r)/2;
mergesort(count,l,mid,nums,left,cursor,cursor_pre);
mergesort(count,mid+1,r,nums,left,cursor,cursor_pre);
System.arraycopy(nums,l,left,0,mid - l + 1);
System.arraycopy(cursor,l,cursor_pre,l,r - l + 1);
int p2 = mid+1; //指向right
int length = mid - l + 1;
mid = 0; //指向left
int i = l; //计数变量
int pre_count = 0;
while(mid<length && p2 <= r)
{
if(left[mid] > nums[p2])
{
pre_count++;
cursor[i] = cursor_pre[p2];
nums[i++] = nums[p2++];
}
else
{
count[cursor_pre[l+mid]]+=pre_count;
cursor[i] = cursor_pre[l + mid];
nums[i++] = left[mid++];
}
}
while(mid<length)
{
count[cursor_pre[l+mid]]+=pre_count;
cursor[i] = cursor_pre[l + mid];
nums[i++] = left[mid++];
}
while(p2 <= r) {
cursor[i] = cursor_pre[p2];
nums[i++] = nums[p2++];
}
}
public List<Integer> countSmaller(int[] nums) {
int[] left = new int[nums.length / 2 + 1];
int[] cursor_pre = new int[nums.length];
int[] cursor = new int[nums.length];
for(int i = 0;i<nums.length;i++)
cursor[i] = i;
int[] count = new int[nums.length];
mergesort(count,0,nums.length - 1,nums,left,cursor,cursor_pre);
List<Integer> ans = new ArrayList<>();
for(int i : count)
{
ans.add(i);
}
return ans;
}
}```