分治(库存管理|||)(4)

https://blog.csdn.net/2601_95366422/article/details/159013900

上节课链接

一.题目

LCR 159. 库存管理 III - 力扣(LeetCode)

二.思路讲解

2.1 思路讲解

本题同样是TopK问题 ,只不过要求的是最小的 cnt 个元素 ,而不是最大的。因此,我们可以沿用上一章学习的快速选择算法 ,但需要将比较逻辑调整为升序划分 。快速选择的核心思想是:通过一次三路划分 ,将数组分为小于基准等于基准大于基准 三个区域,然后根据 cnt 落在哪个区域,只递归处理那个区域 ,从而在期望 O(n) 时间内找到第 cnt 小的元素,并使得前 cnt 个位置恰好包含所有最小的 cnt 个元素(顺序不限)。

具体来说,在每一次递归中,随机选择一个基准值 key,然后使用三个指针将当前区间划分为:

  • 左区间 [l, left]:所有小于 key 的元素

  • 中区间 [left+1, right-1]:所有等于 key 的元素

  • 右区间 [right, r]:所有大于 key 的元素

接下来,计算左区间长度 a 和等于区间长度 b。根据 cnt 与 aa+b 的关系:

  • 如果 cnt ≤ a,说明第 cnt 小的元素在左区间,递归处理左区间,cnt 不变

  • 如果 a < cnt ≤ a+b,说明第 cnt 小的元素就是基准值 key,此时最小的 cnt 个元素已经确定------它们由左区间全部元素和等于区间的前 cnt-a 个元素组成,可以直接返回(由于顺序不限,我们只需确保前 cnt 个位置是这些元素即可)。

  • 如果 cnt > a+b,说明第 cnt 小的元素在右区间,递归处理右区间,但需要更新 cnt = cnt - a - b

通过这种分而治之 的策略,每次递归只处理一个子区间,平均时间复杂度为 O(n)。由于我们只关心最小的 cnt 个元素,而不需要整体排序。

三.代码演示

cpp 复制代码
class Solution {
public:
    vector<int> inventoryManagement(vector<int>& stock, int cnt)
    {

        int n = stock.size();
        srand(time(NULL));
        qsort(stock,0,n-1,cnt);
        return {stock.begin(),stock.begin()+cnt};

    }
    void qsort(vector<int>& stock,int l,int r,int cnt)
    {
        if(l >= r) return;
        //三路划分
        int key = getRandom(stock,l,r);
        int i = l,left = l - 1,right = r + 1;
        while(i < right)
        {
            if(stock[i] < key)
                swap(stock[++left],stock[i++]);

            else if(stock[i] == key)
                i++;

            else
                swap(stock[--right],stock[i]);
        }
        int leftlen = left - l + 1;//左区间长度
        int midlen = right - left - 1;//中间长度
        if(cnt <= leftlen)
        {
            qsort(stock,l,left,cnt);
        }
        else if(cnt <= leftlen + midlen)
        {
            return;
        }
        else
        {
            qsort(stock,right,r,cnt -leftlen - midlen);
        }
    }
    int getRandom(vector<int>& stock,int l,int r)
    {
        int p = rand();
        return stock[p % (r - l + 1) + l];
    }
};

四.代码讲解

一、随机基准值的选择

为了避免快速选择算法在特定输入下退化为 O(n²) ,我们采用随机化策略 。在 getRandom 函数中,通过 rand() % (r - l + 1) + l 生成一个位于当前区间 [l, r] 内的随机下标,并将该位置的元素作为基准值 key。这样,每次划分的基准都是随机的,使得算法在期望时间复杂度上达到 O(n) 。在排序前调用 srand(time(NULL)) 设置随机种子,确保每次运行产生的随机数不同。

二、三路升序划分

为了高效处理重复元素,我们使用三路划分 ,将数组划分为小于基准等于基准大于基准三个区域(即升序排列)。定义三个指针:

  • left :初始为 l - 1,指向小于区域的最后一个元素(即小于区域为空)。

  • right :初始为 r + 1,指向大于区域的第一个元素(即大于区域为空)。

  • i :初始为 l,用于遍历当前元素。

循环条件为 i < right,遍历过程中根据 stock[i]key 的关系分三种情况:

  1. stock[i] < key :该元素应放入小于区域 。先将 left 右移一位(++left),然后交换 stock[left]stock[i],接着 i++。此时 left 指向新放入的小于元素,而 i 继续向后。

  2. stock[i] == key :该元素属于等于区域 ,无需移动,直接 i++

  3. stock[i] > key :该元素应放入大于区域 。先将 right 左移一位(--right),然后交换 stock[right]stock[i]。注意,此时 i 不能增加 ,因为交换过来的元素来自 right 位置,尚未被处理,需要在下一次循环中重新判断。

循环结束后,数组被划分为:

  • [l, left] :全部小于 key 的元素

  • [left+1, right-1] :全部等于 key 的元素

  • [right, r] :全部大于 key 的元素

三、根据区域大小确定最小 cnt 个元素的所在区间

由于我们要找的是最小的 cnt 个元素,而当前数组是升序划分,因此小于区域 在前,等于区域 居中,大于区域在后。我们需要计算各个区域的长度:

  • 左区间(小于区域)长度leftlen = left - l + 1

  • 中间区间(等于区域)长度midlen = right - left - 1

接下来,根据 cnt 与这些长度的关系,决定下一步操作:

  • 如果 cnt <= leftlen :说明最小的 cnt 个元素全部在小于区域 中,递归处理左区间 [l, left],且 cnt 保持不变

  • 如果 leftlen < cnt <= leftlen + midlen :说明最小的 cnt 个元素由整个小于区域和等于区域的一部分组成,此时基准值 key 就是第 cnt 小的元素,且前 cnt 个位置已经包含了所有需要的元素(因为等于区域的部分元素就在中间),因此无需继续递归,直接返回即可。

  • 如果 cnt > leftlen + midlen :说明最小的 cnt 个元素中还包含了大于区域中的一部分,需要递归处理右区间 [right, r],但此时需要更新 cnt ,减去前面两个区域的长度,即新的 cnt = cnt - leftlen - midlen

四、递归处理对应区间

根据上述判断,只对包含目标元素的区间进行递归,其他区间被舍弃。这样每次递归的规模平均减半,保证了线性期望时间复杂度。

五、返回结果

inventoryManagement 函数中,我们调用 qsort 对整个数组进行快速选择。当递归结束时,数组的前 cnt 个元素(即下标 0 到 cnt-1)就是所有最小的 cnt 个元素(顺序不限)。因此,我们直接返回 {stock.begin(), stock.begin()+cnt} 即可。

六、关键细节
  • 随机化:随机选择基准值是避免最坏情况的关键,使得算法在概率上高效。

  • 三路划分:将等于基准值的元素集中在一起,避免了重复元素带来的冗余递归,同时简化了区间判断。

  • 升序划分:代码采用小于在左、大于在右的方式,直接对应最小元素的概念。

  • cnt 的更新:当递归进入大于区域时,需要减去前面区域的大小,因为最小元素的位置在全局中发生了变化。

  • 递归终止:当 cnt 落在等于区域时直接返回,无需进一步递归,这是快速选择比快排更高效的原因之一。

相关推荐
青稞社区.3 小时前
ICLR‘26 Oral | 当 LLM Agent 在多轮推理中迷失时:T3 如何让强化学习重新学会主动推理
人工智能·算法·agi
春花秋月夏海冬雪3 小时前
代码随想录刷题 - 贪心Part1
java·算法·贪心·代码随想录
环黄金线HHJX.3 小时前
Tuan符号系统重塑智能开发
开发语言·人工智能·算法·编辑器
Tanecious.3 小时前
蓝桥杯备赛:Day3-P1102 A-B 数对
c++·蓝桥杯
汀、人工智能4 小时前
[特殊字符] 第2课:字母异位词分组
数据结构·算法·链表·数据库架构··字母异位词分组
Tanecious.4 小时前
蓝桥杯备赛:Day3-P1918 保龄球
c++·蓝桥杯
良木生香4 小时前
【C++初阶】:C++类和对象(下):构造函数promax & 类型转换 & static & 友元 & 内部类 & 匿名对象 & 超级优化
c语言·开发语言·c++
小O的算法实验室4 小时前
2026年SEVC,面向主动成像卫星任务规划问题的群体智能与动态规划混合框架,深度解析+性能实测
算法·论文复现·智能算法·智能算法改进
网安INF4 小时前
数据结构第一章复习:基本概念与算法复杂度分析
数据结构·算法