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

分治归并排序算法解析及经典题目实战

分治归并排序通过递归拆分与有序合并实现高效排序。核心在于“拆分 - 排序 - 合并”三步流程。解析该算法原理,涵盖排序数组基础应用,以及交易逆序对总数、计算右侧小于当前元素个数、翻转对等经典变体。代码采用 C++ 实现,包含中间值计算防溢出、临时数组管理等细节优化。适用于解决涉及顺序统计和区间比较的复杂问题。

魔法巫师发布于 2026/3/16更新于 2026/10/1780 浏览
分治归并排序算法解析及经典题目实战

1. 概念解析

什么是分治 - 归并?

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

2. 排序数组

题目描述:

图片

示例:

图片

排序数组

题解:

图片

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

细节问题:

  • int mid = left + ((right - left) >> 1) 相当于 int mid = left + ((right - left) / 2),二进制的算法效率更高,且该计算中间值的方法能避免整数溢出
  • 最后一步合并数组,nums[left + j] = tmp[j] 而不是 nums[j] = tmp[j],是因为 left 不一定是 0,即不一定是对原来的整个数组进行排序,可能只对数组一部分进行排序
  • 数组排序并不影响逆序对的计算,因为是左右两部分比较,内部已经在递归过程中计算过了

代码实现:

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

3. 交易逆序对的总数

题目描述:

图片

示例:

图片

交易逆序对的总数

题解:

图片

因为归并排序的'分治 + 有序合并'特性,完美匹配逆序对统计的核心需求 —— 高效拆分问题、批量计算逆序对,这是暴力枚举做不到的。当 [left,mid] 和 [mid+1,right] 进行互相比较时,如果是升序,获取到 record[cur1] >= record[cur2] 时,由于是有序,所以 cur2 往后都是小于 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;
    }
};

4. 计算右侧小于当前元素的个数

题目描述:

图片

示例:

图片

计算右侧小于当前元素的个数

题解:

图片

这题和上一题思路基本一致,唯一的难点就是要额外创建一个数组进行值和下表的绑定,因为题目要求的是返回每个 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[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 (int j = 0; j < right - left + 1; ++j) {
            nums[j + left] = tmpNums[j];
            index[j + left] = tmpindex[j];
        }
    }
};

5. 翻转对

题目描述:

图片

示例:

图片

翻转对

题解:

图片

思路还是利用归并解决,但是要提前计算符合题目要求的翻转对,如果在排序过程中进行计算,会漏掉部分翻转对

细节问题:

(long long)nums[cur1] <= 2 * (long long)nums[cur2] 防止溢出

代码实现:

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, i = 0;
        while (cur2 <= right) {
            while (cur1 <= mid && (long long)nums[cur1] <= 2 * (long long)nums[cur2]) {
                cur1++;
            }
            if (cur1 > mid) {
                break;
            }
            ret += mid - cur1 + 1;
            cur2++;
        }
        cur1 = left, cur2 = mid + 1;
        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];
        }
    }
};

目录

  1. 1. 概念解析
  2. 2. 排序数组
  3. 3. 交易逆序对的总数
  4. 4. 计算右侧小于当前元素的个数
  5. 5. 翻转对

更多推荐文章

查看全部
  • C++ std::list 常用接口详解
  • 明基 RD280U 显示器评测:28 寸 3:2 方屏优化编程体验
  • Node.js 下载安装与环境配置全流程
  • GitHub Copilot 提升 AI 原生应用开发效率的 5 种方法
  • GitHub 项目本地运行指南:Python/Node.js/Java 实战部署与容器化
  • Linux 网络编程:UDP Socket 群聊模型实现与细节分析
  • 本地部署 AI 大模型的电脑硬件配置指南
  • 大语言模型联邦微调综述:挑战、方法与应用
  • OpenClaw 龙虾机器人本地部署技术指南
  • Windows 部署 OpenAkita 接入飞书,搭建本地 AI 中台
  • LLM Agent 驱动 SLAM 回环检测:Python 与 LangChain 轻量化实践
  • Vivado 安装指南:从官网下载至完成配置
  • DeepSeek 深度使用指南与提示词技巧
  • Mac 系统安装与配置 OpenClaw 指南
  • 基于C++构建DeepSeek大模型推理SDK:架构设计与工程实践
  • Vue Router 核心功能与进阶使用指南
  • AI 产品经理转型指南:无需深究算法与数学模型
  • Linux 网络编程实战:用 C++ 实现 JSON 解析与 HTTP 服务器
  • OpenClaw WebUI 空白页问题修复指南
  • ClawdBot 本地 AI 实践:树莓派 4 运行 OCR/Whisper/vLLM 高并发

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如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