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

思考

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

  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 ++];elsetmp[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.hqwc.cn/news/179137.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

在Linux中nacos集群模式部署

一、安装 配置nacos 在Linux中建立一个nacos文件夹 mkdir nacos 把下载的压缩包拉入刚才创建好的nacos文件中 解压 tar -zxvf nacos-server-1.4.1\.tar.gz 修改配置文件 进入nacos文件中的conf文件的cluster.conf.example 修改cluster.conf.example文件 vim cluster.conf.exa…

Vue 简单的语法

1.插值表达式 1.插值表达式的作用是什么&#xff1f; 利用表达式进行插值&#xff0c;将数据渲染到页面中&#xff1b; 2.语法结构&#xff1f; {{表达式}} 3.插值表达式的注意点是什么&#xff1f; &#xff08;1&#xff09;使用的数据要存在&#xff0c;在data中&…

11.15 知识总结(模板层、模型层)

一、 模板层 1.1 过滤器 1.什么是过滤器&#xff1f; 过滤器类似于python的内置函数&#xff0c;用来把变量值加以修饰后再显示。 2. 语法 1、 {{ 变量名|过滤器名 }} 2、链式调用&#xff1a;上一个过滤器的结果继续被下一个过滤器处理 {{ 变量名|过滤器1|过滤器2 }} 3、有的过…

网页中 URL 的使用

网页中 URL 的使用 绝对路径相对路径使用场景 网页中的 URL 主要分为两大类&#xff1a;相对路径与绝对路径。 绝对路径 绝对路径可靠性强&#xff0c;而且相对容易理解&#xff0c;在项目中运用较多 形式特点http://test.com/web直接向目标资源发送请求&#xff0c;容易理解…

什么是智能井盖?万宾科技的智能井盖传感器的效果

近年来为打造智慧城市政府一直在不懈努力。加速城市基础建设是一项重要的举措&#xff0c;它有助于推动城市综合治理城市生命线的建设工程。在改善市民生活质量的过程中&#xff0c;市政部门正积极进行井盖的改进和升级工作&#xff0c;特别是那些看似微不足道的井盖却蕴含着重…

Java方法中不使用的对象应该手动赋值为NULL吗?

在java方法中&#xff0c;不使用的对象是否应该手动赋值为null&#xff1f;我们先来通过一个示例看一下。 垃圾回收示例一 public class GuoGuoTest {public static void main(String[] args) {byte[] placeholder new byte[64 * 1024 * 1024];System.gc();} } 上面代码向内…

报错缺少class(org.apache.hadoop.hdfs.DistributedFileSystem)

平台报错缺少 java.lang.RuntimeException:java.lang.ClassNotFoundException: Class org.apache.hadoop.hdfs.DistributedFileSystem not found 实则是缺少jar包 hadoop-hdfs-client-3.1.1.3.1.0.0-78.jar 找到对应的jar放到程序的lib中即可

【外汇天眼】解析外汇交易平台:深度了解DD与NDD两大模式

外汇交易平台种类繁多&#xff0c;涵盖不同的分类与运营模式&#xff0c;令投资者难以甄别&#xff0c;也增加了选择的难度。为了解决这一问题&#xff0c;我们将更深入地了解外汇平台的多样性。 在线外汇交易平台主要分为两大类&#xff1a;处理平台模式&#xff08;Dealing …

移植freertos到qemu上运行

1、freertos源码下载 参考博客&#xff1a;《freertos源码下载和目录结构分析》&#xff1b; 2、编译freertos 2.1、选择合适的Demo freertos官方已经适配过qemu&#xff0c;所以我们并不需要做源码级别的移植&#xff0c;只需要选择合适的Demo文件夹。 2.2、修改Makefile 2.3…

ATFX汇市:10月美国名义CPI年率大降,美元指数创近三月新低

ATFX汇市&#xff1a;据美国劳工部劳动统计局数据&#xff0c;美国10月未季调CPI年率最新值3.2%&#xff0c;低于前值3.7%&#xff0c;低于预期值3.3%&#xff1b;10月未季调核心CPI年率最新值4%&#xff0c;低于前置和预期值的4.1%。名义CPI与核心CPI双双下降&#xff0c;透露…

Python--快速入门四

Python--快速入门四 1.Python函数 1.在括号中放入函数的参数。 2.可以通过return在函数作用域外获取函数作用域内的值。(默认的return值为None) 代码展示&#xff1a;BMI计算函数 def calculate_BMI(fuc_height,fuc_weight):fuc_BMI fuc_weight/(fuc_height**2)return fuc…

day21_mysql

今日内容 零、 复习昨日 第一阶段: Java基础知识(会编程,懂编程) 第二阶段: Web开发(前端,后端,数据库) 一、MySQL 一、引言 二、数据库 2.1 概念 ​ 数据库是“按照数据结构来组织、存储和管理数据的仓库。是一个长期存储在计算机内的、有组织的、有共享的、统一管理的数据集合…