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 原题链接和其他优选题解。

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

相关推荐
吃土少女古拉拉4 分钟前
前端和后端
前端·学习笔记
Sunyanhui110 分钟前
力扣 三数之和-15
数据结构·算法·leetcode
Mr.kanglong10 分钟前
【LeetCode热题100】队列+宽搜
算法·leetcode·职场和发展
sp_fyf_202411 分钟前
计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-11-05
人工智能·深度学习·神经网络·算法·机器学习·语言模型·数据挖掘
钰见梵星23 分钟前
深度学习优化算法
人工智能·深度学习·算法
尘浮生24 分钟前
Java项目实战II基于SpringBoot的共享单车管理系统开发文档+数据库+源码)
java·开发语言·数据库·spring boot·后端·微信小程序·小程序
huaxiaorong28 分钟前
如何将旧的Android手机改造为家用服务器
后端
2401_8574396929 分钟前
社团管理新工具:SpringBoot框架
java·spring boot·后端
2401_8576100330 分钟前
Spring Boot OA:企业办公自动化的创新之路
spring boot·后端·mfc
zzzhpzhpzzz1 小时前
设计模式——观察者模式
算法·观察者模式·设计模式