归并merge思想

小数和

数组中的每个数,它前面比它小的数的所有和,称为数组的小数和。
举例
1,2,3,4,5

1 0
2 1
3 1+2=3
4 1+2+3=6
5 1+2+3+4=10
sum 20

思路转换:求每个数右边大于它的数量,用数量✖️自己的数即可。
举例
1,2,3,4,5

1 1 * 4 = 4
2 2 * 3 = 6
3 3 * 2 = 6
4 4 * 1 = 4
5 5 * 0 = 0
sum 20

使用归并的思想,在merge时,左右子数组比较,一旦出现左小于右时,就开始累加这个小数和。
由于参与merge的两个数组都是有序的,因此可以直接根据下标计算出右数组有几个大于当前左数的。
因此该方法是依赖归并后的有序性的,归并排序的代码一行不能少。
注意merge时如果左右有相等的,要优先排右边的数到temp数组。这样优先把右边相等的排空了才能方便根据下标算出右边有多少个元素大于左边当前元素

  1. public class SmallSum {
  2. private int sum = 0;
  3. public static void main(String[] args) {
  4. SmallSum mergeSort = new SmallSum();
  5. int[] nums = {5,2,7,3,1,9};
  6. mergeSort.sort(nums);
  7. System.out.println();
  8. }
  9. public int[] sort(int[] nums){
  10. if (nums.length < 2){
  11. return nums;
  12. }
  13. sort(nums,0,nums.length-1);
  14. return nums;
  15. }
  16. private void sort(int[] nums, int left, int right){
  17. if (left >= right){
  18. return;
  19. }
  20. int mid = left + ((right - left) >> 1);
  21. sort(nums,left,mid);
  22. sort(nums,mid+1,right);
  23. merge(nums,left,mid,right);
  24. }
  25. private void merge(int[] nums, int left, int mid, int right){
  26. int[] temp = new int[right - left +1];
  27. int leftStart = left, rightStart = mid+1 , index = 0;
  28. while (leftStart <= mid && rightStart <= right){
  29. if (nums[leftStart] < nums[rightStart]){
  30. temp[index++] = nums[leftStart];
  31. // 小数和的代码就这一行,其他都是原汁原味的归并排序
  32. sum = sum + nums[leftStart] * (right - rightStart + 1);
  33. leftStart++;
  34. }else {
  35. temp[index++] = nums[rightStart];
  36. rightStart++;
  37. }
  38. }
  39. while (leftStart <= mid){
  40. temp[index++] = nums[leftStart++];
  41. }
  42. while (rightStart <= right){
  43. temp[index++] = nums[rightStart++];
  44. }
  45. index = 0;
  46. while (left <= right){
  47. nums[left++] = temp[index++];
  48. }
  49. }
  50. }

逆序对

数组中的逆序对
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。

和上面小数和思路一样,小数和是求右边大于它的数量,这个是求右边小于它的数量
下面的代码set的意义在于保留了所有逆序对,可以进行打印,leetcode中的题只求数量要改成count计数,使用set存储会超时。

  1. Set<int[]> sets = new HashSet<>();
  2. public int reversePairs(int[] nums) {
  3. sort(nums,0,nums.length-1);
  4. return sets.size();
  5. }
  6. private void sort(int[] nums, int left, int right){
  7. if (left >= right){
  8. return;
  9. }
  10. int mid = left + ((right - left) >> 1);
  11. sort(nums,left,mid);
  12. sort(nums,mid+1,right);
  13. merge(nums,left,mid,right);
  14. }
  15. private void merge(int[] nums, int left, int mid, int right){
  16. int temp[] = new int[right - left + 1];
  17. int leftIndex = left, rightIndex = mid+1, index = 0;
  18. while (leftIndex <= mid && rightIndex <= right){
  19. // 相等的不做逆序记录
  20. if (nums[leftIndex] <= nums[rightIndex]){
  21. temp[index++] = nums[leftIndex++];
  22. }else {
  23. // 如果左边比右边大,则左边剩余所有的都比当前右边的大
  24. for (int i = leftIndex; i <= mid; i++) {
  25. sets.add(new int[]{nums[i],nums[rightIndex]});
  26. }
  27. temp[index++] = nums[rightIndex++];
  28. }
  29. }
  30. while (leftIndex <= mid){
  31. temp[index++] = nums[leftIndex++];
  32. }
  33. while (rightIndex <= right){
  34. temp[index++] = nums[rightIndex++];
  35. }
  36. index = 0;
  37. while (left <= right){
  38. nums[left++] = temp[index++];
  39. }
  40. }

快排partition思想

荷兰国旗问题

给定数组和target,将数组小于target的放左边,等于target的放中间,大于target的放右边。

  1. /**
  2. * 给定一个target,小于target的放左边,等于target的放中间,大于target的放右边
  3. */
  4. public class Helan {
  5. public static void main(String[] args) {
  6. Helan helan = new Helan();
  7. int[] nums = {4,1,6,2,8,5,0,5,5};
  8. helan.execute(nums,5);
  9. System.out.println();
  10. }
  11. /**
  12. * 维护左右指针,分别代表小于和大于target的区域
  13. * index指针从左到右,小于target就和left的交互,大于就和right交换,等于的直接下一个
  14. * 注意循环条件要覆盖到index = right,因为leftright指向的位置都是下一个要替换的位置
  15. * right指向的位置还没被index访问到
  16. */
  17. public void execute(int[] nums, int target){
  18. int left = 0 , right = nums.length-1;
  19. int index = 0;
  20. while (index <= right){
  21. if (nums[index] < target){
  22. swap(nums,left++,index);
  23. }else if (nums[index] > target){
  24. swap(nums,right--,index);
  25. }
  26. index++;
  27. }
  28. }
  29. private void swap(int[] nums, int i, int j){
  30. if (i == j) {
  31. return;
  32. }
  33. int temp = nums[i];
  34. nums[i] = nums[j];
  35. nums[j] = temp;
  36. }
  37. }

堆排序扩展

一个近似有序的数组,每个元素距离它正确的有序位置距离不超过k,选择一个排序算法对它排序。
使用最小堆实现,我们将前k+1个数放入最小堆中,则此时数组中的最小值肯定是最小堆的堆顶。
因为k的约束,最小值肯定是在数组的前k+1个数中的,以此类推,逐个弹出堆顶,压入后面的元素

  1. /**
  2. * @author jiangwenbin
  3. * @date 2021/12/23
  4. *
  5. * 一个近似有序的数组,每个元素距离它正确的有序位置距离不超过k
  6. * 选择一个排序算法对它排序。
  7. *
  8. * 使用最小堆实现,我们将前k+1个数放入最小堆中,则此时数组中的最小值肯定是最小堆的堆顶。
  9. * 因为k的约束,最小值肯定是在数组的前k+1个数中的,以此类推,逐个弹出堆顶,压入后面的元素
  10. */
  11. public class KDistanceSort {
  12. public static void main(String[] args) {
  13. KDistanceSort kDistanceSort = new KDistanceSort();
  14. int[] nums = {1,3,2,5,4,6};
  15. kDistanceSort.sort(nums,3);
  16. System.out.println();
  17. }
  18. public void sort(int[] nums, int k){
  19. PriorityQueue<Integer> heap = new PriorityQueue<>();
  20. int i = 0;
  21. for (int length = Math.min(nums.length,k+1); i < length; i++) {
  22. heap.add(nums[i]);
  23. }
  24. int index = 0;
  25. for (; i < nums.length; i++) {
  26. nums[index++] = heap.remove();
  27. heap.add(nums[i]);
  28. }
  29. while (!heap.isEmpty()){
  30. nums[index++] = heap.remove();
  31. }
  32. }
  33. }