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

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

并归的应用(LeetCode[315])

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

归并排序思想的核心思路

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

  • 归并排序处理的是两个已排序的子数组(左半部分 left 和右半部分 right)。
  • 当合并时,若左半部分的元素 left[i] 大于右半部分的元素 right[j],则 right[j] 及 right 中 j 之后的所有元素都小于 left[i](因为 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(指向 left[0] = (5, 0)),j=0(指向 right[0] = (1, 3))。
    • 比较 left[0].值=5 和 right[0].值=1:5 > 1。
    • 由于 right 是有序的([1, 2]),right[j=0] 之后的所有元素(即 [2])也都小于 5。
    • 因此,right 中小于 5 的元素数量为 len(right) - j = 2 - 0 = 2(元素 1 和 2)。
    • 所以 counts[0] += 2(原始索引 0 对应的元素 5,右侧有 2 个更小的元素)。
  • 移动左指针 i=1(指向 left[1] = (6, 2)),j 仍为 0。
    • 比较 left[1].值=6 和 right[0].值=1:6 > 1。
    • right[j=0] 之后的元素([2])仍小于 6,数量为 2 - 0 = 2。
    • 因此 counts[2] += 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 # 修改

总结

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

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

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

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

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

  • 涉及 "逆序对" 的问题如:统计逆序对数量、求右侧小于当前元素的数量、数组中的第 K 个最大逆序对数目等。归并排序在合并时能高效统计逆序关系。
  • 需要在排序过程中附加统计 / 计算如:合并两个有序数组时记录某些条件的出现次数,或在分治中同步处理子问题的结果(如统计区间内的特定关系)。
  • 要求 O (n log n) 时间复杂度,且问题可分解为子问题:归并排序的分治思想适合将问题拆解为规模更小的子问题,且子问题的解可合并为原问题的解。
相关推荐
jllllyuz8 分钟前
基于粒子群优化(PSO)的特征选择与支持向量机(SVM)分类
开发语言·算法·matlab
因为奋斗超太帅啦36 分钟前
Git分布式版本控制工具学习笔记(一)——git本地仓库的基本使用
笔记·git·学习
啊吧怪不啊吧43 分钟前
贪心算法(局部最优实现全局最优)第一篇
算法·贪心算法
Jeled43 分钟前
RecyclerView ViewHolder 复用机制详解(含常见错乱问题与优化方案)
android·学习·面试·kotlin
Yue丶越1 小时前
【C语言】深入理解指针(四)
java·c语言·算法
可可苏饼干1 小时前
LVS服务器
linux·运维·笔记·学习·lvs
四谎真好看1 小时前
Java 黑马程序员学习笔记(进阶篇27)
java·开发语言·笔记·学习·学习笔记
Abona1 小时前
自动驾驶、无人机、机器人核心技术双范式
算法·机器人·自动驾驶·无人机
草莓熊Lotso1 小时前
《算法闯关指南:优选算法--模拟》--39.替换所有问号,40.提莫攻击
开发语言·c++·算法·模拟
草莓熊Lotso2 小时前
C++ STL set 系列完全指南:从底层原理、核心接口到实战场景
开发语言·c++·人工智能·经验分享·网络协议·算法·dubbo