一、水塘抽样算法
作用:对于大样本量的数据集,随机选取样本
时间复杂度:O(1),一次遍历
空间复杂度:O(1)
问题:从N个样本种随机选取k个样本,传统做法要么需要借助哈希表等工具,时间复杂度为O(n);要么需要不止一次的遍历,那么怎么优化这个过程呢?
随机选取k个:遍历样本,遍历到第i个样本的时候,随机选取1到i的数字j,若j < k,则将第i个样本放到返回结果的第j个数据中。
随机选取1个:遍历样本,遍历到第i个样本的时候,随机选取1到i的数字j,若j == 0,则将第i个样本作为返回结果。
返回概率:
该算法保证每个结果的返回概率相等,以k = 1为例:
二、应用
1、382.链表随机节点
给你一个单链表,随机选择链表的一个节点,并返回相应的节点值。每个节点 被选中的概率一样 。
实现 Solution 类:
Solution(ListNode head) 使用整数数组初始化对象。
int getRandom() 从链表中随机选择一个节点并返回该节点的值。链表中所有节点被选中的概率相等。
示例:
输入
["Solution", "getRandom", "getRandom", "getRandom", "getRandom", "getRandom"]
[[[1, 2, 3]], [], [], [], [], []]
输出
[null, 1, 3, 2, 2, 3]
解释
Solution solution = new Solution([1, 2, 3]);
solution.getRandom(); // 返回 1
solution.getRandom(); // 返回 3
solution.getRandom(); // 返回 2
solution.getRandom(); // 返回 2
solution.getRandom(); // 返回 3
// getRandom() 方法应随机返回 1、2、3中的一个,每个元素被返回的概率相等。
提示:
链表中的节点数在范围 [1, 10^4] 内
-10^4 <= Node.val <= 10^4
至多调用 getRandom 方法 10^4 次
class Solution:
def __init__(self, head: Optional[ListNode]):
#print(head)
self.head = head
def getRandom(self) -> int:
res = 0
i = 1
node = self.head
while node:
if randrange(i) == 0:
res = node.val
i += 1
node = node.next
return res
2、398.随机数索引
给定一个可能含有重复元素的整数数组,要求随机输出给定的数字的索引。 您可以假设给定的数字一定存在于数组中。
注意:
数组大小可能非常大。 使用太多额外空间的解决方案将不会通过测试。
示例:
int[] nums = new int[] {1,2,3,3,3};
Solution solution = new Solution(nums);
// pick(3) 应该返回索引 2,3 或者 4。每个索引的返回概率应该相等。
solution.pick(3);
// pick(1) 应该返回 0。因为只有nums[0]等于1。
solution.pick(1);
class Solution:
def __init__(self, nums: List[int]):
self.nums = nums
def pick(self, target: int) -> int:
ans = 0
count = 0
for i, num in enumerate(self.nums):
if num == target:
count += 1
if randint(0, count - 1) == 0:
ans = i
return ans