這是一道 LeetCode 爲數很少的機率題,咱們來看下。原題地址:https://leetcode-cn.com/probl...python
有 n 位乘客即將登機,飛機正好有 n 個座位。第一位乘客的票丟了,他隨便選了一個座位坐下。 剩下的乘客將會: 若是他們本身的座位還空着,就坐到本身的座位上, 當他們本身的座位被佔用時,隨機選擇其餘座位 第 n 位乘客坐在本身的座位上的機率是多少? 示例 1: 輸入:n = 1 輸出:1.00000 解釋:第一我的只會坐在本身的位置上。 示例 2: 輸入: n = 2 輸出: 0.50000 解釋:在第一我的選好座位坐下後,第二我的坐在本身的座位上的機率是 0.5。 提示: 1 <= n <= 10^5
這是一道 LeetCode 爲數很少的機率題,咱們來看下。app
咱們定義原問題爲 f(n)。對於第一我的來講,他有 n 中選擇,就是分別選擇 n 個座位中的一個。因爲選擇每一個位置的機率是相同的,那麼選擇每一個位置的機率應該都是 1 / n。優化
咱們分三種狀況來討論:spa
此時的問題轉化關係如圖:code
(紅色表示票丟的人)blog
整個過程分析:遞歸
代碼支持 Python3:ip
Python3 Code:leetcode
class Solution: def nthPersonGetsNthSeat(self, n: int) -> float: if n == 1: return 1 if n == 2: return 0.5 res = 1 / n for i in range(2, n): res += self.nthPersonGetsNthSeat(n - i + 1) * 1 / n return res
上述代碼會棧溢出。rem
咱們考慮使用記憶化遞歸來減小重複計算,雖然這種作法能夠減小運行時間,可是對減小遞歸深度沒有幫助。仍是會棧溢出。
代碼支持 Python3:
Python3 Code:
class Solution: seen = {} def nthPersonGetsNthSeat(self, n: int) -> float: if n == 1: return 1 if n == 2: return 0.5 if n in self.seen: return self.seen[n] res = 1 / n for i in range(2, n): res += self.nthPersonGetsNthSeat(n - i + 1) * 1 / n self.seen[n] = res return res
上面作法會棧溢出。其實咱們根本不須要運行就應該能判斷出棧溢出,題目已經給了數據規模是 1 <= n <= 10 ** 5。 這個量級無論什麼語言,除非使用尾遞歸,否則通常都會棧溢出,具體棧深度你們能夠查閱相關資料。
既然是棧溢出,那麼咱們考慮使用迭代來完成。 很容易想到使用動態規劃來完成。其實遞歸都寫出來,寫一個樸素版的動態規劃也難不到哪去,畢竟動態規劃就是記錄子問題,並創建子問題之間映射而已,這和遞歸併沒有本質區別。
代碼支持 Python3:
Python3 Code:
class Solution: def nthPersonGetsNthSeat(self, n: int) -> float: if n == 1: return 1 if n == 2: return 0.5 dp = [1, .5] * n for i in range(2, n): dp[i] = 1 / n for j in range(2, i): dp[i] += dp[i - j + 1] * 1 / n return dp[-1]
這種思路的代碼超時了,而且僅僅執行了 35/100 testcase 就超時了。
咱們還須要進一步優化時間複雜度,咱們須要思考是否能夠在線性的時間內完成。
咱們繼續前面的思路進行分析, 不可貴出,咱們不妨稱其爲等式 1:
f(n) = 1/n + 0 + 1/n * (f(n-1) + f(n-2) + ... + f(2)) = 1/n * (f(n-1) + f(n-2) + ... + f(2) + 1) = 1/n * (f(n-1) + f(n-2) + ... + f(2) + f(1))
彷佛更復雜了?不要緊,咱們繼續往下看,咱們看下 f(n - 1),咱們不妨稱其爲等式 2。
f(n-1) = 1/(n-1) * (f(n-2) + f(n-3) + ... + f(1))
咱們將等式 1 和等式 2 兩邊分別同時乘以 n 和 n - 1
n * f(n) = f(n-1) + f(n-2) + f(n-3) + ... + f(1) (n-1) * f(n-1) = f(n-2) + f(n-3) + ... + f(1)
咱們將二者相減:
n * f(n) - (n-1)*f(n-1) = f(n-1)
咱們繼續將 (n-1)*f(n-1) 移到等式右邊,獲得:
n * f(n) = n * f(n-1)
也就是說:
f(n) = f(n - 1)
固然前提是 n 大於 2。
既然如此,咱們就能夠減小一層循環, 咱們用這個思路來優化一下上面的 dp 解法。這種解法終於能夠 AC 了。
代碼支持 Python3:
Python3 Code:
class Solution: def nthPersonGetsNthSeat(self, n: int) -> float: if n == 1: return 1 if n == 2: return 0.5 dp = [1, .5] * n for i in range(2, n): dp[i] = 1/n+(n-2)/n * dp[n-1] return dp[-1]
上面咱們經過數學分析,得出了當 n 大於 2 時:
f(n) = f(n - 1)
那麼是否是意味着咱們隨便求出一個 n 就行了? 好比咱們求出 n = 2 的時候的值,是否是就知道 n 爲任意數的值了。 咱們不難想出 n = 2 時候,機率是 0.5,所以只要 n 大於 1 就是 0.5 機率,不然就是 1 機率。
代碼支持 Python3:
Python3 Code:
class Solution: def nthPersonGetsNthSeat(self, n: int) -> float: return 1 if n == 1 else .5