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

news2024/11/14 12:21:39

思考

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

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

Acwing 787. 归并排序

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>

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

Acwing 788. 逆序对的数量

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>

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

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

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

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1212715.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机基础知识53

模板之过滤器 # HTML被直接硬编码在 Python代码之中&#xff0c;Django的 模板系统(Template System) # 过滤器给我们提供的有六十多个&#xff0c;但是我们只需要掌握10个以内即可 过滤器名称就是函数名 # 语法&#xff1a;{{ obj|filter__name:param }} 变量名字…

多个Obj模型合并

MergeObj&#xff08;合并Obj模型&#xff09; 1 概述 由于项目原因&#xff0c;需要下载谷歌地图上的模型&#xff0c;关于谷歌模型下载的&#xff0c;见我的CSDN博客. 由于下载谷歌地图上的数据&#xff0c;会分多个模块下载。下载完成后&#xff0c;怎么合并&#xff0c;在…

电脑检测温度软件有哪些?

环境&#xff1a; Win10 专业版 问题描述&#xff1a; 电脑检测温度软件有哪些&#xff1f; 解决方案&#xff1a; 有很多电脑检测温度的软件可供选择&#xff0c;以下是一些常用的电脑温度监测工具&#xff1a; HWMonitor&#xff1a;一款免费的硬件监控软件&#xff0…

快速生成力扣链表题的链表,实现快速调试

关于力扣链表题需要本地调试创建链表的情况 我们在练习链表题&#xff0c;力扣官方需要会员&#xff0c;我们又不想开会员&#xff0c;想在本地调试给你们提供的代码 声明&#xff1a;本人也是参考的别人的代码&#xff0c;给你们提供不同语言生成链表 参考链接&#xff1a; 参…

正则表达式入门教程

一、本文目标 让你明白正则表达式是什么&#xff0c;并对它有一些基本的了解&#xff0c;让你可以在自己的程序或网页里使用它。 二、如何使用本教程 文本格式约定&#xff1a;专业术语 元字符/语法格式 正则表达式 正则表达式中的一部分(用于分析) 对其进行匹配的源字符串 …

01背包 D. Make Them Equal

Problem - D - Codeforces 输出值不超过k次操作后的最大值。 看b数组的大小&#xff0c;b数组元素是小于1000的正整数。从1到bi如果可以&#xff0c;那么最多是大概10次的&#xff0c;因为是指数递增的&#xff0c;例如&#xff1a;1 -> 2 -> 4 -> 8 -> 16 -> …

12-使用vue2实现todolist待办事项

个人名片&#xff1a; &#x1f60a;作者简介&#xff1a;一名大二在校生 &#x1f921; 个人主页&#xff1a;坠入暮云间x &#x1f43c;座右铭&#xff1a;懒惰受到的惩罚不仅仅是自己的失败&#xff0c;还有别人的成功。 &#x1f385;**学习目标: 坚持每一次的学习打卡 文章…

【Java】若依的使用代码生成及字典的使用

一、导言 1、介绍 若依管理系统是一款基于Java语言开发的开源管理系统。它采用了Spring Boot框架&#xff0c;使得开发更加快速和高效。同时&#xff0c;它还集成了MyBatis Plus&#xff0c;进一步简化了数据库操作。若依管理系统的界面简洁美观&#xff0c;且支持多语言&#…

M系列 Mac使用Homebrew下载配置git和连接GitHub

一、首先我们需要安装Homebrew M系列 Mac安装配置Homebrewhttps://blog.csdn.net/W_Fe5/article/details/134428377?spm1001.2014.3001.5501 二、下载git 1、终端输入一下命令 brew install git 2、这时下载完成 二、配置git 1、创建用户名和邮箱 这里以我自己的邮箱举例…

Unity中Shader矩阵的行列式

文章目录 前言一、什么是矩阵的行列式&#xff1f;1、只有方阵才有行列式&#xff08;即 n X n 的矩阵&#xff09;2、数学上表示为 det(A) 或者 |A|3、行列式可以看做有向面积 或 体积 在空间中的变化影响 二、2 x 2矩阵的行列式三、3 x 3矩阵的行列式四、行列式计算总结五、使…

【电路笔记】-快速了解无源器件

快速了解无源器件 文章目录 快速了解无源器件1、概述2、电阻器作为无源器件3、电感器作为无源器件4、电容器作为无源器件5、总结 无源器件是电子电路的主要构建模块&#xff0c;没有它们&#xff0c;这些电路要么根本无法工作&#xff0c;要么变得不稳定。 1、概述 那么什么是…

word批量图片导出wps office word 图片批量导出

word批量导出图片教程 背景 今天遇到了一个场景&#xff0c;因为word里的图片打开看太模糊了&#xff0c;如果一个一个导出来太麻烦。想批量将word中的图片全部导出 但是&#xff0c;wps导出的时候需要会员 教程开始&#xff1a; 将word保存为 .docx 格式&#xff0c;可以按F1…

JAVA G1垃圾收集器介绍

为解决CMS算法产生空间碎片和其它一系列的问题缺陷&#xff0c;HotSpot提供了另外一种垃圾回收策略&#xff0c;G1&#xff08;Garbage First&#xff09;算法&#xff0c;通过参数-XX:UseG1GC来启用&#xff0c;该算法在JDK 7u4版本被正式推出&#xff0c;官网对此描述如下&am…

C语言判断闰年(ZZULIOJ1028: I love 闰年!)

题目描述 根据一个年份&#xff0c;判断是否是闰年。 输入&#xff1a;输入为一个整数&#xff0c;表示一个年份。 输出&#xff1a;如果是闰年&#xff0c;输出"Yes"&#xff0c;否则输出"No"。输出单独占一行。 样例输入 Copy 2012 样例输出 Copy Yes 分…

二维码智慧门牌管理系统升级技术解决方案

文章目录 前言一、系统升级背景二、系统升级目标 本次系统升级的主要目标包括三、系统升级方案 为实现上述目标&#xff0c;我们提出了以下升级方案&#xff1a;四、系统升级效果 通过本次升级&#xff0c;二维码智慧门牌管理系统将实现个人待办消息提醒和重要通知消息管理等新…

【数据结构】单链表 | 详细讲解

线性表顺序存储结构的优缺点 顺序表优点 无须为了表示中间的元素之间的逻辑关系而增加额外的存储空间&#xff1b;因为以数组形式存储&#xff0c;可以快速地存取表中任一位置的元素。 顺序表缺点 插入和删除操作需要移动大量元素&#xff0c;时间复杂度为O(N)&#xff1b;…

人均年薪70万!华为项目经理具备了哪些能力

大家好&#xff0c;我是老原。 最近在逛脉脉的时候&#xff0c;看到了一位华为项目经理晒出的月收入&#xff1a;5W&#xff0c;这还是不包含每年分红奖励前的到手薪资。 按他现在的19级别&#xff0c;再加上分红奖励&#xff0c;年薪至少在70W&#xff0c;留言区羡慕声一片。…

高科技电子行业采购供应链管理

随着新一代信息技术的发展&#xff0c;我们迈入了数智化时代&#xff0c;各行各业借助技术的升级&#xff0c;加快自身数字化转型的步伐。高科技电子行业作为技术创新的前沿地带&#xff0c;在智能化的道路上也走在传统行业的前面&#xff0c;这其中包括生产研发的技术创新&…

目标检测—Yolo系列(YOLOv1/2/v3/4/5/x/6/7/8)

目标检测概述 什么是目标检测&#xff1f; 滑动窗口&#xff08;Sliding Window&#xff09; 滑动窗口的效率问题和改进 滑动窗口的效率问题&#xff1a;计算成本很大 改进思路 1&#xff1a;使用启发式算法替换暴力遍历 例如 R-CNN&#xff0c;Fast R-CNN 中使用 Selectiv…

IDEA没有Add Framework Support解决办法

点击File—>Settings 点击第一个设置快捷键 点击apply和ok即可 我们要点击一下项目&#xff0c;再按快捷键ctrlk 即可