1. 程式人生 > >【LeetCode】519. Random Flip Matrix 解題報告(Python)

【LeetCode】519. Random Flip Matrix 解題報告(Python)

題目描述:

You are given the number of rows n_rows and number of columns n_cols of a 2D binary matrix where all values are initially 0. Write a function flip which chooses a 0 value uniformly at random, changes it to 1, and then returns the position [row.id, col.id] of that value. Also, write a function reset

which sets all values back to 0. Try to minimize the number of calls to system’s Math.random() and optimize the time and space complexity.

Note:

  1. 1 <= n_rows, n_cols <= 10000
  2. 0 <= row.id < n_rows and 0 <= col.id < n_cols
  3. flip will not be called when the matrix has no 0 values left.
  4. the total number of calls to flip
    and reset will not exceed 1000.

Example 1:

Input: 
["Solution","flip","flip","flip","flip"]
[[2,3],[],[],[],[]]
Output: [null,[0,1],[1,2],[1,0],[1,1]]

Example 2:

Input: 
["Solution","flip","flip","reset","flip"]
[[1,2],[],[],[],[]]
Output: [null,[0,0],[0,1],null,[0,0]]

Explanation of Input Syntax:

The input is two lists: the subroutines called and their arguments. Solution’s constructor has two arguments, n_rows and n_cols. flip and reset have no arguments. Arguments are always wrapped with a list, even if there aren’t any.

題目大意

題目是用n_rows, n_cols給出了一個空白的二維陣列,二維陣列每個數字都是0.現在要使用flip函式隨機選擇0的位置翻轉成1.同時還有一個函式reset是把整個二維陣列重置成0.實現這個要求,並儘可能的優化時間和空間,並且減少random()函式的呼叫。

解題方法

方法一:迴圈生成隨機數

這個題的心路歷程:時間消耗比較多的肯定是random()的呼叫次數,首先分析這個函式能呼叫多少次。很激動的是flip函式竟然最多隻呼叫1000次!而行和列的大小竟然到達了10000!所以很明顯這個題需要我們用時間換空間嘛。肯定不能開個很大的二維陣列然後記錄這個過程。

所以我想了類似點陣圖的方法,只需要一個隨機數字,然後把這個數字轉成二維空間的行數和列數就行。所以使用set來儲存已經使用過的數字,然後選隨機數,如果這個隨機數已經出現過,那麼繼續迴圈找到一個沒有出現過的數字。然後計算這個數字在二維列表中的位置就好了。

求一個數字應該排列在二維陣列中的位置方式是[pos / self.N, pos % self.N]。要記住。

效率怎麼樣呢?很容易想象,當這個二維陣列比較小的時候,那麼衝突肯定很多,所以迴圈的呼叫次數很多。但是,當二維陣列足夠大,比如題目中有10000*10000的空位時候,flip最多才1000次,那麼隨機數碰撞的次數肯定很少了,效率就比較高了。

時間複雜度是O(N),空間複雜度是O(N).N是呼叫次數。超過了52%的提交。

class Solution(object):

    def __init__(self, n_rows, n_cols):
        """
        :type n_rows: int
        :type n_cols: int
        """
        self.M = n_rows
        self.N = n_cols
        self.total = self.M * self.N
        self.fliped = set()

    def flip(self):
        """
        :rtype: List[int]
        """
        pos = random.randint(0, self.total - 1)
        while pos in self.fliped:
            pos = random.randint(0, self.total - 1)
        self.fliped.add(pos)
        return [pos / self.N, pos % self.N]

    def reset(self):
        """
        :rtype: void
        """
        self.fliped.clear()


# Your Solution object will be instantiated and called as such:
# obj = Solution(n_rows, n_cols)
# param_1 = obj.flip()
# obj.reset()

方法二:Fisher–Yates shuffle 洗牌演算法

看到題目說了儘可能的優化隨機數的呼叫,就知道還有更高效的演算法,果然有啊!著名的Fisher–Yates shuffle 洗牌演算法!但是需要改進一下。關於這個演算法可以看這個視訊,還是挺容易弄懂的。這個演算法對N個數字進行隨機洗牌,只需要呼叫N - 1次隨機函式。

這個洗牌演算法的思想就是,使用一個指標從後向前遍歷,它標記的是洗牌的末尾。即這個指標之後的數字已經全部洗牌了,不用再考慮;前面的數字還沒有洗牌,需要處理;隨機生成一個範圍在前面陣列長度的隨機數,表示選中了哪個,然後和指標標記的位置進行交換,指標前移,重複這個過程。

我用一句更明白的話:每次在前面未洗牌部分隨機選擇一個數字,然後放到已經洗牌了數字裡頭。

至於為什麼需要指標以及交換數字,那是為了在原地in-place操作使用的。

同樣地,在這個題中不能直接使用那麼大的陣列進行這個過程的模擬,記憶體不夠。所以,使用一個字典儲存已經被隨機數選擇過的位置,把這個位置和末尾的total交換的實現方式是使用字典儲存這個位置交換成了末尾的那個數字。每次隨機到一個數字,然後在字典中查,如果這個數字不在字典中,表示這個數字還沒被選中過,那麼就直接返回這個數字,把這個數字和末尾數字交換;如果隨機數已經在字典中出現過,那麼說明這個位置已經被選中過,使用字典裡儲存的交換後的數字返回。

舉個例子吧:

輸入:

["Solution", "flip", "flip", "flip", "flip", "flip", "flip"]
[[2, 3], [], [], [], [], [], []]

程式碼第21行打印出來的r, x, self.total, self.d如下

(0, 0, 5, {0: 5})
(0, 5, 4, {0: 4})
(3, 3, 3, {0: 4, 3: 3})
(2, 2, 2, {0: 4, 2: 2, 3: 3})
(1, 1, 1, {0: 4, 1: 1, 2: 2, 3: 3})
(0, 4, 0, {0: 4, 1: 1, 2: 2, 3: 3})

希望這個例子能幫助理解吧!

時間複雜度是O(N),空間複雜度是O(N).N是呼叫次數。超過了31%的提交。

class Solution(object):

    def __init__(self, n_rows, n_cols):
        """
        :type n_rows: int
        :type n_cols: int
        """
        self.M = n_rows
        self.N = n_cols
        self.total = self.M * self.N
        self.d = dict()

    def flip(self):
        """
        :rtype: List[int]
        """
        r = random.randint(0, self.total - 1)
        self.total -= 1
        x = self.d.get(r, r)
        self.d[r] = self.d.get(self.total, self.total)
        # print(r, x, self.total, self.d)
        return [x / self.N, x % self.N]
        

    def reset(self):
        """
        :rtype: void
        """
        self.d.clear()
        self.total = self.M * self.N


# Your Solution object will be instantiated and called as such:
# obj = Solution(n_rows, n_cols)
# param_1 = obj.flip()
# obj.reset()

日期

2018 年 10 月 19 日 —— 自古逢秋悲寂寥,我言秋日勝春朝