• LeetCode710. 黑名单中的随机数.Random Pick with Blacklist [hash映射][前缀和][二分]


    By Jalan

    知识工具需求

    数据结构和算法

    • 前缀和
    • 二分
    • hash映射

    题解

    第一次

    思路

    hash映射
    设blackLength为黑名单长度。
    我们把n分成两部分,前n - blackLength部分是白名单,后blackLength部分是黑名单。
    然后我们将前半部分的黑名单映射到后半部分的白名单即可。

    1. 标记本来就在黑名单区域中的黑名单元素
    2. 将在白名单区域中的黑名单元素映射到黑名单区域中的白名单元素上
    3. 随机白名单区域,假如没有映射则说明是白名单元素,直接输出,有映射则说明是黑名单元素
      在这里插入图片描述

    编写用时

    15分钟

    代码

    CPP

    #include <bits/stdc++.h>
    #include <cstdlib>
    #include <unordered_map>
    #include <vector>
    using namespace std;
    class Solution
    {
    public:
        int                       whiteListSize;
        unordered_map< int, int > hashMap;
        Solution( int n, vector< int >&& blacklist )
        {
    
            int blackListSize = blacklist.size();
            whiteListSize     = n - blackListSize;
            int hashIndex     = n - blackListSize - 1;
            //1  标记本来就在黑名单区域中的黑名单元素
            for ( auto iter : blacklist ) {
                if ( iter > whiteListSize - 1 ) {
                    hashMap[ iter ] = -1;
                }
            }
            //2  将在白名单区域中的黑名单元素映射到黑名单区域中的白名单元素上
            for ( auto iter : blacklist ) {
                if ( iter <= whiteListSize - 1 ) {
                    ++hashIndex;
                    while ( hashMap[ hashIndex ] == -1 ) {
                        ++hashIndex;
                    }
                    hashMap[ iter ] = hashIndex;
                }
            }
        }
    
        int pick()
        {
            //3  随机白名单区域,假如没有映射则说明是白名单元素,直接输出,有映射则说明是黑名单元素
            int random = rand() % ( whiteListSize );
            if ( hashMap.count( random ) ) {
                return hashMap[ random ];
            } else {
                return random;
            }
        }
    };
    /**
     * Your Solution object will be instantiated and called as such:
     * Solution* obj = new Solution(n, blacklist);
     * int param_1 = obj->pick();
     */
    int main( int argc, const char** argv )
    {
        Solution s( 1000000000, vector< int >{ 640145908 } );
        for ( int i = 0; i < 10; i++ ) {
            std::cout << s.pick() << std::endl;
        }
    
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    运行用时

    第二次

    思路

    第一种方法时空效率不是很高.
    (其实应该不错啊)

    编写用时

    20分钟,这个思路借鉴了 LeetCode:京城打工人 感谢

    代码

    CPP

    class Solution
    {
    public:
        using T = pair< int, int >;  //左右端点[l,r] 从0开始
        vector< T >   m;             // 存放各个区间
        vector< int > s;             // 存放前缀和, 即m各个区间长度的前缀和
        Solution( int n, vector< int >&& blacklist )
        {
            sort( blacklist.begin(), blacklist.end() );  // 首先对黑名单区间排序
            int st = 0;                                  // st表示一个新区间的左端点, 默认为0
            s.push_back( 0 );
            for ( auto v : blacklist ) {
                if ( v == st )  // 若该区间的待加入区间的左端点是黑名单的数则++
                {
                    ++st;
                    continue;
                }
                m.push_back( { st, v - 1 } );      // 加入一个新区间
                s.push_back( s.back() + v - st );  // (v - 1) - st + 1 表示该新区间的长度 要表示的是[st,v-1]的长度,所以(v - 1) - st还需要+1
                st = v + 1;                        //下一段的开始从本次的上界的右侧开始找.
            }
    
            // 最后一个区间的加入 st ~ n - 1, 但要注意判断是否可加入
            if ( st != n ) {
                m.push_back( { st, n - 1 } );
                s.push_back( s.back() + n - st );
            }
        }
    
        int pick()
        {
            int N = s.back();
            int t = rand() % N + 1;  // 总线段得到一个随机数, 因为区间长度至少为1, 所以需要+1, 从1开始
    
            // 二分查找, 找 t 是落在那个区间上的
            int l = 1, r = s.size() - 1;
            while ( r > l ) {
                int mid = ( l + r ) >> 1;
                if ( s[ mid ] >= t )
                    r = mid;
                else
                    l = mid + 1;
            }
            auto iter = m[ r - 1 ];                    // 注意 r - 1, 因为m的区间是从0下标开始存储的
            int  n    = iter.second - iter.first + 1;  // 得到本次随机到区间的长度
            return iter.first + rand() % n;            // 该区间的(左端点数值 + 随机数) 即为最终答案
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    运行用时

    结尾

    看在我写了这么多注释的份上可以给我点个赞嘛,求求惹=]砰砰砰,给我加点写下去的油呀
    @.@
    也欢迎关注我的CSDN账号呀=]

                                            **开心code每一天**
    
    • 1
  • 相关阅读:
    Java程序员必学知识:高并发+微服务+数据结构+Mybatis实战实践
    Java分支语句
    面试:Android广播相关
    【数据结构与算法 | 堆篇】力扣215
    docker 命令 查看启动进程及日志
    linux中如何查看mac地址
    java计算机毕业设计航空售票系统MyBatis+系统+LW文档+源码+调试部署
    国外客户发开发信怎么发?写外贸邮件方法?
    SVD求解两个点集之间的刚体运动,即旋转矩阵和平移向量。
    听GPT 讲Rust源代码--library/core/src(7)
  • 原文地址:https://blog.csdn.net/weixin_43950087/article/details/125471409