跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

分治归并排序核心原理及 LeetCode 实战

本文深入剖析分治归并排序的核心原理,通过 C++ 代码实战演示其在四个经典算法题中的应用。涵盖基础排序、逆序对统计、右侧小于当前元素计数以及翻转对问题。文章重点讲解了如何利用归并过程的有序性高效计算逆序关系,避免了暴力枚举的性能瓶颈,并提供了详细的代码实现与关键细节说明,适合希望掌握高级排序技巧的开发者阅读。

SparkGeek发布于 2026/3/28更新于 2026/9/1054 浏览
分治归并排序核心原理及 LeetCode 实战
概念解析

分治归并(基于分治思想的归并排序)是分治算法在排序问题中的经典应用。核心思路是通过'拆分 - 排序 - 合并'三步,将无序数组转化为有序数组,本质是化繁为简、再合简为繁的解题策略。

基础:排序数组

题目描述:

在这里插入图片描述

示例:

在这里插入图片描述

题目链接: LeetCode 912. 排序数组

实现思路:

本质上分治归并就是一个后序遍历的过程。快排是前序遍历,而归并则是不断向下细分数组,然后从下往上把左右两分支的数组排序并合并,以此向上循环往复。

代码实现:

#include <iostream>
#include <vector>
using namespace std;

class Solution {
    vector<int> tmp;
public:
    vector<int> sortArray(vector<int>& nums) {
        tmp.resize(nums.size());
        mergeSort(nums, 0, nums.size() - 1);
        return nums;
    }

    void mergeSort(vector<int>& nums, int left, int right) {
        if (left >= right) return;
        int mid = left + ((right - left) >> 1);
        mergeSort(nums, left, mid);
        mergeSort(nums, mid + 1, right);
        
        int cur1 = 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++];
        
        for (int j = 0; j <= right - left; ++j) {
            nums[left + j] = tmp[j];
        }
    }
};

细节注意:

  • mid 计算使用 left + ((right - left) >> 1) 而非 (left + right) / 2,这是为了避免整数溢出,同时位运算效率略高。
  • 最后一步合并回原数组时,赋值目标是 nums[left + j] 而不是 nums[j],因为递归过程中 left 不一定为 0,我们可能只在对数组的一部分进行排序。
  • 数组排序本身不影响逆序对的计算逻辑,因为逆序对统计是在左右两部分比较时完成的,内部递归已经处理了子区间。
进阶:交易逆序对的总数

题目描述:

在这里插入图片描述

示例:

在这里插入图片描述

题目链接: 剑指 Offer 51. 数组中的逆序对

实现思路:

归并排序的'分治 + 有序合并'特性完美匹配逆序对统计的核心需求。暴力枚举无法应对大数据量,而归并可以在 $O(n \log n)$ 时间内完成。

当 [left, mid] 和 [mid+1, right] 进行互相比较时,如果当前处于升序状态,且发现 record[cur1] >= record[cur2],由于左半部分已有序,所以 cur2 之后的所有元素都小于 record[cur1]。这意味着我们可以直接批量计算出一批逆序数对,无需逐个比对。

代码实现:

class Solution {
    vector<int> tmp;
public:
    int reversePairs(vector<int>& record) {
        tmp.resize(50010);
        return mergeSort(record, 0, record.size() - 1);
    }

    int mergeSort(vector<int>& record, int left, int right) {
        if (left >= right) return 0;
        int ret = 0;
        int mid = left + ((right - left) >> 1);
        ret += mergeSort(record, left, mid);
        ret += mergeSort(record, mid + 1, right);
        
        int cur1 = left, cur2 = mid + 1, i = 0;
        while (cur1 <= mid && cur2 <= right) {
            if (record[cur1] <= record[cur2]) {
                tmp[i++] = record[cur1++];
            } else {
                ret += mid - cur1 + 1;
                tmp[i++] = record[cur2++];
            }
        }
        while (cur1 <= mid) tmp[i++] = record[cur1++];
        while (cur2 <= right) tmp[i++] = record[cur2++];
        
        for (int j = 0; j < right - left + 1; ++j) {
            record[j + left] = tmp[j];
        }
        return ret;
    }
};
扩展:计算右侧小于当前元素的个数

题目描述:

在这里插入图片描述

示例:

在这里插入图片描述

题目链接: LeetCode 315. 计算右侧小于当前元素的个数

实现思路:

这题和上一题思路基本一致,唯一的难点在于题目要求返回每个 index 对应的值。有人可能会问为什么不用哈希表?可以是可以,但如果有重复值会很麻烦。因此额外创建一个数组进行 index 和值的绑定更方便,index 数组跟着 nums 数组一起移动即可。

代码实现:

class Solution {
    vector<int> ret;
    vector<int> index;
    int tmpNums[500010];
    int tmpIndex[500010];
public:
    vector<int> countSmaller(vector<int>& nums) {
        int n = nums.size();
        ret.resize(n, 0);
        index.resize(n);
        for (int i = 0; i < n; ++i) index[i] = i;
        mergeSort(nums, 0, n - 1);
        return ret;
    }

    void mergeSort(vector<int>& nums, int left, int right) {
        if (left >= right) return;
        int mid = left + ((right - left) >> 1);
        mergeSort(nums, left, mid);
        mergeSort(nums, mid + 1, right);
        
        int cur1 = left, cur2 = mid + 1, i = 0;
        while (cur1 <= mid && cur2 <= right) {
            if (nums[cur1] <= nums[cur2]) {
                tmpNums[i] = nums[cur1];
                tmpIndex[i++] = index[cur1++];
            } else {
                ret[index[cur1]] += right - cur2 + 1;
                tmpNums[i] = nums[cur2];
                tmpIndex[i++] = index[cur2++];
            }
        }
        while (cur1 <= mid) {
            tmpNums[i] = nums[cur1];
            tmpIndex[i++] = index[cur1++];
        }
        while (cur2 <= right) {
            tmpNums[i] = nums[cur2];
            tmpIndex[i++] = index[cur2++];
        }
        for (int j = 0; j < right - left + 1; ++j) {
            nums[j + left] = tmpNums[j];
            index[j + left] = tmpIndex[j];
        }
    }
};
变体:翻转对

题目描述:

在这里插入图片描述

示例:

在这里插入图片描述

题目链接: LeetCode 493. 翻转对

实现思路:

思路依然是利用归并解决,但要提前计算符合题目要求的翻转对。如果在排序过程中直接计算,会漏掉部分翻转对,因为排序会打乱原始相对位置。我们需要在归并排序的合并阶段之前,先统计满足条件的对数。

代码实现:

class Solution {
    vector<int> tmp;
    int ret = 0;
public:
    int reversePairs(vector<int>& nums) {
        tmp.resize(nums.size());
        mergeSort(nums, 0, nums.size() - 1);
        return ret;
    }

    void mergeSort(vector<int>& nums, int left, int right) {
        if (left >= right) return;
        int mid = left + ((right - left) >> 1);
        mergeSort(nums, left, mid);
        mergeSort(nums, mid + 1, right);
        
        // 先统计翻转对,此时左右两边各自有序
        int cur1 = left, cur2 = mid + 1;
        while (cur2 <= right) {
            while (cur1 <= mid && (long long)nums[cur1] <= 2LL * nums[cur2]) {
                cur1++;
            }
            if (cur1 > mid) break;
            ret += mid - cur1 + 1;
            cur2++;
        }
        
        // 再进行正常的归并排序
        cur1 = left, cur2 = mid + 1;
        int i = 0;
        while (cur1 <= mid && cur2 <= right) {
            if (nums[cur1] <= nums[cur2]) {
                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 = 0; j < right - left + 1; ++j) {
            nums[j + left] = tmp[j];
        }
    }
};

细节注意:

  • 判断条件 (long long)nums[cur1] <= 2LL * nums[cur2] 必须强制转换类型,防止乘法溢出。
  • 统计翻转对和归并排序是两个独立的步骤,顺序不能颠倒。

目录

  1. 概念解析
  2. 基础:排序数组
  3. 进阶:交易逆序对的总数
  4. 扩展:计算右侧小于当前元素的个数
  5. 变体:翻转对

更多推荐文章

查看全部
  • GitHub Copilot 登录失败排查指南
  • AI Agent 智能体核心架构与实战解析
  • Python 技能实战:从自动化办公到数据分析的职业进阶
  • Python 逆向工程:PyInstaller 字节码提取与恢复指南
  • LangGraph v0.1 正式发布:构建自定义认知架构的新工具
  • 从敏捷到生成式:AIGC如何改变软件测试的全流程
  • Git 原理与进阶使用:远程协作、标签管理与企业级模型
  • LangChain 工具调用与结构化输出实战
  • AI入门系列:人工智能ABC:AI核心概念速通教程
  • 基于 GitHub Actions 的 Notion RSS 自动化部署指南
  • GitHub Copilot 学生认证指南:零基础免费使用 AI 编程助手
  • Spring 配置文件与 MyBatis 基础用法
  • STL 容器适配器 stack 与 queue 底层模拟及算法实战
  • 基于 Rokid 灵珠平台的旅游 AR 智能体搭建指南
  • K-RagRec:知识图谱检索增强生成在 LLM 推荐中的应用
  • OpenCode 与 Claude Code 对比:开源 AI 编程工具选择指南
  • Flutter 三方库 bavard 在鸿蒙系统的适配指南:聊天协议与机器人逻辑
  • C++ 排序函数 sort() 用法与原理
  • OpenClaw Docker 部署教程:飞书/钉钉/QQ 机器人集成
  • Web 聊天室消息加解密方案详解

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online