【逻辑有点乱,主题可能不是特别明确。仅作为自己刷题的理解日记。(非常菜的】
由于自己对这个方法的理解不到位【第一次接触时认为是合并两个有序数组,但是今天发现不是有序的数组也可以。一直以为这个方法执行一次,就能将长度大于2的数组排序好】实际上是,通过递归调用这个方法,先将两个元素排序,两组两个的数组合并就能做到合并后的有序。若是合并的两个数组不是有序的,那么合并后得到的数组并不是有序的。
leetcode315题的整体代码:
这一题的解决是在归并排序的代码框架上实现的。
注:在官方给的题解中加入了自己测试的代码
package com.tree_;
import org.junit.Test;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* 给你一个整数数组 nums ,按要求返回一个新数组 counts 。数组 counts 有该性质: counts[i] 的值是 nums[i] 右侧小于 nums[i] 的元素的数量。
*
* 来源:力扣(LeetCode)
* 链接:https://leetcode-cn.com/problems/count-of-smaller-numbers-after-self
* 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
*/
public class CountOfSmallerNumbersAfterSelf315 {
/**
* debug merge方法,看看逻辑是怎么实现的
*/
@Test
public void test(){
int[] nums={5,2,6,1};
int len=nums.length;
// System.out.println(len);
Solution solution = new Solution();
solution.temp=new int[len];
solution.tempIndex=new int[len];
solution.ans=new int[len];
solution.index=new int[len];
for (int i = 0; i < nums.length; ++i) {
solution.index[i] = i;
}
// 输出数组查看是否初始化成功【依旧报错空指针,但是数组是没有问题的呀】
// System.out.println(Arrays.toString(solution.temp));
// index数组忘了初始化
solution.merge(nums,0,len/2,len-1);
System.out.println(Arrays.toString(nums));
}
/**
* 官方的归并方法解析
*/
class Solution {
private int[] index;
private int[] temp;
private int[] tempIndex;
private int[] ans;
public List<Integer> countSmaller(int[] nums) {
this.index = new int[nums.length];
this.temp = new int[nums.length];
this.tempIndex = new int[nums.length];
this.ans = new int[nums.length];
for (int i = 0; i < nums.length; ++i) {
index[i] = i;
}
int l = 0, r = nums.length - 1;
mergeSort(nums, l, r);
List<Integer> list = new ArrayList<Integer>();
for (int num : ans) {
list.add(num);
}
return list;
}
/**
* 对数组进行归并排序 注意,需要加上方法merge才是实现排序的功能
* @param a 需要排序的数组
* @param l
* @param r
*/
public void mergeSort(int[] a, int l, int r) {
if (l >= r) {
return;
}
int mid = (l + r) >> 1;
mergeSort(a, l, mid);
mergeSort(a, mid + 1, r);
merge(a, l, mid, r);
}
/**
* 从一个数组的中间分开,将左右两侧对比排序
* @param a 有序的数组
* @param l
* @param mid
* @param r
*/
public void merge(int[] a, int l, int mid, int r) {
int i = l, j = mid + 1, p = l;
while (i <= mid && j <= r) {
if (a[i] <= a[j]) {
temp[p] = a[i];
tempIndex[p] = index[i];
ans[index[i]] += (j - mid - 1);
++i;
++p;
} else {
// 一个数组的左半段与后半段对比 将较小的数放到temp数组中 并将数组的指针向后移
temp[p] = a[j];
// tempIndex数组存储较小数值的索引,从而使得temp的位置与tempIndex同一位置的关系是:原始数组索引为tempIndex[i]的位置的值是temp[i]
tempIndex[p] = index[j];
++j;
++p;
}
}
while (i <= mid) {
temp[p] = a[i];
tempIndex[p] = index[i];
ans[index[i]] += (j - mid - 1);
++i;
++p;
}
while (j <= r) {
temp[p] = a[j];
tempIndex[p] = index[j];
++j;
++p;
}
for (int k = l; k <= r; ++k) {
index[k] = tempIndex[k];
a[k] = temp[k];
}
}
}
}
这篇博文需要琢磨的代码是merge方法:
public void merge(int[] a, int l, int mid, int r) {
int i = l, j = mid + 1, p = l;
while (i <= mid && j <= r) {
if (a[i] <= a[j]) {
temp[p] = a[i];
tempIndex[p] = index[i];
ans[index[i]] += (j - mid - 1);
++i;
++p;
} else {
// 一个数组的左半段与后半段对比 将较小的数放到temp数组中 并将数组的指针向后移
temp[p] = a[j];
// tempIndex数组存储较小数值的索引,从而使得temp的位置与tempIndex同一位置的关系是:原始数组索引为tempIndex[i]的位置的值是temp[i]
tempIndex[p] = index[j];
++j;
++p;
}
}
while (i <= mid) {
temp[p] = a[i];
tempIndex[p] = index[i];
ans[index[i]] += (j - mid - 1);
++i;
++p;
}
while (j <= r) {
temp[p] = a[j];
tempIndex[p] = index[j];
++j;
++p;
}
for (int k = l; k <= r; ++k) {
index[k] = tempIndex[k];
a[k] = temp[k];
}
}
以下图解更直观
结论:
merge方法是将右侧数组插到左侧数组中,插入的规则如下:
将右侧的元素插入到第一个大于这个元素之前的位置
(或者说,找到右侧元素大于左侧元素的后一个位置,然后插入),
且插入一个就向后移。
如,右侧元素6,在左侧找到第一个它大于的元素5,然后在5后面插入;
再将右侧的下一个元素插入。
如果没有找到,就不动。
把右侧中所有小于左侧的最大值的元素插入左侧。