news 2026/5/31 8:27:30

【算法题】归并排序

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【算法题】归并排序

归并排序是基于分治思想的经典排序算法,核心逻辑是“拆分→排序→合并”:将数组递归拆分为子数组,分别排序后再合并为有序数组。它是稳定排序(相同元素相对位置不变),时间复杂度稳定为O(nlog⁡n)O(n\log n)O(nlogn),不仅能解决排序问题,更能高效处理“逆序对统计”“右侧元素计数”等衍生问题。本文通过4道经典题目,拆解归并排序在不同场景下的解题思路与代码实现。

一、排序数组

题目描述:
实现归并排序,将整数数组升序排列(不能使用内置排序函数,要求时间复杂度O(nlog⁡n)O(n\log n)O(nlogn))。

示例

  • 输入:nums = [5,2,3,1],输出:[1,2,3,5]

解题思路:
标准归并排序的三步流程:

  1. 拆分:将数组从中间拆分为左右两个子数组,递归排序子数组。
  2. 合并:用临时数组tmp合并两个有序子数组(双指针遍历左右子数组,取较小值存入临时数组)。
  3. 还原:将临时数组的有序元素写回原数组的对应区间。

完整代码:

classSolution{vector<int>tmp;public:vector<int>sortArray(vector<int>&nums){tmp.resize(nums.size());mergeSort(nums,0,nums.size()-1);returnnums;}voidmergeSort(vector<int>&nums,intleft,intright){if(left>=right)return;// 1. 取中间点intmid=(left+right)>>1;// 2. 分区间排序mergeSort(nums,left,mid);mergeSort(nums,mid+1,right);// 3. 合并intcur1=left,cur2=mid+1,i=0;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++];// 4. 还原for(inti=left;i<=right;i++){nums[i]=tmp[i-left];}}};

复杂度分析:

  • 时间复杂度:O(nlog⁡n)O(n\log n)O(nlogn),拆分的递归深度为O(log⁡n)O(\log n)O(logn),每层合并的时间为O(n)O(n)O(n)
  • 空间复杂度:O(n)O(n)O(n),临时数组tmp存储合并结果(递归栈深度为O(log⁡n)O(\log n)O(logn),可忽略)。

二、交易逆序对的总数(经典逆序对)

题目描述:
给定股票交易记录数组record,若前一天股价高于后一天股价,视为一个“交易逆序对”,返回逆序对总数。

示例

  • 输入:record = [9,7,5,4,6],输出:8(逆序对包括(9,7)(9,5)等)

解题思路:
在归并排序的合并阶段统计逆序对

  1. 拆分并递归排序左右子数组,同时统计子数组内部的逆序对。
  2. 合并时,若左子数组的当前元素nums[cur1] > nums[cur2],说明左子数组中cur1~mid的所有元素都与nums[cur2]构成逆序对,逆序对数量增加mid - cur1 + 1
  3. 合并完成后,将有序元素写回原数组。

完整代码:

classSolution{vector<int>tmp;public:intreversePairs(vector<int>&record){tmp.resize(record.size());returnmergeSort(record,0,record.size()-1);}intmergeSort(vector<int>&nums,intleft,intright){if(left>=right)return0;intret=0;// 1. 找中点划分区间intmid=(left+right)>>1;// 2. 找左/右区间个数并排序ret+=mergeSort(nums,left,mid);ret+=mergeSort(nums,mid+1,right);inti=0,cur1=left,cur2=mid+1;// 3. 一左一右找并合并排序while(cur1<=mid&&cur2<=right){if(nums[cur1]<=nums[cur2])tmp[i++]=nums[cur1++];else{ret+=mid-cur1+1;tmp[i++]=nums[cur2++];}}// 4. 处理剩余元素while(cur1<=mid)tmp[i++]=nums[cur1++];while(cur2<=right)tmp[i++]=nums[cur2++];// 5. 还原for(inti=left;i<=right;i++)nums[i]=tmp[i-left];returnret;}};

复杂度分析:

  • 时间复杂度:O(nlog⁡n)O(n\log n)O(nlogn),排序与统计逆序对的时间均为O(nlog⁡n)O(n\log n)O(nlogn)
  • 空间复杂度:O(n)O(n)O(n),临时数组tmp存储合并结果。

三、计算右侧小于当前元素的个数(带索引的逆序统计)

题目描述:
返回数组counts,其中counts[i]nums[i]右侧小于nums[i]的元素数量。

示例

  • 输入:nums = [5,2,6,1],输出:[2,1,1,0]

解题思路:
在归并排序中维护元素的原始索引,从而统计每个元素右侧更小的元素数量:

  1. index数组记录元素的原始索引,合并时同步维护索引的顺序。
  2. 合并阶段,若左子数组的当前元素nums[cur1] > nums[cur2],说明nums[cur1]右侧存在right - cur2 + 1个更小的元素,将该值累加到ret[index[cur1]]
  3. 合并完成后,同步还原numsindex数组的顺序。

完整代码:

classSolution{vector<int>ret;vector<int>index;inttmpNums[500010];inttmpIndex[500010];public:vector<int>countSmaller(vector<int>&nums){intn=nums.size();ret.resize(n);index.resize(n);for(inti=0;i<n;i++)index[i]=i;mergeSort(nums,0,n-1);returnret;}voidmergeSort(vector<int>&nums,intleft,intright){if(left>=right)return;intmid=(left+right)>>1;mergeSort(nums,left,mid);mergeSort(nums,mid+1,right);intcur1=left,cur2=mid+1,i=0;while(cur1<=mid&&cur2<=right){if(nums[cur1]<=nums[cur2]){tmpNums[i]=nums[cur2];tmpIndex[i++]=index[cur2++];}else{ret[index[cur1]]+=right-cur2+1;tmpNums[i]=nums[cur1];tmpIndex[i++]=index[cur1++];}}while(cur1<=mid){tmpNums[i]=nums[cur1];tmpIndex[i++]=index[cur1++];}while(cur2<=right){tmpNums[i]=nums[cur2];tmpIndex[i++]=index[cur2++];}for(inti=left;i<=right;i++){nums[i]=tmpNums[i-left];index[i]=tmpIndex[i-left];}}};

复杂度分析:

  • 时间复杂度:O(nlog⁡n)O(n\log n)O(nlogn),排序与统计的时间均为O(nlog⁡n)O(n\log n)O(nlogn)
  • 空间复杂度:O(n)O(n)O(n),存储临时数组和索引数组。

四、翻转对(扩展逆序对)

题目描述:
i < jnums[i] > 2 * nums[j],则称(i,j)为“翻转对”,返回数组中的翻转对总数。

示例

  • 输入:nums = [1,3,2,3,1],输出:2(翻转对为(3,1)(3,1)

解题思路:
在归并排序的合并前阶段统计翻转对

  1. 拆分并递归排序左右子数组,同时统计子数组内部的翻转对。
  2. 合并前,用双指针遍历左右子数组:若nums[cur1] > 2 * nums[cur2],则左子数组中cur1~mid的所有元素都与nums[cur2]构成翻转对,翻转对数量增加mid - cur1 + 1,并右移cur2;否则右移cur1
  3. 统计完成后,合并两个有序子数组并写回原数组。

完整代码:

classSolution{inttmp[50010];public:intreversePairs(vector<int>&nums){returnmergeSort(nums,0,nums.size()-1);}intmergeSort(vector<int>&nums,intleft,intright){if(left>=right)return0;intret=0;intmid=(left+right)>>1;ret+=mergeSort(nums,left,mid);ret+=mergeSort(nums,mid+1,right);// 合并前统计翻转对intcur1=left,cur2=mid+1;while(cur1<=mid){while(cur2<=right&&nums[cur1]/2.0<=nums[cur2])cur2++;if(cur2>right)break;ret+=right-cur2+1;cur1++;}// 合并两个有序子数组cur1=left,cur2=mid+1;inti=0;while(cur1<=mid&&cur2<=right)tmp[i++]=nums[cur1]<=nums[cur2]?nums[cur2++]:nums[cur1++];while(cur1<=mid)tmp[i++]=nums[cur1++];while(cur2<=right)tmp[i++]=nums[cur2++];for(inti=left;i<=right;i++)nums[i]=tmp[i-left];returnret;}};

复杂度分析:

  • 时间复杂度:O(nlog⁡n)O(n\log n)O(nlogn),统计翻转对与合并的时间均为O(nlog⁡n)O(n\log n)O(nlogn)
  • 空间复杂度:O(n)O(n)O(n),临时数组tmp存储合并结果。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/28 12:58:12

智能电梯门禁(可视对讲联动梯控)方案实现梯控联动召梯、呼梯、访客联动功能,完全融入楼宇可视对讲门禁系统,核心通过协议对接 + 物理接线双重方式,保障乘梯权限管理与联动控制的稳定性。

这份清单非常专业&#xff0c;清晰地勾勒出了一套深度融入楼宇对讲系统的智能梯控解决方案。这不仅仅是设备的堆砌&#xff0c;更是一套通过协议对接和硬件联动&#xff0c;实现从“业主无感通行”到“访客精准授权”全场景覆盖的完整蓝图楼宇可视对讲门禁与梯控系统联动方案一…

作者头像 李华
网站建设 2026/5/28 21:46:33

Linux网络编程-UDP 广播原理与实战

一、UDP 广播核心概念 UDP 广播是指一台主机向所在子网&#xff08;同一局域网&#xff09;内的所有主机发送数据的通信方式&#xff0c;是 UDP 无连接特性的典型应用场景。 1.1 广播地址分类 类型格式 / 示例特点受限广播地址255.255.255.255① 不会被路由器转发&#xff1…

作者头像 李华
网站建设 2026/5/28 15:28:00

什么是RPKI

文章目录为什么需要RPKIRPKI是如何工作的RPKI功能扩展RPKI&#xff08;Resource Public Key Infrastructure&#xff0c;资源公钥基础设施&#xff09;是一种基于PKI&#xff08;Public Key Infrastructure&#xff0c;公钥基础设施&#xff09;的技术&#xff0c;专门用于验证…

作者头像 李华
网站建设 2026/5/29 1:37:58

销售额飙涨 2.5 倍,TACOS 直降 10 点!DeepBI 助力亚马逊卖家高效破局

亚马逊美国站卖家&#xff0c;谁没遇到过 “卖得多、赚得少” 的尴尬&#xff1f;深圳一家深耕美国站点的工厂卖家&#xff0c;就曾面临这样的困境——5月广告总销售额虽高&#xff0c;ACOS却高达30.58%&#xff0c;运营成本居高不下。直到邂逅DeepBI智能AI广告系统&#xff0c…

作者头像 李华