参考:
问题总览
一、洗牌算法
这道题的价值是:如何确保打乱的算法是随机性的?
只要确保总的选择数为n!就可以。
class Solution {
int[] shuffed;
int[] source;
Random random;
public Solution(int[] nums) {
int n = nums.length;
shuffed = new int[n];
source = new int[n];
random = new Random();
for (int i = 0; i < n; i++) {
source[i] = nums[i];
}
}
public int[] reset() {
return source;
}
public int[] shuffle() {
int n = source.length;
for (int i = 0; i < n; i++) {
shuffed[i] = source[i];
}
for (int i = 0; i < n; i++) {
int r = i + random.nextInt(n - i);
// 这边必须使用swap,不能用替换,注意r是随机的,是有可能重复的
// 如果每次从source数组中取一个数,那么可能会重复选择,所以只能用swap
swap(shuffed, i, r);
}
return shuffed;
}
public void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
二、水塘抽样算法
考察的是在一个长度未知的链表(数组)随机读取元素。
class Solution {
ListNode head;
Random r = new Random();
public Solution(ListNode head) {
this.head = head;
}
/* 返回链表中一个随机节点的值 */
int getRandom() {
ListNode p = head;
int i = 0;
int res = 0;
while (p != null) {
i++;
// [0,i)中随机选择一个元素等于0的概率为1/i,比如遍历到第三个元素,i==3,此时选项有0,1,2
// 选到0的概率就是1/3
if (r.nextInt(i) == 0) {
res = p.val;
}
p = p.next;
}
// 如果一直没有选中,返回第一个元素
return res;
}
}
// 用hashmap+洗牌算法解
class Solution {
Map<Integer, List<Integer>> elements;
Random random = new Random();
public Solution(int[] nums) {
elements = new HashMap<>();
int len = nums.length;
for (int i = 0; i < len; i++) {
List<Integer> values = elements.getOrDefault(nums[i], new ArrayList<Integer>());
values.add(i);
elements.put(nums[i], values);
}
}
public int pick(int target) {
List<Integer> values = elements.get(target);
if (values.size() == 1) {
return values.get(0);
}
int r = random.nextInt(0, values.size());
return values.get(r);
}
}
// 用水塘抽样算法写
class Solution {
int[] nums;
Random random = new Random();
public Solution(int[] nums) {
this.nums = nums;
this.random = new Random();
}
public int pick(int target) {
int count = 0;
int res = -1;
for (int i = 0; i < nums.length; i++) {
// 不等于target的处理
if (nums[i] != target) {
continue;
}
// 等于target的处理
count++;
if (random.nextInt(count) == 0) {
res = i;
}
}
return res;
}
}
三、带权重的随机选择
用前缀和把权重铺平。
比如:[3,1,2,4]
得到:[3,4,6,10]
| 可以选择到的数 | 对应的索引 | 概率 |
|---|---|---|
| 1,2,3 | 0 | 3/10 |
| 4 | 1 | 1/10 |
| 5,6 | 2 | 2/10 |
| 7,8,9,10 | 3 | 4/10 |
class Solution {
int[] preSums;
int sum = 0;
Random random;
public Solution(int[] w) {
int n = w.length;
preSums = new int[n];
random = new Random();
for (int i = 0; i < n; i++) {
sum += w[i];
preSums[i] = sum;
}
}
public int pickIndex() {
return find(preSums, random.nextInt(sum) + 1);
}
public int find(int[] nums, int target) {
if (nums.length == 0) return -1;
int left = 0, right = nums.length;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
right = mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid;
}
}
return left;
}
}
四、对随机元素的操作
class RandomizedSet {
Map<Integer, Integer> map;
LinkedList<Integer> elements;
Random random;
public RandomizedSet() {
map = new HashMap<>();
elements = new LinkedList<>();
random = new Random();
}
// 不存在时插入,返回true
// 存在时,返回false
public boolean insert(int val) {
if (map.containsKey(val)) {
return false;
}
// 插入到末尾
elements.addLast(val);
map.put(val, elements.size() - 1);
return true;
}
// 存在时删除,并返回true
// 不存在时,返回false
public boolean remove(int val) {
if (!map.containsKey(val)) {
return false;
}
// 获取元素当前的位置
int idx = map.get(val);
int idx2 = elements.size() - 1;
int val2 = elements.get(idx2);
// 把原先末尾的值的索引移动到被删除的元素上来
Collections.swap(elements, idx, idx2);
map.put(val2, idx);
// 删除末尾元素
map.remove(val);
elements.removeLast();
return true;
}
// 从现有集合中随机返回一个元素
public int getRandom() {
return elements.get(random.nextInt(elements.size()));
}
}
381. O(1) 时间插入、删除和获取随机元素 - 允许重复
class RandomizedCollection {
// 用Set代替List
Map<Integer, Set<Integer>> map;
LinkedList<Integer> elements;
Random random;
public RandomizedCollection() {
map = new HashMap<>();
elements = new LinkedList<>();
random = new Random();
}
public boolean insert(int val) {
// 将每一个元素的位置都保存到哈希表里
Set<Integer> res = map.getOrDefault(val, new HashSet<>());
// 每次都插入队尾,所以直接用element.size()
res.add(elements.size());
map.put(val, res);
// 插入队尾
elements.addLast(val);
return res.size() == 1;
}
public boolean remove(int val) {
// 不存在,则返回false
if (!map.containsKey(val)) {
return false;
}
// 存在
Set<Integer> res = map.getOrDefault(val, new HashSet<>());
// 随机取一个元素的索引,为什么要取第一条,是为了保证O(1)
int idx = res.iterator().next();
int idx2 = elements.size() - 1;
// 取链表的最后一个元素来替换
int val2 = elements.get(idx2);
Collections.swap(elements, idx, idx2);
elements.removeLast();
// 删除元素
res.remove(idx);
if (res.size() == 0) {
// 考虑被替换的元素只有一个
map.remove(val);
} else {
map.put(val, res);
}
// 更新被替换的元素的索引
Set<Integer> res2 = map.getOrDefault(val2, new HashSet<>());
if (res2.size() > 0) {
res2.remove(idx2);
res2.add(idx);
map.put(val2, res2);
}
return true;
}
public int getRandom() {
return elements.get(random.nextInt(elements.size()));
}
}
五、带黑名单的随机数
class Solution {
int len, m, n;
Map<Integer, Integer> mapping;
Random random;
public Solution(int m, int n) {
len = m * n;
this.m = m;
this.n = n;
mapping = new HashMap<>();
random = new Random();
}
public int[] flip() {
int rand = random.nextInt(len);
int res = rand;
if (mapping.containsKey(rand)) {
// 已经被选过了,改成另一个数
res = mapping.get(rand);
}
int last = len - 1;
if (mapping.containsKey(last)) {
// 已经被选过了,改成另一个数
last = mapping.get(last);
}
mapping.put(rand, last);
len--;
//want = y + x * n
return new int[]{res / n, res % n};
}
public void reset() {
len = m * n;
mapping.clear();
}
}
class Solution {
int sz;
Map<Integer, Integer> mapping;
public Solution(int N, int[] blacklist) {
sz = N - blacklist.length;
mapping = new HashMap<>();
for (int b : blacklist) {
mapping.put(b, 666); // 标记黑名单
}
int last = N - 1;
for (int b : blacklist) {
// 如果 b 已经在区间 [sz, N),可以直接忽略
if (b >= sz) {
continue;
}
while (mapping.containsKey(last)) { // 找到可以映射的位置
last--;
}
mapping.put(b, last); // 映射
last--;
}
}
public int pick() {
// 随机选取一个索引
int index = (int)(Math.random() * sz);
// 这个索引命中了黑名单,需要被映射到其他位置
if (mapping.containsKey(index)) {
return mapping.get(index);
}
// 若没命中黑名单,则直接返回
return index;
}
}
六、其他
class Solution extends SolBase {
public int rand10() {
int row, col, idx;
do {
idx = 1;
row = rand7();
col = rand7();
idx = col + (row - 1) * 7;
} while (idx > 40);
// idx的取值范围在[1,40],这40个位置上的元素是[1,10],并且每一个元素的概率的1/10
return 1 + (idx - 1) % 10;
}
}