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;
}

运行用时

第二次

思路

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

编写用时

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;            // 该区间的(左端点数值 + 随机数) 即为最终答案
    }
};
运行用时

结尾

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

                                        **开心code每一天**
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值