• 【算法总结】归并排序专题(刷题有感)


    思考

    一定要注意归并排序的含义,思考归并的意义。
    主要分为两个步骤:

    1. 拆分
      1. 每次对半分(mid = l +r >> 1)
      2. 输入:raw整块,输出:raw左块+ raw右块
    2. 合并
      1. 每次都要对raw左块raw右块按照某种规则进行合并
      2. 输入:raw左块+ raw右块,输出:raw整块

    知道两个步骤之后,可以总结其他的特点:

    1. 拆分阶段和合并阶段是一一对应的,只不过拆分阶段raw的,合并阶段符合一定的性质(对于归并排序则满足有序性)。
    2. 拆分时,段内是无序的,合并时,每一段都是有序的(数值有序性)。合并是针对两个有序的段进行合并,所以会经常用到双指针算法。
    3. 如下图所示,在合并过程中,段内是数值有序,但是相对顺序被破坏了,而两个段之间的相对顺序是不变的6、7、8相对于1、2、3的顺序是不变的,6、7、8依然在1、2、3的左边。

    几道题做下来,感觉归并排序类型题的难点在于

    1. 题意的转化:重点就要题意是否支持将原模型分成两半来考虑,即计算左段相对后段的某种性质。
    2. 合并阶段对结果的计算,比如说求逆序对,那么合并的时候如何求逆序对的个数,双重循环遍历?双指针?等等。。。

    普通模板

    int* merge(int l, int r) {
        if (l > r) return nullptr;
        
        int* tmp = new int[r - l + 1];
        
        if (l == r) {
            tmp[0] = a[l];
            return tmp;
        }
        
        int mid = l + ((r - l) >> 1);
        
        int llen = mid - l + 1, rlen = r - mid;
        int* la = merge(l, mid);
        int* ra = merge(mid + 1, r);
        
        int i = 0, j = 0, cnt = 0;
        for (; i < llen && j < rlen; ) {
            if (la[i] > ra[j]) {
                tmp[cnt ++] = ra[j ++];
            } else {
                tmp[cnt ++] = la[i ++];
            }
        }
        // 上边的循环结束之后,可能存在一个数组还未完全遍历。
        while(i < llen) tmp[cnt ++] = la[i ++];
        while(j < rlen) tmp[cnt ++] = ra[j ++];
        return tmp;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    Acwing 787. 归并排序

    #include
    #include
    #include
    #include
    
    using namespace std;
    
    
    const int N = 100000 + 100;
    int a[N], tmp[N];
    
    void merge(int q[], int l, int r) {
        if (l >= r) return;
        
        int mid = l + ((r - l) >> 1);
    
        merge(q, l, mid);
        merge(q, mid + 1, r);
        
        int i = l, j = mid + 1, cnt = 0;
        for (; i <= mid && j <= r; ) {
            if (q[i] > q[j]) {
                tmp[cnt ++] = q[j ++];
            } else {
                tmp[cnt ++] = q[i ++];
            }
        }
        while(i <= mid) tmp[cnt ++] = q[i ++];
        while(j <= r) tmp[cnt ++] = q[j ++];
        
        for (int i = l, j = 0; i <= r; i ++, j ++)
            q[i] = tmp[j];
    }
    
    int main()
    {
        int n;
        cin >> n;
        for (int i = 0; i < n; i ++) cin >> a[i];
        merge(a, 0, n - 1);
        
        for (int i = 0; i < n; i ++) {
            printf("%d ", a[i]);
        }
        printf("\n");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    Acwing 788. 逆序对的数量

    #include
    #include
    #include
    #include
    
    using namespace std;
    
    typedef long long LL;
    
    const int N = 100100;
    
    int a[N];
    int n;
    LL ans;
    
    void print_arr(int* arr, int size) {
        for (int i = 0; i < size; i ++) {
            printf("%d ", arr[i]);
        }
        printf("\n");
    }
    
    int* merge(int l, int r) {
        if (l > r) return nullptr;
        
        int* tmp = new int[r - l + 1];
        if (r == l) {
            tmp[0] = a[l];
            return tmp;
        }
        int mid = l + r >> 1;
        
        int* larr = merge(l, mid);
        int* rarr = merge(mid + 1, r);
        
        
        int llen = (mid - l) + 1, rlen = (r - mid - 1) + 1;
        // printf("l:\n");
        // print_arr(larr, llen);
        // printf("r:\n");
        // print_arr(rarr, rlen);
        
        int i = 0, j = 0, cnt = 0;
        for (; i < llen && j < rlen;)
        {
            if (larr[i] > rarr[j]) {
                
                ans += (llen - 1 - i) + 1;
                tmp[cnt ++] = rarr[j ++];
            } else {
                tmp[cnt ++] = larr[i ++];
            }
        }
        while(i < llen) tmp[cnt ++] = larr[i ++];
        while(j < rlen) tmp[cnt ++] = rarr[j ++];
        
        // printf("merge\n");
        // print_arr(tmp, (r - l) + 1);
        // printf("ans : %d\n", ans);
        return tmp;
    }
    
    int main()
    {
        cin >> n;
        for (int i = 0; i < n; i ++) cin >> a[i];
        int* h = merge(0, n - 1);
        // for (int i = 0; i < n; i ++) {
        //     printf("%d\n", h[i]);
        // }
        printf("%lld\n", ans);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    Leetcode 493. 翻转对

    class Solution {
        long ans = 0;
        public int reversePairs(int[] nums) {
            int n = nums.length;
            mergeSort(nums, 0, n - 1);
            return (int)ans;
        }
    
        void mergeSort(int[] nums, int l, int r) {
            if (l >= r) return;
            int[] tmp = new int[r - l + 1];
    
            int mid = l + ((r - l) >> 1);
            mergeSort(nums, l, mid);
            mergeSort(nums, mid + 1, r);
    
            int i = l, j = mid + 1, cnt = 0;
            int base = 0;
            for (; i <= mid; i ++) {
                while (j <= r && (long)nums[i] > 2L * nums[j]) {
                    j ++;
                }
                ans += (j - (mid + 1));
            }
            i = l;
            j = mid + 1;
            for (; i <= mid && j <= r; ) {
                if (nums[i] > nums[j]) tmp[cnt ++] = nums[j ++];
                else tmp[cnt ++] = nums[i ++];
            }
            while(i <= mid) tmp[cnt ++] = nums[i ++];
            while(j <= r) tmp[cnt ++] = nums[j ++];
            for (int k = 0; k < cnt; k ++)
                nums[l + k] = tmp[k];
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    Leetcode 315. 计算右侧小于当前元素的个数

    1. 这个题比较恶心的就是要维护元素原来的位置
    class Node {
        int x;
        int id;
        Node(int x, int id) {
            this.x = x;
            this.id = id;
        }
    }
    
    class Solution {
        List<Integer> ans = null;
        public List<Integer> countSmaller(int[] nums) {
            int n = nums.length;
            ans = new ArrayList<>(Collections.nCopies(n, 0));
            Node[] nodes = new Node[n];
            for (int i = 0; i < n; i ++) {
                nodes[i] = new Node(nums[i], i);
            }
            merge(nodes, 0, n - 1);
            return ans;
        }
    
        void merge(Node[] nodes, int l, int r) {
            if (l >= r) return;
    
            Node[] tmp = new Node[r - l + 1];
            int mid = l + ((r - l) >> 1);
            merge(nodes, l, mid);
            merge(nodes, mid + 1, r);
    
            int i = l, j = mid + 1, cnt = 0;
            int base = 0;
            for (; i <= mid;) {
                if (j == r + 1 || nodes[i].x <= nodes[j].x) {
                    ans.set(nodes[i].id, ans.get(nodes[i].id) + base);
                    tmp[cnt ++] = nodes[i ++];
                } else {
                    tmp[cnt ++] = nodes[j ++];
                    base ++;
                }
            }
            while (j <= r) tmp[cnt ++] = nodes[j ++];
    
            for (int k = 0; k < cnt; k ++)
                nodes[l + k] = tmp[k];
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    Leetcode 327. 区间和的个数(前缀和)

    1. 这个题首先要想到利用前缀和将原来的数组进行转换。
    2. 要求的是区间和属于[lower, upper]区间的个数,转化为数学符号之后就是这样: l o w e r < = s u m [ i ] − s u m [ j ] < = u p p e r lower <= sum[i] - sum[j] <= upper lower<=sum[i]sum[j]<=upper
      1. 对于这样的不等式,可以分两步来考虑:
        1. 将连续不等式拆分成单个不等式, s u m [ i ] − s u m [ j ] < = u p p e r sum[i] - sum[j] <= upper sum[i]sum[j]<=upper
        2. 将变量i固定,求另外一个变量的值
      2. 之后对于刚才的连续不等式就可以计算出符合条件的区间[m, n]
    3. 计算符合条件的区间的时机:在合并阶段,i的范围是[mid + 1, r]
    class Solution {
        int lower = 0, upper = 0, ans = 0;
        public int countRangeSum(int[] nums, int lower, int upper) {
            this.upper = upper;
            this.lower = lower;
            int n = nums.length;
    
            long[] pre = new long[n + 1];
            for (int i = 1; i <= n; i ++)
                pre[i] = pre[i - 1] + nums[i - 1];
            merge(pre, 0, n);
            return ans;
        }
    
        void merge(long[] nums, int l, int r) {
            if (l >= r) return;
    
            long[] tmp = new long[r - l + 1];
    
            int mid = l + ((r - l) >> 1);
            merge(nums, l, mid);
            merge(nums, mid + 1, r);
        	// 核心代码
            for (int i = mid + 1, j = l, k = l; i <= r; i ++) {
                while (j <= mid && nums[i] - nums[j] > upper) j ++;
                while (k <= mid && nums[i] - nums[k] >= lower) k ++;
                ans += k - j;
            }
            
            int cnt = 0;
            for (int i = l, j = mid + 1; i <= mid || j <= r; ) {
                if (i == mid + 1) tmp[cnt ++] = nums[j ++];
                else if (j == r + 1) tmp[cnt ++] = nums[i ++];
                else {
                    if (nums[i] > nums[j])
                        tmp[cnt ++] = nums[j ++];
                    else
                        tmp[cnt ++] = nums[i ++];
                }
            }
            
            for (int i = 0; i < cnt; i ++)
                nums[i + l] = tmp[i];
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    Acwing 65. 数组中的逆序对

    1. 题意就在题面上,所以直接套模板。
    class Solution {
        int ans = 0;
        public int inversePairs(int[] nums) {
            int n = nums.length;
            mergeSort(nums, 0, n - 1);
            return ans;
        }
        
        void mergeSort(int[] nums, int l, int r) {
            if (l >= r) return;
            
            int[] tmp = new int[r - l + 1];
            int mid = l + ((r - l) >> 1);
            mergeSort(nums, l, mid);
            mergeSort(nums, mid + 1, r);
            
            int i = l, j = mid + 1, cnt = 0;
            for (; i <= mid && j <= r; ) {
                if (nums[i] > nums[j]) {
                    ans += (mid - i) + 1;
                    tmp[cnt ++] = nums[j ++];
                } else {
                    tmp[cnt ++] = nums[i ++];
                }
            }
            while (i <= mid) tmp[cnt ++] = nums[i ++];
            while (j <= r) tmp[cnt ++] = nums[j ++];
            
            for (int k = 0; k < cnt; k ++)
                nums[l + k] = tmp[k];
        }
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    Acwing 107. 超快速排序

    1. 根据题意可以分析出本题是要求逆序的数量, 那就直接套模板。
    import java.util.Scanner;
    
    class Main {
        static long ans = 0;
        public static void main(String[] args) {
            Scanner sc = new Scanner(System.in);
            int n = 0;
            while((n = sc.nextInt()) != 0) {
                ans = 0;
                int[] nums = new int[n];    
                for (int i = 0; i < n; i ++) {
                    nums[i] = sc.nextInt();
                }
                mergeSort(nums, 0, n - 1);
                System.out.println(ans);
            }
        }
        
        static void mergeSort(int[] nums, int l, int r) {
            if (l >= r) return;
            
            int[] tmp = new int[r - l + 1];
            int mid = l + ((r - l) >> 1);
            mergeSort(nums, l, mid);
            mergeSort(nums, mid + 1, r);
            
            int i = l, j = mid + 1, cnt = 0;
            for (; i <= mid && j <= r; ) {
                if (nums[i] > nums[j]) {
                    ans += (mid - i) + 1;
                    tmp[cnt ++] = nums[j ++];
                } else {
                    tmp[cnt ++] = nums[i ++];
                }
            }
            while (i <= mid) tmp[cnt ++] = nums[i ++];
            while (j <= r) tmp[cnt ++] = nums[j ++];
            
            for (int k = 0; k < cnt; k ++)
                nums[l + k] = tmp[k];
        }
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
  • 相关阅读:
    每 日 练 习
    《二进制方式搭建一个完整K8s集群》v1.20-详细版
    jdk历程
    MIMO信道的随机性
    Java多线程详解
    物联网行业知识概览(一)
    Python接口自动化 —— token登录(详解)
    Vue 登录密码验证 MD5加密
    【Vue2.x源码系列07】监听器watch原理
    “Alibaba Druid 未授权访问” 安全漏洞
  • 原文地址:https://blog.csdn.net/m0_38072683/article/details/134430471