2336. 无限集中的最小数字 : 容易又高效的分类做法

题目描述

这是 LeetCode 上的 2336. 无限集中的最小数字 ,难度为 中等

Tag : 「优先队列(堆)」、「哈希表」

现有一个包含所有正整数的集合 <math xmlns="http://www.w3.org/1998/Math/MathML"> [ 1 , 2 , 3 , 4 , 5 , . . . ] [1, 2, 3, 4, 5, ...] </math>[1,2,3,4,5,...] 。

实现 SmallestInfiniteSet 类:

  • SmallestInfiniteSet() 初始化 SmallestInfiniteSet 对象以包含所有正整数。
  • int popSmallest() 移除并返回该无限集中的最小整数。
  • void addBack(int num) 如果正整数 num 不存在于无限集中,则将一个 num 添加到该无限集中。

示例:

scss 复制代码
输入
["SmallestInfiniteSet", "addBack", "popSmallest", "popSmallest", "popSmallest", "addBack", "popSmallest", "popSmallest", "popSmallest"]
[[], [2], [], [], [], [1], [], [], []]

输出
[null, null, 1, 2, 3, null, 1, 4, 5]

解释
SmallestInfiniteSet smallestInfiniteSet = new SmallestInfiniteSet();
smallestInfiniteSet.addBack(2);    // 2 已经在集合中,所以不做任何变更。
smallestInfiniteSet.popSmallest(); // 返回 1 ,因为 1 是最小的整数,并将其从集合中移除。
smallestInfiniteSet.popSmallest(); // 返回 2 ,并将其从集合中移除。
smallestInfiniteSet.popSmallest(); // 返回 3 ,并将其从集合中移除。
smallestInfiniteSet.addBack(1);    // 将 1 添加到该集合中。
smallestInfiniteSet.popSmallest(); // 返回 1 ,因为 1 在上一步中被添加到集合中,
                                   // 且 1 是最小的整数,并将其从集合中移除。
smallestInfiniteSet.popSmallest(); // 返回 4 ,并将其从集合中移除。
smallestInfiniteSet.popSmallest(); // 返回 5 ,并将其从集合中移除。

提示:

  • <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 < = n u m < = 1000 1 <= num <= 1000 </math>1<=num<=1000
  • 最多调用 popSmallestaddBack 方法 共计 <math xmlns="http://www.w3.org/1998/Math/MathML"> 1000 1000 </math>1000 次

优先队列(小根堆)+ 哈希表

使用 idx 代表顺序弹出的集合左边界, <math xmlns="http://www.w3.org/1998/Math/MathML"> [ i d x , + ∞ ] [idx, +\infty] </math>[idx,+∞] 范围内的数均为待弹出,起始有 <math xmlns="http://www.w3.org/1998/Math/MathML"> i d x = 1 idx = 1 </math>idx=1。

考虑当调用 addBack 往集合添加数值 x 时,该如何处理:

  • <math xmlns="http://www.w3.org/1998/Math/MathML"> x ≥ i d x x \geq idx </math>x≥idx:数值本身就存在于集合中,忽略该添加操作;
  • <math xmlns="http://www.w3.org/1998/Math/MathML"> x = i d x − 1 x = idx - 1 </math>x=idx−1:数值刚好位于边界左侧,更新 <math xmlns="http://www.w3.org/1998/Math/MathML"> i d x = i d x − 1 idx = idx - 1 </math>idx=idx−1;
  • <math xmlns="http://www.w3.org/1998/Math/MathML"> x < i d x − 1 x < idx - 1 </math>x<idx−1:考虑将数值添加到某个容器中,该容器支持返回最小值,容易联想到"小根堆";但小根堆并没有"去重"功能,为防止重复弹出,还需额外使用"哈希表"来记录哪些元素在堆中。

该做法本质上将集合分成两类:一类是从 idx 到正无穷的连续段,对此类操作的复杂度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( 1 ) O(1) </math>O(1);一类是比 idx 要小的离散类数集,对该类操作复杂度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( log ⁡ n ) O(\log{n}) </math>O(logn),其中 <math xmlns="http://www.w3.org/1998/Math/MathML"> n n </math>n 为调用 addBack 的最大次数。

Java 代码:

Java 复制代码
class SmallestInfiniteSet {
    boolean[] vis = new boolean[1010];
    PriorityQueue<Integer> q = new PriorityQueue<>((a,b)->a-b);
    int idx = 1;
    public int popSmallest() {
        int ans = -1;
        if (!q.isEmpty()) {
            ans = q.poll();
            vis[ans] = false;
        } else {
            ans = idx++;
        }
        return ans;
    }
    public void addBack(int x) {
        if (x >= idx || vis[x]) return ;
        if (x == idx - 1) {
            idx--;
        } else {
            q.add(x);
            vis[x] = true;
        }
    }
}

C++ 代码:

C++ 复制代码
class SmallestInfiniteSet {
public:
    vector<bool> vis;
    priority_queue<int, vector<int>, greater<int>> q;
    int idx;
    SmallestInfiniteSet() : idx(1) {
        vis.resize(1010, false);
    }
    int popSmallest() {
        int ans = -1;
        if (!q.empty()) {
            ans = q.top();
            q.pop();
            vis[ans] = false;
        } else {
            ans = idx++;
        }
        return ans;
    }
    void addBack(int x) {
        if (x >= idx || vis[x]) return;
        if (x == idx - 1) {
            idx--;
        } else {
            q.push(x);
            vis[x] = true;
        }
    }
};

Python 代码:

Python 复制代码
class SmallestInfiniteSet:
    def __init__(self):
        self.vis = [False] * 1010
        self.q = []
        self.idx = 1

    def popSmallest(self):
        ans = -1
        if self.q:
            ans = heapq.heappop(self.q)
            self.vis[ans] = False
        else:
            ans = self.idx
            self.idx += 1
        return ans

    def addBack(self, x):
        if x >= self.idx or self.vis[x]:
            return
        if x == self.idx - 1:
            self.idx -= 1
        else:
            heapq.heappush(self.q, x)
            self.vis[x] = True
  • 时间复杂度:插入和取出的最坏复杂度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( log ⁡ n ) O(\log{n}) </math>O(logn)
  • 空间复杂度: <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n ) O(n) </math>O(n)

最后

这是我们「刷穿 LeetCode」系列文章的第 No.2336 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:github.com/SharingSour...

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。

更多更全更热门的「笔试/面试」相关资料可访问排版精美的 合集新基地 🎉🎉

相关推荐
金融小师妹2 小时前
应用BERT-GCN跨模态情绪分析:贸易缓和与金价波动的AI归因
大数据·人工智能·算法
广州智造2 小时前
OptiStruct实例:3D实体转子分析
数据库·人工智能·算法·机器学习·数学建模·3d·性能优化
热河暖男3 小时前
【实战解决方案】Spring Boot+Redisson构建高并发Excel导出服务,彻底解决系统阻塞难题
spring boot·后端·excel
Trent19854 小时前
影楼精修-肤色统一算法解析
图像处理·人工智能·算法·计算机视觉
feifeigo1234 小时前
高光谱遥感图像处理之数据分类的fcm算法
图像处理·算法·分类
北上ing5 小时前
算法练习:19.JZ29 顺时针打印矩阵
算法·leetcode·矩阵
水银嘻嘻5 小时前
12 web 自动化之基于关键字+数据驱动-反射自动化框架搭建
运维·前端·自动化
小嘟嚷ovo5 小时前
h5,原生html,echarts关系网实现
前端·html·echarts
十一吖i6 小时前
Vue3项目使用ElDrawer后select方法不生效
前端
只可远观6 小时前
Flutter目录结构介绍、入口、Widget、Center组件、Text组件、MaterialApp组件、Scaffold组件
前端·flutter