算法学习记录08——并归的应用(LeetCode[315])

前文算法学习记录07------归并排序的实现与优化

并归的应用(LeetCode315)

给你一个整数数组 nums ,按要求返回一个新数组 counts 。数组 counts 有该性质: counts[i] 的值是 nums[i] 右侧小于 nums[i] 的元素的数量。

归并排序思想的核心思路

归并排序的过程是将数组不断二分,排序后合并。在合并过程中,我们可以同步统计 "右侧小于当前元素的数量",原因如下:

  • 归并排序处理的是两个已排序的子数组(左半部分 left 和右半部分 right)。
  • 当合并时,若左半部分的元素 lefti 大于右半部分的元素 rightj,则 rightj 及 right 中 j 之后的所有元素都小于 lefti(因为 right 已排序),因此可直接统计数量。

举例说明(实在不懂,自己画个图,很好懂的)

假设我们有两个已排序的子数组:​

  • 左子数组 left = (5, 0), (6, 2)(元素值为 5、6,原始索引为 0、2)
  • 右子数组 right = (2, 1), (1, 3)(元素值为 2、1,原始索引为 1、3)
    注意:这里的右子数组在实际归并过程中已经过排序,正确的有序右子数组应为 right = (1, 3), (2, 1)(按值从小到大排列)。

合并过程分析:​

  • 初始化指针 i=0(指向 left0 = (5, 0)),j=0(指向 right0 = (1, 3))。
    • 比较 left0.值=5 和 right0.值=1:5 > 1。
    • 由于 right 是有序的(1, 2),rightj=0 之后的所有元素(即 2)也都小于 5。
    • 因此,right 中小于 5 的元素数量为 len(right) - j = 2 - 0 = 2(元素 1 和 2)。
    • 所以 counts[0] += 2(原始索引 0 对应的元素 5,右侧有 2 个更小的元素)。
  • 移动左指针 i=1(指向 left1 = (6, 2)),j 仍为 0。
    • 比较 left1.值=6 和 right0.值=1:6 > 1。
    • rightj=0 之后的元素(2)仍小于 6,数量为 2 - 0 = 2。
    • 因此 counts2 += 2(原始索引 2 对应的元素 6,右侧暂时统计到 2 个更小的元素)。
  • 移动左指针 i 超出左数组长度,将右数组剩余元素加入结果,合并结束。

代码实现

python 复制代码
class Solution(object):
    def countSmaller(self, nums):
        """
        :type nums: List[int]
        :rtype: List[int]
        """
        n = len(nums)
        temp = [0] * n
        ##添加的内容#########################################################
        for i, num in enumerate(nums):
            nums[i] = (i, num)
        res = [0] * n
        ####################################################################
        def merge(l, mid, h):
            """合并 nums[l..mid-1] 和 nums[mid..h-1]"""
            for i in range(l, h):
                temp[i] = nums[i]

            i, j = l, mid
            p = l

            while i < mid and j < h:
                if temp[i][1] <= temp[j][1]:
                    nums[p] = temp[i]
                    ##添加的内容#################################################
                    res[temp[i][0]] += j - mid
                    ###########################################################
                    i += 1
                else:
                    nums[p] = temp[j]
                    j += 1
                p += 1
            while i < mid:
                nums[p] = temp[i]
                ##添加的内容##################################################
                res[temp[i][0]] += j - mid
                #############################################################
                i += 1
                p += 1
            while j < h:
                nums[p] = temp[j]
                j += 1
                p += 1

        def sort(l, h):
            """递归排序 nums[l..h-1]"""
            if h - l <= 1:
                return
            mid = l + (h - l) // 2
            sort(l, mid)
            sort(mid, h)

            if nums[mid - 1][1] <= nums[mid][1]:
                return

            merge(l, mid, h)

        sort(0, n)
        return res # 修改

总结

一、为什么归并排序能解决 "右侧小于当前元素的数量" 问题?

核心原因是归并排序的分治过程天然具备 "比较左右两部分元素" 的特性,可以在排序的同时统计右侧小元素的数量。

  • 归并排序将数组分成左右两部分,递归排序后合并。
  • 合并时,当左半部分的元素 numsi 大于右半部分的元素 numsj 时,说明右半部分中从 j 到末尾的所有元素都小于 numsi(因为右半部分已排序)。
  • 因此,可直接累加这些元素的数量到 countsi 中,实现 "一边排序一边统计"。

二、什么样的题型适合用归并排序解决?

归并排序的核心价值在于分治过程中对 "左右两部分有序子数组" 的比较和合并,因此适合以下场景:

  • 涉及 "逆序对" 的问题如:统计逆序对数量、求右侧小于当前元素的数量、数组中的第 K 个最大逆序对数目等。归并排序在合并时能高效统计逆序关系。
  • 需要在排序过程中附加统计 / 计算如:合并两个有序数组时记录某些条件的出现次数,或在分治中同步处理子问题的结果(如统计区间内的特定关系)。
  • 要求 O (n log n) 时间复杂度,且问题可分解为子问题:归并排序的分治思想适合将问题拆解为规模更小的子问题,且子问题的解可合并为原问题的解。
相关推荐
小雉系统2 小时前
BcoreOS安全通信架构:FsSession 抗算法后门安全设计
算法·安全·架构
§ꦿCFོ༉3 小时前
嵌入式八股文—C语言入门学习(四)
c语言·开发语言·学习
2603_969694855 小时前
【JavaWeb学习记录】MyBatis核心知识点与实战笔记:从入门到动态SQL
笔记·sql·学习·mysql·mybatis·javaweb·学习方法
唐墨1236 小时前
linux kernel 比较数据大小的宏定义
linux·算法
拂拉氏7 小时前
【知识讲解-题目讲解】 统计满足K约束的子字符串数量2 (滑动窗口+前缀和+二分查找+分析)
算法·前缀和·二分查找·滑动窗口
北京云帆互联科技11 小时前
云帆培训考试系统更新说明:v9.3.0
java·学习
通信小呆呆12 小时前
电子战抗干扰技术:当“不被干扰”成为一种系统
算法·通信·mimo·isac
笑容掩饰我的失落12 小时前
区块链从小白到精通(八):从零实现一个标准 ERC-20 代币(完整可运行代码)
算法·区块链·php·智能合约·哈希算法
Severus_black12 小时前
【C++初阶】Stack、Queue算法题大练兵!
c语言·c++·算法·stl