leetcode 384 打亂陣列(洗牌演算法)

2021-09-19 23:24:47 字數 4320 閱讀 2355

打亂乙個沒有重複元素的陣列。

示例:

// 以數字集合 1, 2 和 3 初始化陣列。

int nums = ;

solution solution = new solution(nums);

// 打亂陣列 [1,2,3] 並返回結果。任何 [1,2,3]的排列返回的概率應該相同。

solution.shuffle();

// 重設陣列到它的初始狀態[1,2,3]。

solution.reset();

// 隨機返回陣列[1,2,3]打亂後的結果。

solution.shuffle();

n個不同的數中隨機取出不重複的m個數

基本思想是從原始陣列中隨機取乙個之前沒取過的數字到新的陣列中,具體如下:

(1)初始化原始陣列和新陣列,原始陣列長度為n(已知)

(2)從還沒出來ide陣列(假設還剩k個)中,隨機產生乙個[0,k)

(3)從剩下的k個數中把第p個數取出;

(4) 重複步驟2和3直到數字全部取完;

(5)從步驟3取出的數字序列便是乙個打亂了的數列。

下面證明其隨機性,即每個元素被放置在新陣列中的第i個位置是1/n(假設陣列大小是n)。

證明:乙個元素m被放入第i個位置的概率p = 前i-1個位置選擇元素時沒有選中m的概率 * 第i個位置選中m的概率,即

p =n

−1n×

n−2n

−1×⋯

×n−i

+1n−

i+2×

1n−i

+1=1

np=\frac\times\frac\times\dots\times\frac\times\frac\\=\frac

p=nn−1

​×n−

1n−2

​×⋯×

n−i+

2n−i

+1​×

n−i+

11​=n1​

#define n 10

#define m 5

void

fisher_yates_shuffle

(vector<

int>

& arr,vector<

int>

& res)

}

時間複雜度:o(n^2) 空間複雜度:o(n)

knuth 和 durstenfeld 在fisher 等人的基礎上對演算法進行了改進,在原始陣列上對數字進行互動,省去了額外o(n)的空間。該演算法的基本思想和 fisher 類似,每次從未處理的資料中隨機取出乙個數字,然後把該數字放在陣列的尾部,即陣列尾部存放的是已經處理過的數字。

演算法步驟為:

(1) 建立乙個陣列大小為 n 的陣列 arr,分別存放 1 到 n 的數值;

(2)生成乙個從 0 到 n - 1 的隨機數 x;

(3)輸出 arr 下標為 x 的數值,即為第乙個隨機數;

(4)將 arr 的尾元素和下標為 x 的元素互換;

(5)同2,生成乙個從 0 到 n - 2 的隨機數 x;

(6)輸出 arr 下標為 x 的數值,為第二個隨機數;

(7)將 arr 的倒數第二個元素和下標為 x 的元素互換;

如上,直到輸出 m 個數為止

時間複雜度為o(n),空間複雜度為o(1),缺點必須知道陣列長度n.

void

knuth_durstenfeld_shuffle

(vector<

int>

&arr)

原始陣列被修改了,這是乙個原地打亂順序的演算法,演算法時間複雜度也從fisher演算法的 o(n2)提公升到了o(n)。由於是從後往前掃瞄,無法處理不知道長度或動態增長的陣列。

knuth-durstenfeld shuffle 是乙個內部打亂的演算法,演算法完成後原始資料被直接打亂,儘管這個方法可以節省空間,但在有些應用中可能需要保留原始資料,所以需要另外開闢乙個陣列來儲存生成的新序列。

inside-out algorithm 演算法的基本思想:

從前向後掃瞄資料,把位置i的資料隨機插入到前i個(包括第i個)位置中(假設為k),這個操作是在新陣列中進行,然後把原始資料中位置k的數字替換新陣列位置i的數字。其實效果相當於新陣列中位置k和位置i的數字進行互動。

如果知道arr的lengh的話,可以改為for迴圈,由於是從前往後遍歷,所以可以應對arr數目未知的情況,或者arr是乙個動態增加的情況

證明如下:

原陣列第i個元素(隨機到的數)在新陣列的前i個位置的概率都是:

1 i×

ii+1

×i+1

i+2⋯

×n−1

n=1n

\frac \times \frac \times \frac \dots \times \frac = \frac

i1​×i+

1i​×

i+2i

+1​⋯

×nn−

1​=n

1​(即第i次剛好隨機放到了該位置,在後面的n-i 次選擇中該數字不被選中)

原陣列的第 i 個元素(隨機到的數)在新陣列的 i+1 (包括i + 1)以後的位置(假設是第k個位置)的概率是:

1 k×

kk+1

⋯×n−

1n=1

n\frac \times \frac \dots \times \frac = \frac

k1​×k+

1k​⋯

×nn−

1​=n

1​(即第k次剛好隨機放到了該位置,在後面的n-k次選擇中該數字不被選中)。

void

inside_out_shuffle

(const vector<

int>

&arr,vector<

int>

& res)

}

從n個元素中隨機等概率取出k個元素,n長度未知。它能夠在o(n)時間內對n個資料進行等概率隨機抽取。如果資料集合的量特別大或者還在增長(相當於未知資料集合總量),該演算法依然可以等概率抽樣。

先選中第1到k個元素,作為被選中的元素。然後依次對第k+1至第n個元素做如下操作:

每個元素都有k/x的概率被選中,然後等概率的(1/k)替換掉被選中的元素。其中x是元素的序號。

證明:第m個物件被選中的概率=選擇m的概率 * (其後元素不被選擇的概率+其後元素被選擇的概率*不替換第m個物件的概率),即

* 隨機產生[0,k]位置隨機數,與位置k元素交換;每次產生乙個隨機數放佇列末尾

* 原地打亂演算法 時間複雜度o(n)

*/class

solution

/** resets the array to its original configuration and return it. */

vector<

int>

reset()

/** returns a random shuffling of the array. */

vector<

int>

shuffle()

return arr;}}

;

/*

* inside-out algorithm

* 從前向後掃瞄資料,把位置i的資料隨機插入到前i個(包括第i個)位置中(假設為k)

* 時間複雜度o(n) 空間複雜度o(n)

*/class

solution

/** resets the array to its original configuration and return it. */

vector<

int>

reset()

/** returns a random shuffling of the array. */

vector<

int>

shuffle()

return arr;}}

;

LeetCode 384 打亂陣列

打亂乙個沒有重複元素的陣列。示例 以數字集合 1,2 和 3 初始化陣列。int nums solution solution new solution nums 打亂陣列 1,2,3 並返回結果。任何 1,2,3 的排列返回的概率應該相同。solution.shuffle 重設陣列到它的初始狀態 ...

Leetcode 384 打亂陣列

打亂乙個沒有重複元素的陣列。示例 以數字集合 1,2 和 3 初始化陣列。int nums solution solution new solution nums 打亂陣列 1,2,3 並返回結果。任何 1,2,3 的排列返回的概率應該相同。solution.shuffle 重設陣列到它的初始狀態 ...

LeetCode 384 打亂陣列

題目 打亂乙個沒有重複元素的陣列。示例 以數字集合 1,2 和 3 初始化陣列。int nums solution solution new solution nums 打亂陣列 1,2,3 並返回結果。任何 1,2,3 的排列返回的概率應該相同。solution.shuffle 重設陣列到它的初始...