LeetCode算法日记 - Day 26: 归并排序、交易逆序对的总数

目录

1. 归并排序

1.1 题目解析

1.2 解法

1.3 代码实现

2. 交易逆序对的总数

2.1 题目解析

2.2 解法

2.3 代码实现


1. 归并排序

912. 排序数组 - 力扣(LeetCode)

给你一个整数数组 nums,请你将该数组升序排列。

你必须在 不使用任何内置函数 的情况下解决问题,时间复杂度为 O(nlog(n)),并且空间复杂度尽可能小。

    示例 1:

    输入:nums = [5,2,3,1]
    输出:[1,2,3,5]
    解释:数组排序后,某些数字的位置没有改变(例如,2 和 3),而其他数字的位置发生了改变(例如,1 和 5)。
    

    示例 2:

    输入:nums = [5,1,1,2,0,0]
    输出:[0,0,1,1,2,5]
    解释:请注意,nums 的值不一定唯一。

    提示:

    • 1 <= nums.length <= 5 * 104
    • -5 * 104 <= nums[i] <= 5 * 104

    1.1 题目解析

    题目本质

    把整数数组 nums 升序排列。目标是在不调用内置排序的前提下,做到 O(n log n) 的时间复杂度,且额外空间尽量小

    常规解法

    • 直接用冒泡 / 插入 / 选择:实现简单,但时间复杂度 O(n^2),在 n=5*10^4 时会超时。

    • 快排(手写):平均 O(n log n),最坏 O(n^2),实现要注意枢轴与不变式,否则容易退化。

    问题分析

    题目希望稳定达成 O(n log n)。稳定、可控且好实现的选择是归并排序

    • 分而治之(递归二分)保证 log n 层;

    • 每层合并代价 O(n)

    • 总体 O(n log n)

    • 代价是需要一段临时数组 tmpO(n) 额外空间)。

    思路转折

    要想时间稳定在 O(n log n) 且实现稳健 → 走归并排序路线:

    • 先把左右两段分别排好;

    • 合并时双指针线性扫一遍,把更小的元素依次写入 tmp;

    • 最后把 tmp 回写到 nums[left..right]。

    预测:时间 O(n log n);空间 O(n)(单份 tmp 复用,已经是归并常见的最小开销版)。

    1.2 解法

    算法思想

    分治 + 合并 对区间 [left, right]:

    1. 划分中点 mid = (right + left) / 2;

    2. 递归排好 [left, mid] 与 [mid+1, right];

    3. 用双指针 cur1、cur2 合并到 tmp[0..];

    4. 把 tmp 覆盖回 nums[left..right]。

    此实现是稳定排序:当相等时 nums[cur1] <= nums[cur2] 先取左边,保持相对次序。

    i)在 sortArray 中创建一份长度为 nums.length 的 tmp,供所有递归复用;

    ii)mergeSort(nums, left, right): 

    • 递归终止:left >= right;

    • 分治:mergeSort(nums, left, mid) 与 mergeSort(nums, mid+1, right);

    • 合并:

      • cur1 = left, cur2 = mid+1, i = 0;

      • 当 cur1<=mid && cur2<=right:把较小的放入 tmp[i++];

      • 扫尾:把剩余的另一侧元素依次放入 tmp;

      • 回写:for (int j = left; j <= right; j++) nums[j] = tmp[j-left];

    易错点

    • 合并时比较的是元素值:nums[cur1] <= nums[cur2],不是下标范围。

    • 递增 i 指针 tmp[i++] 。

    • 回写时的偏移要用 j - left,因为 tmp 从 0 写起。

    1.3 代码实现

    class Solution {int[] tmp;public int[] sortArray(int[] nums) {tmp = new int[nums.length];mergeSort(nums, 0, nums.length - 1);return nums;    }public void mergeSort(int[] nums, int left, int right) {if (left >= right) {return;}int mid = (right + left) / 2;mergeSort(nums, left, mid);mergeSort(nums, mid + 1, right);int i = 0, cur1 = left, cur2 = mid + 1;while (cur1 <= mid && cur2 <= right) {tmp[i++] = nums[cur1] <= nums[cur2] ? nums[cur1++] : nums[cur2++];}while (cur1 <= mid)   tmp[i++] = nums[cur1++];while (cur2 <= right) tmp[i++] = nums[cur2++];for (int j = left; j <= right; j++) {nums[j] = tmp[j - left];}}
    }
    

    复杂度分析

    • 时间复杂度:O(n log n)

    • 空间复杂度:O(n)

    2. 交易逆序对的总数

    LCR 170. 交易逆序对的总数 - 力扣(LeetCode)

    在股票交易中,如果前一天的股价高于后一天的股价,则可以认为存在一个「交易逆序对」。请设计一个程序,输入一段时间内的股票交易记录 record,返回其中存在的「交易逆序对」总数。

    示例 1:

    输入:record = [9, 7, 5, 4, 6]
    输出:8
    解释:交易中的逆序对为 (9, 7), (9, 5), (9, 4), (9, 6), (7, 5), (7, 4), (7, 6), (5, 4)。

    提示:

    0 <= record.length <= 50000

    2.1 题目解析

    题目本质

    这是典型的逆序对计数:给定数组 record,统计满足 i < j 且 record[i] > record[j] 的有序对数量。


    核心抽象:用分治 + 合并(归并排序),在合并两段有序区间时批量统计跨段逆序对。

    常规解法

    双重循环枚举所有 (i, j),判断 record[i] > record[j]。

    问题分析

    暴力是 O(n^2),n=5e4 时约 12.5 亿次比较,不可接受。

    思路转折

    要高效 → 需要在有序结构里批量计数 → 归并合并时一次比较可累计一段:

    • 升序合并:若 nums[i] > nums[j](i∈[left,mid], j∈[mid+1,right]),则左侧 [i..mid] 全部大于 nums[j],一次性加 mid - i + 1。

    • 降序合并:若 nums[i] > nums[j],则右侧 [j..right] 全部小于 nums[i],一次性加 right - j + 1。

    预测:整体 O(n log n),可过最大规模。

    2.2 解法

    算法思想

    分治 + 合并计数 对 [left, right]:

    • 递归统计左区间 [left, mid] 总数、右区间 [mid+1, right] 总数;

    • 合并时统计跨跨区间总数

    • 总数 ret = 左区间 + 右区间  + 跨区间。

    升序合并公式

    nums[i] > nums[j] ⇒ ret += (mid - i + 1)。

    降序合并公式

    nums[i] > nums[j] ⇒ ret += (right - j + 1)。

    两种写法都对;差别只在“谁先入 tmp ”以及“加哪一侧的剩余长度”。

    i)递归把 [left, right] 拆到长度为 1;

    ii)分别统计左右段的逆序对;

    iii)合并两段有序数组,同时批量统计跨段逆序对;

    iiii)回写 tmp 到 nums[left..right];

    iiiii)返回计数。

    易错点

    • 递归区间错误

    • 合并时比较的是“值”,不是下标范围:if (nums[i] <= nums[j])。

    • 递增临时数组指针:tmp[k++]

    • 计数的“参照物”要和合并方向匹配

      • 升序合并:右更小时加左侧剩余 mid - i + 1。

      • 降序合并:左更大时加右侧剩余 right - j + 1。

    • 回写用偏移:nums[j] = tmp[j-left],不要用已移动过的 left 做下标。j 遍历原区间 [left..right],j-left 就是对应的 tmp 下标。

    2.3 代码实现

    方法一:升序合并计数

    class Solution {int dest;         int[] tmp;public int reversePairs(int[] record) {if (record == null || record.length < 2) return 0;dest = 0;                                  tmp = new int[record.length];int result = mergeSort(record, 0, record.length - 1);return result;}public int mergeSort(int[] nums, int left, int right) {if (left >= right) return 0;int mid = left + (right - left) / 2;int ret = 0;ret += mergeSort(nums, left, mid);ret += mergeSort(nums, mid + 1, right);int cur1 = left, cur2 = mid + 1, i = 0;while (cur1 <= mid && cur2 <= right) {     // 升序合并if (nums[cur1] <= nums[cur2]) {tmp[i++] = nums[cur1++];} else {// 右更小 → 左侧 [cur1..mid] 都 > nums[cur2]ret += (mid - cur1 + 1);tmp[i++] = nums[cur2++];}}while (cur1 <= mid)   tmp[i++] = nums[cur1++];while (cur2 <= right) tmp[i++] = nums[cur2++];for (int j = left; j <= right; j++) {nums[j] = tmp[j - left];}return ret;}
    }
    

    方法二:降序合并计数

    class Solution {int dest;          int[] tmp;public int reversePairs(int[] record) {if (record == null || record.length < 2) return 0;dest = 0;tmp = new int[record.length];int result = mergeSort(record, 0, record.length - 1);return result;}public int mergeSort(int[] nums, int left, int right) {if (left >= right) return 0;int mid = left + (right - left) / 2;int ret = 0;ret += mergeSort(nums, left, mid);ret += mergeSort(nums, mid + 1, right);int cur1 = left, cur2 = mid + 1, i = 0;while (cur1 <= mid && cur2 <= right) {     // 降序合并if (nums[cur1] > nums[cur2]) {// 左更大 → 右侧 [cur2..right] 都 < nums[cur1]ret += (right - cur2 + 1);tmp[i++] = nums[cur1++];} else {tmp[i++] = nums[cur2++];}}while (cur1 <= mid)   tmp[i++] = nums[cur1++];while (cur2 <= right) tmp[i++] = nums[cur2++];for (int j = left; j <= right; j++) {nums[j] = tmp[j - left];}return ret;}
    }
    

    复杂度分析

    • 时间复杂度:O(n log n)

    • 空间复杂度:O(n)

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

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

    相关文章

    C++(Qt)软件调试---vcpkg安装crashpad(34)

    C(Qt)软件调试—vcpkg安装crashpad&#xff08;34&#xff09; 文章目录C(Qt)软件调试---vcpkg安装crashpad&#xff08;34&#xff09;[toc]1 概述&#x1f41c;2 环境配置3 qt使用crashpad库捕获异常4 cmake中添加crashpad5 相关地址&#x1f410;更多精彩内容&#x1f449;内…

    Kafka 副本同步异常与 ISR 收缩故障排查实录

    背景 某高流量 Kafka 集群&#xff08;原 10G 网卡&#xff09;在切中心时频繁触发带宽报警&#xff0c;扩容至 25G 网卡后出现副本同步异常&#xff1a; 操作流程&#xff1a;停机→升级网卡→重启→触发分区同步→切换首选 Leader现象&#xff1a; 写入流量上升后&#xff0c…

    顶点 (VS)vs 片段(FS):OpenGL纹理滚动着色器的性能博弈与设计哲学

    一个微妙的选择&#xff0c;影响整个应用性能表现在实时图形渲染中&#xff0c;实现纹理滚动效果是一种常见需求。但当我们在顶点着色器和片段着色器之间做出不同实现选择时&#xff0c;会对性能产生显著影响。今天&#xff0c;我们将深入探讨这两种实现的差异&#xff0c;帮助…

    基于博客系统的自动化测试项目

    目录 一、引言 二、项目背景 三、项目功能 1&#xff09;初始登录界面 2&#xff09;博客首页 3&#xff09;博客详情页 4&#xff09;博客编辑页 四、测试工具 1&#xff09;基础操作系统环境 2&#xff09;浏览器环境 3&#xff09;开发与测试工具环境 4&#xf…

    R 语言 eulerr 包绘制韦恩图:比例精准

    在数据可视化中,韦恩图是展示多组数据交集关系的常用工具,尤其在生物信息(如基因差异表达分析)、统计分析等领域高频使用。但传统绘图工具常面临椭圆比例失衡、数值显示混乱、样式调整繁琐等问题,而 R 语言的eulerr包恰好能解决这些痛点 —— 它支持按数据比例自动适配图形…

    CRYPT32!CryptMsgUpdate函数分析和asn.1 editor nt5inf.cat 的总览信息

    0000: 30 83 09 69 2f ; SEQUENCE (9692f Bytes) 0005: 06 09 ; OBJECT_IDENTIFIER (9 Bytes) 0007: | 2a 86 48 86 f7 0d 01 07 02| ; "PKCS 7 已签名 (1.2.840.113549.1.7.2)" 0010: …

    04数据库约束实战:从入门到精通

    感谢黑马程序员提供的免费课程约束概念&#xff1a;约束是作用于表中字段上的规则&#xff0c;用于限制存储在表中的数据。目的&#xff1a;保证数据库中数据的正确、有效性和完整性。常见的几种约束&#xff1a;注意&#xff1a;约束是作用于表中字段上的&#xff0c;可以在创…

    WPF+IOC学习记录

    最近在学WPF&#xff0c;上一篇文章记录了WPF的MVVM自己实现和用框架的区别&#xff08;WPFMVVM入门学习&#xff09;&#xff0c;接下这篇文章记录一下在WPF中使用IOC&#xff0c;这里演示用的是微软官方的DependencyInjection&#xff0c;也可以用其他的第三方框架。 项目源…

    从零开始学习单片机16

    STM32单片机STM32和51单片机的区别51单片机的外设资源少&#xff0c;寄存器少&#xff0c;运行速度慢&#xff0c;价格便宜&#xff0c;容易上手STM32单片机的外设资源更多&#xff0c;寄存器多&#xff0c;运行速度相对快&#xff0c;价格相对贵&#xff0c;上手相对较难STM32…

    [特殊字符]论一个 bug 如何经过千难万险占领线上

    谨以此文献给每一个曾与 Bug 搏斗、最终却目睹它成功上线的你 本文旨在揭露 Bug 的狡猾&#xff0c;绝非鼓励以下行为。若你照做&#xff0c;后果自负&#x1f436;每一个在线上逍遥法外的 Bug&#xff0c;都不是偶然。它是一场精心策划的奇迹&#xff0c;是开发、联调、测试、…

    Day12-python文件操作(二)

    目录前言一、Excel文档操作1.1、xlrd和xlwt库1.2、openpyxl库1.3、pandas库总结前言 今天继续学习文件操作相关内容&#xff0c;为后续办公自动化打基础。 一、Excel文档操作 1.1、xlrd和xlwt库 如果要兼容 Excel 2007 以前的版本&#xff0c;也就是xls格式的 Excel 文件&am…

    CollageIt:简单易用的照片拼贴工具

    在数字图像处理领域&#xff0c;制作照片拼贴是一种常见的创意表达方式。CollageIt作为一款体积小巧、简单易用的照片拼贴工具&#xff0c;能够帮助用户轻松将多张图片拼合成一张精美的拼贴画。它不仅操作简单&#xff0c;还支持多种图片格式&#xff0c;确保用户可以快速制作出…

    Java全栈工程师的实战面试:从基础到微服务的全面解析

    Java全栈工程师的实战面试&#xff1a;从基础到微服务的全面解析 一、开场介绍 面试官&#xff1a;你好&#xff0c;欢迎来到我们公司。我是今天的面试官&#xff0c;负责技术部分的评估。请先简单介绍一下你自己。 应聘者&#xff1a;您好&#xff0c;我叫李明&#xff0c;25岁…

    驱动开发系列68 - GLSL编译器实现 - 算数指令折叠及访存优化

    一 : 指令合并概述 指令折叠的意思,原本一个语句会产生多条指令,通过折叠,可以删除一些中间指令,减少指令数量,并且能够减少寄存器占用。提高执行效率。 举一个例子: MUL A, B, 4 ; A = B * 4MAD D, A, 2, F ; D = A * 2 + F MAD G, A, 3, I ; G …

    深入解析Qt节点编辑器框架:高级特性与性能优化(四)

    文章目录一、高级交互特性&#xff1a;超越基础操作的用户体验提升1. 节点组管理&#xff1a;折叠与嵌套的层级组织2. 智能连接线路由&#xff1a;避免交叉与视觉混乱3. 批量操作与快捷键&#xff1a;提升操作效率二、性能优化&#xff1a;应对大规模节点场景的核心策略1. 图形…

    Python 入门操作指南

    引言 Python 是一种简单易学却功能强大的编程语言,广泛应用于数据分析、人工智能、Web 开发等领域。对于初学者而言,掌握 Python 的入门操作是迈向编程世界的第一步。本文将以总分总的结构,系统介绍 Python 的安装方法、推荐的开发工具、第一个 Python 程序示例,以及包管理…

    ZooKeeper 安装配置

    前言 有时会需要安装开源的大数据集群进行测评或者验证问题&#xff0c;已经装过很多遍了&#xff0c;所以想系统的总结整理一下各个组件的安装部署&#xff0c;包括 Zookeeper、Hadoop、Hive、Spark 等。 版本 Zookeeper 3.5.6 3.8.4 3.9.3 初始化 包括主机名修改、SSH互…

    考研数据结构Part3——二叉树知识点总结

    一、前言 二叉树是一种特殊的树形结构&#xff0c;每个节点最多有两个子节点&#xff0c;分别称为左子树和右子树。其特点是子树有严格的左右之分&#xff0c;顺序不可颠倒。从历年真题来看&#xff0c;二叉树的链式存储实现、遍历算法、属性统计是高频考点&#xff0c;常以选择…

    网络与信息安全有哪些岗位:(12)威胁分析师

    今天是七夕节&#xff0c;首先祝大家早遇良缘、有情人终成眷属&#xff01;&#xff01;七夕节快乐、工作顺利、学业有成~~ 想知道网络与信息安全领域有哪些具体岗位吗&#xff1f;此前我们已陆续介绍网络安全工程师、渗透测试工程师、SOC 总监、SOC 工具运维工程师等核心角色&…

    mysql双机热备(主主模式)

    一、环境准备 主机名ip操作系统备注node01192.168.48.91CentOS Linux 7 (Core)mysql主库node01192.168.48.92CentOS Linux 7 (Core)mysql主库192.168.48.90漂移IP&#xff08;VIP&#xff09; centos7镜像下载地址&#xff1a; https://mirrors.aliyun.com/centos/7.9.2009/…