此题来自力扣,题目主要标签为二分搜索.
题目描述:
给定一个正整数数组 w ,其中 w[i] 代表位置 i 的权重,请写一个函数 pickIndex ,它可以随机地获取位置 i,选取位置 i 的概率与 w[i] 成正比。
解题思路:
思路1:
初看到这道题时,我的最基本思路就是根据权重数组w[]
构建扩展数组extenedArray[]
,如果位置i的权重为k,则在扩展数组包含k个i.基于此,我们在范围[0, 权重和)
之间生成随机值i
, 返回extenedArray[i]
即可.
算法分析:
该算法时间复杂度为O(1),但其空间复杂度则与位置i的权重大小相关,扩展数组所需的空间为权重和.假设位置1,2权重分别为1, 1000000, 则扩展数组所需空间为1000001,空间复杂度上限可能为无穷大.因此算法在最坏情况下是不切实际的.
思路2:
思路2是基于官方题解整理出的思路.
与思路1类似,我们可以生成在范围[0, 权重和)
之间的随机值i
,但我们并不直接构建扩展数组,而是考虑如何将随机值i
映射到权重数组的对应下标.这便是该方法最关键的一步.
要点:
1.根据权重数组,构建累加数组.
假设权重数组w = {1, 4, 2}
, 则权重数组的累加数组为cumSum = {1, 5, 7}
.
2. 使用二分搜索搜索随机值i
在累加数组的对应下标.
注意到此处二分搜索不同于基本的二分搜索方法, 对于累加数组{1, 5, 7}
, 搜索3, 4, 5
都应当返回1
,类似地,搜索6, 7
都应当返回2
.当然对于二分搜索的代码只需要微小的改动即可实现.
分析:
该算法空间复杂度主要分析构建累加数组所需额额外空间, 为O(n);
时间复杂度与二分搜索一致, 为O(lgN);
java实现
class Solution {
int totalCount; //纪录权重和
int[] cumSum; //权重权重累加数组
Random rand = new Random();
public Solution(int[] w) {
cumSum = new int[w.length];
for(int i = 0; i < w.length; i++){
if(i == 0)
cumSum[i] = w[i];
else
cumSum[i] = w[i] + cumSum[i-1];
}
totalCount = cumSum[cumSum.length-1];
}
public int pickIndex() {
//从区间[1, totalCount]中返回一个随机值,查找该随机值在前缀数组中的位置
int randIndex = rand.nextInt(totalCount) + 1;
return binarySearch(cumSum, randIndex);
}
private int binarySearch(int[] cumSum, int x){
int lo = 0;
int hi = cumSum.length;
while(lo < hi){
int mid = lo + (hi-lo)/2;
if(cumSum[mid] == x)
return mid;
else if(cumSum[mid] < x){
//可以肯定, 此时x的对应下标一定大于mid, 所以在[mid+1, hi)中搜索
lo = mid;
}
else
//x的对应下标可能等于mid, 先在[lo, mid)中搜索, 如果搜索不到, 则while循环退出, 返回hi即可
hi = mid;
}
return hi;
}
}