第4节.pptx

1 归并排序

01 递归实现

04 归并排序 mergeSort - 图1估计递归时间复杂度
04 归并排序 mergeSort - 图2

  1. public static void mergeSort(int[] arr) {
  2. if (arr == null || arr.length < 2) {
  3. return;
  4. }
  5. process(arr, 0, arr.length - 1);
  6. }
  7. public static void process(int[] arr, int left, int right) {
  8. if (left == right) {
  9. return;
  10. }
  11. int mid = left + ((right - left) >> 1);
  12. process(arr, left, mid);
  13. process(arr, mid + 1, right);
  14. merge(arr, left, mid, right);
  15. }
  16. public static void merge(int[] arr, int left, int mid, int right) {
  17. int[] help = new int[right - left + 1];
  18. int p1 = left;
  19. int p2 = mid + 1;
  20. int i = 0;
  21. while (p1 <= mid && p2 <= right) {
  22. help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
  23. }
  24. while (p1 <= mid) {
  25. help[i++] = arr[p1++];
  26. }
  27. while (p2 <= right) {
  28. help[i++] = arr[p2++];
  29. }
  30. for (int j = 0; j < help.length; j++) {
  31. arr[left + j] = help[j];
  32. }
  33. }

02 迭代实现

  1. public static void mergeSort2(int[] arr) {
  2. if (arr == null || arr.length < 2) {
  3. return;
  4. }
  5. int mergeSize = 1;
  6. int length = arr.length;
  7. while (mergeSize < length) {
  8. int left = 0;
  9. while (left < length) {
  10. if (mergeSize > length - left) {
  11. break;
  12. }
  13. int mid = left + mergeSize - 1;
  14. int right = mid + Math.min(mergeSize, length - mid - 1);
  15. merge(arr, left, mid, right);
  16. left = right + 1;
  17. }
  18. if (mergeSize > (left >> 1)) {
  19. break;
  20. }
  21. mergeSize <<= 1;
  22. }
  23. }
  24. public static void merge(int[] arr, int left, int mid, int right) {
  25. int[] help = new int[right - left + 1];
  26. int p1 = left;
  27. int p2 = mid + 1;
  28. int i = 0;
  29. while (p1 <= mid && p2 <= right) {
  30. help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
  31. }
  32. while (p1 <= mid) {
  33. help[i++] = arr[p1++];
  34. }
  35. while (p2 <= right) {
  36. help[i++] = arr[p2++];
  37. }
  38. for (int j = 0; j < help.length; j++) {
  39. arr[left + j] = help[j];
  40. }
  41. }

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;
    }