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

归并排序实战:计算右侧小于当前元素个数与翻转对

综述由AI生成归并排序是解决逆序类问题的利器。针对计算右侧小于当前元素个数,核心在于归并过程中维护原始下标,实时统计右半部分比左半部分小的数量;而翻转对因涉及数值倍数关系,需在合并前独立遍历统计,防止排序干扰计数。两者时间复杂度均为 O(n log n),体现了分治策略在算法优化中的典型应用。

技术博主发布于 2026/3/29更新于 2026/6/1420 浏览
归并排序实战:计算右侧小于当前元素个数与翻转对

归并排序实战:计算右侧小于当前元素个数与翻转对

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

题目描述

给定一个整数数组 nums,按要求返回一个新数组 counts。其中 counts[i] 表示在 nums[i] 右边且比它小的元素个数。

示例图

解法思路

这道题本质上是求逆序对的变种。普通的逆序对统计的是总数,而这里需要记录每个元素对应的数量。

归并排序过程中,元素的下标会发生变化,为了准确统计,我们需要维护一个辅助数组来绑定原始下标。当左半区间的元素大于右半区间的元素时,说明左边的这个元素比右边剩余的所有元素都大,此时可以直接累加计数。

C++ 代码实现

class Solution {
public:
    vector<int> counts;
    vector<int> index; // 记录 nums 中当前元素的原始下标
    vector<int> tmp;
    vector<int> tmpi; // 临时记录合并后对应元素位置变化下标随之变化的数组

    vector<int> countSmaller(vector<int>& nums) {
        counts.resize(nums.size());
        index.resize(nums.size());
        tmp.resize(nums.size());
        tmpi.resize(nums.size());
        
        // 初始化 index,使得 nums 每个元素下标和 index 一一对应
        for(int i = 0; i < nums.size(); i++) {
            index[i] = i;
        }
        mergesort(nums, 0, nums.size() - 1);
        return counts;
    }

    void mergesort(vector<int>& nums, int left, int right) {
        if(left == right) {
            return;
        }
        int mid = (right - left) / 2 + left;
        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]) {
                counts[index[cur1]] += right - cur2 + 1; // 关键点:左边元素大于右边,则右边剩余元素均小于左边
                tmpi[i] = index[cur1];
                tmp[i++] = nums[cur1++];
            } else {
                tmpi[i] = index[cur2];
                tmp[i++] = nums[cur2++];
            }
        }
        while(cur1 <= mid) {
            tmpi[i] = index[cur1];
            tmp[i++] = nums[cur1++];
        }
        while(cur2 <= right) {
            tmpi[i] = index[cur2];
            tmp[i++] = nums[cur2++];
        }
        for(int j = left; j <= right; j++) {
            nums[j] = tmp[j - left];
            index[j] = tmpi[j - left];
        }
    }
};

算法流程解析

在合并阶段,如果 nums[cur1] > nums[cur2],由于左右区间已经有序,cur1 及其之后的所有元素都会大于 nums[cur2] 及其之后的元素。因此,对于 index[cur1] 指向的原始位置,其右侧比它小的元素数量增加了 right - cur2 + 1 个。

流程解析图


50. 翻转对

题目描述

给定一个数组 nums,如果 i < j 且 nums[i] > 2 * nums[j],我们就将 (i, j) 称作一个重要翻转对。

示例图

解法思路

翻转对与逆序对类似,区别在于判断条件是 nums[i] > 2 * nums[j]。同样可以利用归并排序的分治思想,将问题分解为三部分:左半区间、右半区间、以及跨越左右区间的翻转对。

关键难点在于:普通逆序对可以在合并时顺便统计,但翻转对涉及数值倍数关系,直接合并可能会打乱顺序导致无法快速判断。因此,需要在归并排序之前先完成翻转对的统计,然后再进行正常的归并操作。

C++ 代码实现

class Solution {
public:
    vector<int> tmp;
    int count = 0;

    int reversePairs(vector<int>& nums) {
        tmp.resize(nums.size());
        mergesort(nums, 0, nums.size() - 1);
        return count;
    }

    void mergesort(vector<int>& nums, int left, int right) {
        if(left >= right) {
            return;
        }
        int mid = (right - left) / 2 + left;
        mergesort(nums, left, mid);
        mergesort(nums, mid + 1, right);

        // 先统计翻转对,再合并数组
        int cur1 = left, cur2 = mid + 1;
        while(cur1 <= mid && cur2 <= right) {
            if(nums[cur1] > (long long)2 * nums[cur2]) {
                count += right - cur2 + 1;
                cur1++;
            } else {
                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 i = left; i <= right; i++) {
            nums[i] = tmp[i - left];
        }
    }
};

算法流程解析

注意这里使用了 long long 类型转换,防止 2 * nums[cur2] 发生整数溢出。统计逻辑是:当 nums[cur1] 满足条件时,由于右区间有序,cur1 左侧(相对于 cur2)的所有元素也都满足条件,因此直接累加剩余数量。

流程解析图

目录

  1. 归并排序实战:计算右侧小于当前元素个数与翻转对
  2. 49. 计算右侧小于当前元素的个数
  3. 题目描述
  4. 解法思路
  5. C++ 代码实现
  6. 算法流程解析
  7. 50. 翻转对
  8. 题目描述
  9. 解法思路
  10. C++ 代码实现
  11. 算法流程解析
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • JVS-APS:算法驱动与低代码融合的智能排产系统
  • Trae IDE + MCP Server: 一键将 Figma 设计稿转为前端代码
  • Git SSH 公钥配置指南(GitHub/GitLab/Gitee)
  • Spring HttpMediaTypeNotAcceptableException 异常分析与解决方案
  • AI 行业周报:NVIDIA GTC 硬件路线图与各大模型动态解析
  • 云开发 Copilot:AI 辅助低代码开发实践与解析
  • AI Agent 入门指南:从零开始掌握大模型智能体技术
  • AI 股票分析系统搭建:Python 爬虫数据采集实战
  • LangChain v1.0 中间件机制:AI Agent 上下文控制指南
  • Python 数据分析实战:基于 Pandas 的数据处理全流程指南
  • AI Agent 架构:基础组成模块深度解析
  • DeepSeek 结合通义万相制作 AI 视频实战详解
  • TortoiseGit 下载与 SSH 密钥配置指南
  • GitHub Copilot 接入第三方 OpenAI 兼容模型方案
  • 前后端分离与不分离架构的全面分析
  • 基于 Ollama 本地部署 Llama3 模型指南
  • GitHub Copilot Agent 模式使用经验与配置指南
  • GLM-4.6V-Flash 实时推理方案:按秒计费与成本精确控制
  • 前端 Word 文档在线预览的多种实现方案
  • Ansible 批量部署 Nginx 实战:从安装到远程穿透

相关免费在线工具

  • 加密/解密文本

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