原題連結:【398. 隨機數索引】:https://leetcode-cn.com/problems/random-pick-index/
題目描述:
給定一個可能含有重複元素的整數陣列,要求隨機輸出給定的數字的索引。 您可以假設給定的數字一定存在於陣列中。
注意:
陣列大小可能非常大。 使用太多額外空間的解決方案將不會通過測試。
示例:
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);
解題思路:
解法1:雜湊表。
暴力解法。使用雜湊表儲存陣列中每個元素值到其所有的下標索引的對映。
即key為元素值,value為元素值對應的索引下標列表。
再用random函式,從下標列表中隨機選擇一個索引返回即可。
這個解法非常佔用額外空間O(N),如果陣列很大,很容易oom。
解法2:動態陣列儲存索引列表。
定義一個儲存索引下標的索引列表indexList,用動態陣列,方便擴充套件。
遍歷陣列,如果元素跟target目標值相等,則把索引值新增進indexList列表。
取隨機數,rand範圍為【0,indexList.length ),然後將對應位置的索引值返回即可。
概率為:1 / indexList.length
這裡相比上一種解法,已經降了一個數量級,但是如果N很大,或者數字分佈不均勻,indexList也可能非常大,也非常佔用額外空間,空間消耗也可能會達到O(N),可能會oom。
解法3:蓄水池抽樣演算法。
演算法是:要從超大陣列N中取k個元素,這裡是取一個元素,即K=1
那麼每次只取一個樣本值,那麼逐個遍歷陣列,當遇到第i個數時,就以 1 / i 的概率抽取它做樣本值,以(i - 1)/ i 的概率保留原來的樣本值,不替換。
我們不需要儲存索引下標列表,只需要一個變數cnt計數,統計匹配到了多少個相同的數字,方便後去取隨機數,算概率。
遍歷陣列,如果元素跟target目標值相等,則cnt++,計數器+1。
然後取隨機數,rand範圍為【1,cnt】。
因為這裡只取一個數,就是K=1的案例。
所以如果rand == 1,則取當前元素的下標,替換為結果值。
如果rand > 1,則保留之前的結果,繼續往下走。知道結束返回結果值。
特點:這裡只需要遍歷一遍陣列,而且沒有額外的空間消耗,只有一個計數器變數cnt。空間複雜度為O(1)。(非常推薦)
題解程式碼:
程式碼如下:(java版)
//給定一個可能含有重複元素的整數陣列,要求隨機輸出給定的數字的索引。 您可以假設給定的數字一定存在於陣列中。 // // 注意: //陣列大小可能非常大。 使用太多額外空間的解決方案將不會通過測試。 // // 示例: // // //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); // // Related Topics 水塘抽樣 雜湊表 數學 隨機化 // 125 0 import java.util.Random; //leetcode submit region begin(Prohibit modification and deletion) class Solution { // 需要把nums傳遞給pick方法,所以我們需要定義一個類變數 int[] nums; public Solution(int[] nums) { this.nums = nums; } public int pick(int target) { // 定義一個變數,儲存結果值 int result = 0; // 定義一個變數cnt,計數器,標記匹配到了幾個相同的數字,方便後面取隨機數範圍 int cnt = 0; // 迴圈遍歷連結串列 for (int i=0; i<nums.length; i++) { // 匹配跟target元素是否相等 if (nums[i] == target) { // 匹配到了,則計數器+1 cnt++; // 取隨機數rand範圍為[1, cnt],注意這裡邊界是cnt int rand = new Random().nextInt(cnt) + 1; // 取每個數的概率都是 1 / cnt,也就是說有1 / cnt的概率,更新結果下標值 if (rand == 1) { // rand等於1,則取當前元素的下標,更新結果值 result = i; } } } // 返回結果下標 return result; } } /** * Your Solution object will be instantiated and called as such: * Solution obj = new Solution(nums); * int param_1 = obj.pick(target); */ //leetcode submit region end(Prohibit modification and deletion)