0

0

使用归并排序高效统计数组中的“坏对”数量

心靈之曲

心靈之曲

发布时间:2025-10-03 14:35:00

|

987人浏览过

|

来源于php中文网

原创

使用归并排序高效统计数组中的“坏对”数量

本文探讨了如何高效统计数组中不满足降序排列条件的“坏对”数量,即前一个元素小于后一个元素的情况。文章首先介绍了一种直观的O(N^2)暴力解法,随后重点阐述了如何通过改进归并排序算法,在O(N log N)的时间复杂度内完成统计,并提供了详细的代码实现和注意事项,旨在帮助读者理解并应用该优化技术。

引言:理解“坏对”问题

在数组处理中,有时我们需要识别不符合特定排序规则的元素对。本教程关注的是一种特殊的“坏对”:给定一个整数数组 hs,如果存在索引 i

例如:

  • 对于 hs = [7, 3, 5, 4, 1],坏对有 (3, 5) 和 (3, 4),总数为 2。
  • 对于 hs = [8, 5, 6, 7, 2, 1],坏对有 (5, 6)、(5, 7) 和 (6, 7),总数为 3。

这个问题本质上是统计数组中的“逆序对”,但通常逆序对定义为 arr[i] > arr[j] 且 i

暴力解法:O(N^2) 时间复杂度

最直观的方法是使用两层嵌套循环遍历数组,对每对满足 i

算法思路

  1. 初始化一个计数器 count 为 0。
  2. 外层循环 i 从 0 遍历到 hs.length - 1。
  3. 内层循环 j 从 i + 1 遍历到 hs.length - 1。
  4. 在内层循环中,如果 hs[i]
  5. 返回 count。

示例代码

public class BadPairCounter {

    /**
     * 使用暴力法统计数组中的“坏对”数量。
     * 时间复杂度:O(N^2)
     *
     * @param hs 输入整数数组
     * @return 坏对的总数
     */
    public static int countBaadBruteForce(int[] hs) {
        int count = 0;
        for (int i = 0; i < hs.length; i++) {
            for (int j = i + 1; j < hs.length; j++) {
                if (hs[i] < hs[j]) {
                    // System.out.println("Found bad pair: (" + hs[i] + "," + hs[j] + ")"); // 可选:打印坏对
                    count++;
                }
            }
        }
        return count;
    }

    public static void main(String[] args) {
        int[] arr1 = {7, 3, 5, 4, 1};
        System.out.println("Array: " + java.util.Arrays.toString(arr1) + ", Bad pairs (Brute Force): " + countBaadBruteForce(arr1)); // Expected: 2

        int[] arr2 = {8, 5, 6, 7, 2, 1};
        System.out.println("Array: " + java.util.Arrays.toString(arr2) + ", Bad pairs (Brute Force): " + countBaadBruteForce(arr2)); // Expected: 3
    }
}

局限性

暴力解法简单易懂,但其时间复杂度为 O(N^2),对于大型数据集,性能会急剧下降,导致计算时间过长。

归并排序优化法:O(N log N) 时间复杂度

为了提高效率,我们可以利用归并排序(Merge Sort)的特性来解决这个问题。归并排序在合并两个已排序的子数组时,会进行元素间的比较,这正是我们统计“坏对”的绝佳时机。

云点滴客户关系管理CRM OA系统
云点滴客户关系管理CRM OA系统

云点滴客户解决方案是针对中小企业量身制定的具有简单易用、功能强大、永久免费使用、终身升级维护的智能化客户解决方案。依托功能强大、安全稳定的阿里云平 台,性价比高、扩展性好、安全性高、稳定性好。高内聚低耦合的模块化设计,使得每个模块最大限度的满足需求,相关模块的组合能满足用户的一系列要求。简单 易用的云备份使得用户随时随地简单、安全、可靠的备份客户信息。功能强大的报表统计使得用户大数据分析变的简单,

下载

核心思想

标准的归并排序将数组递归地分成两半,直到每个子数组只包含一个元素。然后,它将这些子数组两两合并,每次合并都确保子数组是有序的。在这个合并过程中,我们可以有效地统计“坏对”。

为了统计 hs[i] 降序排列的子数组时进行计数。

假设我们有两个已经按降序排列的子数组 L 和 R。在合并它们时,我们从 L 和 R 的头部开始比较:

  • 如果 L[lIdx] >= R[rIdx],这意味着 L[lIdx] 已经比 R[rIdx] 大或相等,符合降序要求,我们将其放入结果数组。
  • 如果 L[lIdx]

算法步骤

  1. countBaad 方法:作为入口,调用 mergeSort 方法。为了避免修改原始数组,可以先复制一份。
  2. mergeSort 方法
    • 基准情况:如果数组长度小于 2(即 0 或 1),则无需排序,也没有“坏对”,返回 0。
    • 分割:将数组 a 分成左右两个子数组 l 和 r。可以使用 System.arraycopy 提高效率。
    • 递归:递归调用 mergeSort(l, mid) 和 mergeSort(r, n - mid),并将它们返回的“坏对”数量累加到 result 中。
    • 合并与计数:调用 merge(a, l, r) 方法,将左右子数组合并到 a 中,并返回合并过程中发现的“坏对”数量,同样累加到 result 中。
    • 返回 result。
  3. merge 方法
    • 初始化 size(用于计数坏对)、lIdx、rIdx、aIdx 为 0。
    • 当 lIdx 和 rIdx 都在各自数组范围内时:
      • 如果 l[lIdx] >= r[rIdx]:将 l[lIdx] 放入 a,lIdx 和 aIdx 递增。
      • 如果 l[lIdx]
    • 处理剩余元素:将 l 或 r 中剩余的元素直接复制到 a 的末尾。这里同样可以使用 System.arraycopy。
    • 返回 size。

示例代码

import java.util.Arrays;

public class BadPairMergeSortCounter {

    /**
     * 入口方法,统计数组中的“坏对”数量。
     * 为了不修改原始数组,先进行复制。
     *
     * @param hs 输入整数数组
     * @return 坏对的总数
     */
    public static int countBaad(int[] hs) {
        // 复制数组以避免修改原始输入
        int[] tempArray = Arrays.copyOf(hs, hs.length);
        return mergeSort(tempArray, tempArray.length);
    }

    /**
     * 递归地对数组进行归并排序并统计“坏对”。
     *
     * @param a 待排序和计数的数组
     * @param n 数组长度
     * @return 当前子数组及其子问题中发现的坏对总数
     */
    public static int mergeSort(int[] a, int n) {
        if (n <= 1) { // 基准情况:数组长度为0或1,没有坏对
            return 0;
        }

        int mid = n / 2;
        int[] l = new int[mid];
        int[] r = new int[n - mid];

        // 分割数组
        System.arraycopy(a, 0, l, 0, mid);
        if (n - mid > 0) { // 确保右子数组有元素
            System.arraycopy(a, mid, r, 0, n - mid);
        }

        int result = 0;
        // 递归统计左右子数组中的坏对
        result += mergeSort(l, mid);
        result += mergeSort(r, n - mid);

        // 合并左右子数组,并统计合并过程中产生的坏对
        result += merge(a, l, r);
        return result;
    }

    /**
     * 合并两个已降序排序的子数组,并统计合并过程中产生的“坏对”。
     *
     * @param a 目标数组,用于存放合并后的结果
     * @param l 左子数组 (已降序排序)
     * @param r 右子数组 (已降序排序)
     * @return 合并过程中发现的坏对数量
     */
    public static int merge(int[] a, int[] l, int[] r) {
        // System.out.println("Merging " + Arrays.toString(l) + " and " + Arrays.toString(r)); // 可选:日志输出合并过程

        int badPairCount = 0;
        int lIdx = 0, rIdx = 0, aIdx = 0;

        while (lIdx < l.length && rIdx < r.length) {
            // 我们希望结果数组是降序的
            if (l[lIdx] >= r[rIdx]) {
                // 如果左边元素大于或等于右边元素,符合降序要求,取左边元素
                a[aIdx++] = l[lIdx++];
            } else {
                // 如果左边元素小于右边元素 (l[lIdx] < r[rIdx])
                // 这意味着 l[lIdx] 与 r[rIdx] 构成一个“坏对”。
                // 由于 l 数组和 r 数组都是降序排列的,
                // r[rIdx] 比 l 数组中从 lIdx 位置到末尾的所有元素都大。
                // 因此,r[rIdx] 与 l 数组中所有剩余元素 (l.length - lIdx 个) 都构成“坏对”。
                badPairCount += (l.length - lIdx);
                // System.out.println("  Found " + (l.length - lIdx) + " bad pairs with " + r[rIdx] + " from " + Arrays.toString(Arrays.copyOfRange(l, lIdx, l.length))); // 可选:详细日志
                a[aIdx++] = r[rIdx++]; // 取右边元素
            }
        }

        // 将左子数组中剩余的元素复制到目标数组
        if (lIdx < l.length) {
            System.arraycopy(l, lIdx, a, aIdx, l.length - lIdx);
        }
        // 将右子数组中剩余的元素复制到目标数组
        if (rIdx < r.length) {
            System.arraycopy(r, rIdx, a, aIdx, r.length - rIdx);
        }

        return badPairCount;
    }

    public static void main(String[] args) {
        int[] arr1 = {7, 3, 5, 4, 1};
        System.out.println("Array: " + java.util.Arrays.toString(arr1) + ", Bad pairs (Merge Sort): " + countBaad(arr1)); // Expected: 2

        int[] arr2 = {8, 5, 6, 7, 2, 1};
        System.out.println("Array: " + java.util.Arrays.toString(arr2) + ", Bad pairs (Merge Sort): " + countBaad(arr2)); // Expected: 3
    }
}

性能与注意事项

  1. 时间复杂度:归并排序本身的时间复杂度为 O(N log N)。在 merge 过程中,我们只增加了常数时间的比较和计数操作,因此整体时间复杂度依然保持在 O(N log N),这比暴力解法 O(N^2) 有显著提升。
  2. 空间复杂度:归并排序需要额外的空间来存储子数组,因此空间复杂度为 O(N)。
  3. 避免副作用:mergeSort 算法会对其操作的数组进行排序。如果仅仅需要统计“坏对”数量而不希望修改原始数组,务必在 countBaad 方法中对输入数组进行深拷贝,例如使用 Arrays.copyOf(hs, hs.length)。
  4. System.arraycopy:在复制子数组时,使用 System.arraycopy 比手动循环赋值效率更高,尤其对于大型数组。

总结

统计数组中特定类型的“坏对”是一个常见的算法问题。虽然暴力解法简单直接,但其 O(N^2) 的时间复杂度使其不适用于大规模数据。通过巧妙地修改归并排序算法,我们可以在 O(N log N) 的时间复杂度内高效地解决这个问题,这在处理大量数据时具有显著的性能优势。理解归并排序中 merge 阶段的计数逻辑是实现这一优化的关键。在实际应用中,还需注意对原始数据的保护(通过复制数组)以及使用高效的数组操作(如 System.arraycopy)。

相关专题

更多
counta和count的区别
counta和count的区别

Count函数用于计算指定范围内数字的个数,而CountA函数用于计算指定范围内非空单元格的个数。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

197

2023.11.20

sort排序函数用法
sort排序函数用法

sort排序函数的用法:1、对列表进行排序,默认情况下,sort函数按升序排序,因此最终输出的结果是按从小到大的顺序排列的;2、对元组进行排序,默认情况下,sort函数按元素的大小进行排序,因此最终输出的结果是按从小到大的顺序排列的;3、对字典进行排序,由于字典是无序的,因此排序后的结果仍然是原来的字典,使用一个lambda表达式作为key参数的值,用于指定排序的依据。

387

2023.09.04

length函数用法
length函数用法

length函数用于返回指定字符串的字符数或字节数。可以用于计算字符串的长度,以便在查询和处理字符串数据时进行操作和判断。 需要注意的是length函数计算的是字符串的字符数,而不是字节数。对于多字节字符集,一个字符可能由多个字节组成。因此,length函数在计算字符串长度时会将多字节字符作为一个字符来计算。更多关于length函数的用法,大家可以阅读本专题下面的文章。

922

2023.09.19

页面置换算法
页面置换算法

页面置换算法是操作系统中用来决定在内存中哪些页面应该被换出以便为新的页面提供空间的算法。本专题为大家提供页面置换算法的相关文章,大家可以免费体验。

403

2023.08.14

云朵浏览器入口合集
云朵浏览器入口合集

本专题整合了云朵浏览器入口合集,阅读专题下面的文章了解更多详细地址。

20

2026.01.20

Java JVM 原理与性能调优实战
Java JVM 原理与性能调优实战

本专题系统讲解 Java 虚拟机(JVM)的核心工作原理与性能调优方法,包括 JVM 内存结构、对象创建与回收流程、垃圾回收器(Serial、CMS、G1、ZGC)对比分析、常见内存泄漏与性能瓶颈排查,以及 JVM 参数调优与监控工具(jstat、jmap、jvisualvm)的实战使用。通过真实案例,帮助学习者掌握 Java 应用在生产环境中的性能分析与优化能力。

29

2026.01.20

PS使用蒙版相关教程
PS使用蒙版相关教程

本专题整合了ps使用蒙版相关教程,阅读专题下面的文章了解更多详细内容。

160

2026.01.19

java用途介绍
java用途介绍

本专题整合了java用途功能相关介绍,阅读专题下面的文章了解更多详细内容。

120

2026.01.19

java输出数组相关教程
java输出数组相关教程

本专题整合了java输出数组相关教程,阅读专题下面的文章了解更多详细内容。

41

2026.01.19

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 2.7万人学习

C# 教程
C# 教程

共94课时 | 7.2万人学习

Java 教程
Java 教程

共578课时 | 48.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号