当前位置: 首页 > news >正文

Day 21: 数组中的逆序对

在股票交易中,如果前一天的股价高于后一天的股价,则可以认为存在一个「交易逆序对」。请设计一个程序,输入一段时间内的股票交易记录 record,返回其中存在的「交易逆序对」总数。

示例 1:

输入:record = [9, 7, 5, 4, 6]
输出:8
解释:交易中的逆序对为 (9, 7), (9, 5), (9, 4), (9, 6), (7, 5), (7, 4), (7, 6), (5, 4)。

提示:

0 <= record.length <= 50000

LCR 170. 交易逆序对的总数 - 力扣(LeetCode)

 这个题目要用归并排序来写,暴力解法很容易想得到。刚好借这个题目来复习一下归并排序。

归并排序的代码如下:

public class MergeSort {

    // 归并排序的主方法
    public static void mergeSort(int[] arr) {
        if (arr == null || arr.length <= 1) {
            return; // 如果数组为空或只有一个元素,直接返回
        }
        int[] temp = new int[arr.length]; // 临时数组,用于合并
        mergeSort(arr, 0, arr.length - 1, temp);
    }

    // 递归分治
    private static void mergeSort(int[] arr, int left, int right, int[] temp) {
        if (left < right) {
            int mid = left + (right - left) / 2; // 计算中间位置
            mergeSort(arr, left, mid, temp);     // 对左半部分排序
            mergeSort(arr, mid + 1, right, temp); // 对右半部分排序
            merge(arr, left, mid, right, temp);  // 合并两个有序部分
        }
    }

    // 合并两个有序数组
    private static void merge(int[] arr, int left, int mid, int right, int[] temp) {
        int i = left;    // 左半部分的起始索引
        int j = mid + 1; // 右半部分的起始索引
        int k = 0;       // 临时数组的起始索引

        // 将两个有序数组合并到临时数组中
        while (i <= mid && j <= right) {
            if (arr[i] <= arr[j]) {
                temp[k++] = arr[i++];
            } else {
                temp[k++] = arr[j++];
            }
        }

        // 将左半部分剩余的元素复制到临时数组
        while (i <= mid) {
            temp[k++] = arr[i++];
        }

        // 将右半部分剩余的元素复制到临时数组
        while (j <= right) {
            temp[k++] = arr[j++];
        }

        // 将临时数组中的元素复制回原数组
        k = 0;
        while (left <= right) {
            arr[left++] = temp[k++];
        }
    }

需要注意这段代码:

// 将临时数组中的元素复制回原数组
        k = 0;
        while (left <= right) {
            arr[left++] = temp[k++];
        }

写的时候容易漏掉,临时数组的元素一定要返回到原数组中。 

我们会发现在归并排序的过程中都会比较两个子序列中两个数的大小,在合并两个子序列的过程中,右边的数组指针指向i项,比左边的元素小,由于左右子序列都是已经排好序的了,那么右边数组就有i项比左边现在这个元素小,也就是有i个子序列。注意一开始指针指向子数组末尾。

基于这个思想,只要在归并排序的过程中统计逆序对个数就行了。

class Solution {
    public int reversePairs(int[] record) {
        if (record == null || record.length <= 1) {
            return 0; // 如果数组为空或只有一个元素,直接返回
        }
        int[] temp = new int[record.length]; // 临时数组,用于合并
        int num = mergeSort(record, 0, record.length - 1, temp);
        return num;
    }

    private int mergeSort(int[] arr, int left, int right, int[] temp){
        if(left >= right){
            return 0;
        }
        int mid = left + (right - left)/2;
        int l = mergeSort(arr, left, mid, temp);
        int r = mergeSort(arr, mid + 1, right, temp);

        if (arr[mid] <= arr[mid + 1]) {
            return l + r;
        }

        int cur = merge(arr, left, mid, right, temp);

        return l + r + cur;
    }

    private int merge(int[] arr, int left, int mid, int right, int[] temp){
        int i = mid;
        int j = right;
        int k = right;//临时数组的索引
        int reversePairsNum = 0;//逆序对个数。
        while (i >= left && j > mid) {
            if (arr[i] > arr[j]) {
                reversePairsNum += (j - mid);
                temp[k] = arr[i];
                k--;
                i--;
            } else {
                temp[k] = arr[j];
                k--;
                j--;
            }
        }

         // 将左半部分剩余的元素复制到临时数组
        while (i >= left) {
            temp[k--] = arr[i--];
        }

        // 将右半部分剩余的元素复制到临时数组
        while (j > mid) {
            temp[k--] = arr[j--];
        }

        // 将临时数组中的元素复制回原数组
        for (i = left; i <= right; i++) {
            arr[i] = temp[i];
        }

        return reversePairsNum;
    }
}

 很多细节没注意到,一开始写成i > left导致总是漏掉最小元素的逆序对,我觉得还是归并的熟练度不够。我是按照剑指offer里面思路写的,把归并排序逆过来了。比较直观。但是逆过来的时候等号取不取得到要注意一下。建议手动排序一下。

力扣上采用的是正向的归并排序,不容易出错:

 private int mergeAndCount(int[] record, int left, int mid, int right, int[] temp) {
        for (int i = left; i <= right; i++) {
            temp[i] = record[i];
        }

        int i = left;
        int j = mid + 1;

        int count = 0;
        for (int k = left; k <= right; k++) {

            if (i == mid + 1) {
                record[k] = temp[j];
                j++;
            } else if (j == right + 1) {
                record[k] = temp[i];
                i++;
            } else if (temp[i] <= temp[j]) {
                record[k] = temp[i];
                i++;
            } else {
                record[k] = temp[j];
                j++;
                count += (mid - i + 1);
            }
        }
        return count;
    }
}

作者:力扣官方题解
链接:https://leetcode.cn/problems/shu-zu-zhong-de-ni-xu-dui-lcof/solutions/216984/shu-zu-zhong-de-ni-xu-dui-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

其实二者的本质是一样的,【3,5,7,9】和 【4,6,8,10】当5>4的时候就把(7,4)  (9,4)  算进去。所以每次count只要加上mid-i +1即可。

我一开始想用j - mid发现在偶数是对的,奇数就是错的,因为比如[3,5,7,8][4,6,9]这样的数组,因为奇数个的会把mid取在left这边,就会导致8为第一项的逆序对无法加入,因为不会出现8>?的判断条件。

相关文章:

  • Java Stream两种list判断字符串是否存在方案
  • 深度学习优化技术教程
  • 机器人打磨控制技术
  • electron框架(4.0)electron-builde和electron Forge的打包方式
  • 第J3周:DenseNet121算法实现01(Pytorch版)
  • C语言指针(详细总结)
  • Linux应用:Linux的信号
  • UI设计中的加载动画:优化用户体验的细节
  • MCU-芯片时钟与总线和定时器关系,举例QSPI
  • AI agent 开发全链路工具集
  • 《论语别裁》第01章 学而(31) 诗的人生
  • [C++游戏开发基础]:数据封装(隐藏)的好处
  • JVM 的类加载机制原理
  • 常用的git和linux命令有哪些?
  • 【C#】CS学习之Modbus通讯
  • 微信小程序计算属性与监听器:miniprogram-computed
  • 【Mybatis】动态sql
  • HarmonyOS NEXT 组件状态管理的对比
  • IoT设备测试:从协议到硬件的全栈验证体系与实践指南
  • 某公司制造业研发供应链生产数字化蓝图规划P140(140页PPT)(文末有下载方式)
  • 北方将现今年首场大范围高温天气,山西河南山东陕西局地可超40℃
  • 马上评|训斥打骂女儿致死,无暴力应是“管教”底线
  • 昔日千亿房企祥生集团约2.03亿元债权被拍卖,起拍价8000万元
  • 国防部:菲方应停止一切侵权挑衅危险举动,否则只会自食苦果
  • 押井守在30年前创造的虚拟世界何以比当下更超前?
  • 经济日报评外卖平台被约谈:行业竞争不能背离服务本质