一行代碼就能夠經過 LeetCode?來看下我是怎麼作到的!

這是一道 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

  • 若是第一我的選擇了第一我的的位置(也就是選擇了本身的位置),那麼剩下的人按照票上的座位作就行了,這種狀況第 n 我的必定能作到本身的位置
  • 若是第一我的選擇了第 n 我的的位置,那麼第 n 我的確定坐不到本身的位置。
  • 若是第一我的選擇了第 i (1 < i < n)我的的位置,那麼第 i 我的就至關於變成了「票丟的人」,此時問題轉化爲 f(n - i + 1)。

此時的問題轉化關係如圖: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

暴力遞歸 + hashtable

思路

咱們考慮使用記憶化遞歸來減小重複計算,雖然這種作法能夠減小運行時間,可是對減小遞歸深度沒有幫助。仍是會棧溢出。

代碼

代碼支持 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

關鍵點

  • 機率分析
  • 數學推導
  • 動態規劃
  • 遞歸 + mapper
  • 棧限制大小
  • 尾遞歸
相關文章
相關標籤/搜索