带权重的随机选择算法 :: labuladong的算法小抄 #996
Replies: 48 comments 12 replies
-
今天面Cisco就出了这道.... 细节啊,卡了我半天,总结就是一定要私下自己手写 |
Beta Was this translation helpful? Give feedback.
-
java方法,前缀数组下标从0开始,最终的结果不需要再-1了: class Solution {
private Random random = new Random();
private int[] preSum;
private int n;
public Solution(int[] w) {
this.n = w.length;
// 前缀数组,下标0就是原数组w[0]
preSum = new int[n];
// 下标0就是原数组w[0]
preSum[0] = w[0];
for (int i = 1; i < n; i++) {
preSum[i] = preSum[i-1] + w[i];
}
}
public int pickIndex() {
// 保证取到[1, preSum[n-1]]的闭区间
int target = random.nextInt(preSum[n-1]) + 1;
// right可以从n-1开始,也可以从n开始,对结果的正确性没有影响
int left = 0, right = n;
while (left < right) {
int mid = left + (right - left) / 2;
// 如果找到了,直接去当前的下标
if (preSum[mid] == target) {
left =mid;
break;
} else if (preSum[mid] > target) {
// 向左找,因为当前mid的值大于target,可能是"第一个大于target"的值,所以不能丢弃mid
// 如果mid的值不再需要了(最终不会取到现在的mid),那么就可以right=mid-1;
right = mid;
} else {
// 向右找,因为当前的mid的值比target小,永远不会取到了。所以left=mid+1;
left = mid + 1;
}
}
// left代表的含义:
// 当目标元素 target 不存在数组 nums 中时,搜索左侧边界的二分搜索的返回值可以做以下几种解读:
// 1、返回的这个值是 nums 中大于等于 target 的最小元素索引。
// 2、返回的这个值是 target 应该插入在 nums 中的索引位置。
// 3、返回的这个值是 nums 中小于 target 的元素个数。
return left;
}
} |
Beta Was this translation helpful? Give feedback.
-
不懂就问,为啥后面的这些,二分的写法都不用总结的,还是用这种大多数人用的呢 |
Beta Was this translation helpful? Give feedback.
-
等价于 random from [0...sum(w)-1],然后找这个值的右插入位置:
|
Beta Was this translation helpful? Give feedback.
-
为什么前缀和数组中 0 本质上是个占位符啊?这句话还是不太理解,就是为什么target的范围不是【0,7】? |
Beta Was this translation helpful? Give feedback.
-
@thy485280869 不看就滚,别搁这叽叽歪歪 |
Beta Was this translation helpful? Give feedback.
-
是不是不一定要用搜索左侧边界的二分法呀,我用了最普通的二分搜索也通过了,把返回值改成left就可以了(虽然和模板不一样,但是感觉这样好像更容易理解 |
Beta Was this translation helpful? Give feedback.
-
@WanrenDing 如果你能说清楚为什么这样写能过,那就算是真的理解了 |
Beta Was this translation helpful? Give feedback.
-
@WanrenDing 因为这道题每个位置必定占有一定权重w[i]不可能为0,所以preSum里不会有重复元素,那么二分找到的第一个位置必然就是左侧边界了;返回值left能保证是target应插入preSum的位置的原因是我们取的是左闭右开的区间,left会移动至mid的右边一位,所以能保证left最终落在恰好比target大的位置(当然你return right也行) |
Beta Was this translation helpful? Give feedback.
-
@labuladong 请问使用左右都闭合的区间怎么写,我用了你的左右都闭合的区间的二分方法没有成功。求大佬教教 |
Beta Was this translation helpful? Give feedback.
-
这题还可以用lottery算法,空间复杂度O(1)就可以做出来 |
Beta Was this translation helpful? Give feedback.
-
请问有朋友用 Python 写的吗 num = random.uniform(0, self.pre_sum[-1]) + 1 但是用 randint 在 leetcode 上跑就会报错 num = random.randint(0, self.pre_sum[-1]) + 1 |
Beta Was this translation helpful? Give feedback.
-
同样左右闭合的左侧边界二分搜索过不了,return是return left==0 ? 0 : left-1; 我太菜了www |
Beta Was this translation helpful? Give feedback.
-
打卡 二分搜索的妙用! |
Beta Was this translation helpful? Give feedback.
-
带权重:搜索左侧边界target = random[1, preSum[n-1]] w = [1,3,2,1] preSum = [0, 1, 4, 6, 7] target 的取值范围 |
Beta Was this translation helpful? Give feedback.
-
我倒是挺喜欢lol还有lol手游的匹配机制的,因为在这两款游戏中,我都是那条菜狗😋 |
Beta Was this translation helpful? Give feedback.
-
class Solution {
int [] presum = null;
Random random = new Random();
public Solution(int[] w) {
int n = w.length;
if(n==0)return;
presum = new int[n];
presum[0] = w[0];
for(int i = 1 ; i<n;i++){
presum[i] = presum[i-1]+w[i];
}
}
public int pickIndex() {
int n = presum.length;
int target = random.nextInt(presum[n-1])+1;
return left_bound(target);
}
public int left_bound(int target){
int left = 0 , right = presum.length-1;
while(left<=right){
int mid = left + (right-left)/2;
if(presum[mid]>target){
right = mid - 1;
}else if(presum[mid]<target){
left = mid + 1;
}else if(presum[mid] == target){
left = mid + 1;
}
}
if(right<0)return left;
return presum[right] == target? right: left;
}
}
/**
* Your Solution object will be instantiated and called as such:
* Solution obj = new Solution(w);
* int param_1 = obj.pickIndex();
*/ 这里我发现了一个小细节 首先前缀和可以不使用n+1 只用n 东哥可能是为了和前面讲的前缀和呼应 所以我这里写的代码的意思是 如果target能被找到 那么就返回right 找不到 那就乖乖返回left |
Beta Was this translation helpful? Give feedback.
-
class Solution {
// 前缀和数组
private preSum: number[] | undefined;
constructor(w: number[]) {
const n = w.length;
this.preSum = new Array(n + 1);
this.preSum[0] = 0;
for (let i = 1; i <= n; i++) {
this.preSum[i] = this.preSum[i - 1] + w[i - 1];
}
}
pickIndex(): number {
const last = this.preSum.slice(-1)?.[0];
const random = Math.ceil(Math.random() * last);
const i = this.preSum.findIndex(f => f >= random);
return i - 1;
}
} |
Beta Was this translation helpful? Give feedback.
-
东哥写的还是那么巧妙,感觉细节还是多的,比如随机从[1,7] |
Beta Was this translation helpful? Give feedback.
-
type Solution struct {
presum []int
}
func Constructor(w []int) Solution {
n:=len(w)
presum:=make([]int,n+1)
presum[0]=0
for i:=1;i<=n;i++{
presum[i] = presum[i-1] + w[i-1]
}
return Solution{presum}
}
func (this *Solution) PickIndex() int {
n:=len(this.presum)
target:=rand.Intn(this.presum[n-1])+1
return leftBound(this.presum,target) - 1
}
func leftBound(nums []int,target int) int{
if len(nums) == 0 {
return -1
}
left,right:=0,len(nums)
for left<right {
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
}
/**
* Your Solution object will be instantiated and called as such:
* obj := Constructor(w);
* param_1 := obj.PickIndex();
*/ |
Beta Was this translation helpful? Give feedback.
-
提供用 二分搜尋找 gt ge lt le 的相關資料 其實有一定的規律 def find_lt(a, x): 解讀為 https://docs.python.org/3/library/bisect.html#searching-sorted-lists |
Beta Was this translation helpful? Give feedback.
-
生成随机数,int target = rand.nextInt(preSum[n - 1]) + 1; |
Beta Was this translation helpful? Give feedback.
-
前缀数组不用占位符也能做,即preSum[0] = w[0],而且这样感觉更好理解,不需要返回结果减1。不过这样二分搜索时要需要使用双闭区间,左闭右开会有问题,同时随机数的范围也要双闭 |
Beta Was this translation helpful? Give feedback.
-
20230110 打卡 |
Beta Was this translation helpful? Give feedback.
-
为啥用闭区间二分搜索做这个就通不过呢😰 |
Beta Was this translation helpful? Give feedback.
-
没懂为什么不能用右边界 |
Beta Was this translation helpful? Give feedback.
-
文章链接点这里:带权重的随机选择算法
评论礼仪 见这里,违者直接拉黑。
Beta Was this translation helpful? Give feedback.
All reactions