leetcode 474

題目:

難度中等163

在計算機界中,我們總是追求用有限的資源獲取最大的收益。

現在,假設你分別支配着 m 個 0 和 n 個 1。另外,還有一個僅包含 0 和 1 字符串的數組。

你的任務是使用給定的 m 個 0 和 n 個 1 ,找到能拼出存在於數組中的字符串的最大數量。每個 0 和 1 至多被使用一次

注意:

  1. 給定 0 和 1 的數量都不會超過 100
  2. 給定字符串數組的長度不會超過 600

示例 1:

輸入: Array = {"10", "0001", "111001", "1", "0"}, m = 5, n = 3
輸出: 4
解釋: 總共 4 個字符串可以通過 5 個 0 和 3 個 1 拼出,即 "10","0001","1","0" 。

示例 2:

輸入: Array = {"10", "0", "1"}, m = 1, n = 1
輸出: 2


解釋: 你可以拼出 "10",但之後就沒有剩餘數字了。更好的選擇是拼出 "0" 和 "1" 。

題解:

     這道題很明顯是一個二重揹包問題,我們將字符串設爲放進揹包的物體,0和1的數量可以視爲物體的體積和重量(限制條件),字符串的數量就是我們所求的物體總價值。

     現在,我們設當前可以選擇放進揹包的字符串有i種,揹包目前允許的最大體積和最大重量是j,k(目前能用的0和1的總數),我們可以寫出狀態轉移方程:

dp[i][j][k] = dp[i-1][j][k]  (第i种放不下了,所以結果和只允許放前i-1種物品時一樣)

dp[i][j][k] = max(dp[i-1][j-zero][k-one]+1)(第i種可以放下,因此它相當於只允許放前i-1種物品時的情況加1,j-zero和k-one是因爲之前這點體積根本就沒有用,max是因爲我們要取最大值,萬一放了結果更不好呢?)

      先放O(n3)的代碼:

      

class Solution {
public:
    int findMaxForm(vector<string>& strs, int m, int n) {
        int dp[601][101][101] = {0};
        
        for(int i = 1;i<=strs.size();i++)
        {
            int zero = 0,one = 0;
            for(char c : strs[i-1])
            {
                if(c == '0') zero++;
                else one++;
            }
            for(int j = 0;j<=m;j++)
            {
                for(int k = 0;k<=n;k++)
                {
                    dp[i][j][k] = dp[i-1][j][k];
                    if(j >= zero && k >= one)
                        dp[i][j][k] = max(dp[i-1][j][k],dp[i-1][j-zero][k-one]+1);    
                }
            }
        }
        
        return dp[strs.size()][m][n];
    }
};

          相信大家也會覺得,這個時間複雜度有點太高了,那麼,該怎麼解決呢?

  題解二:

          我們發現,dp[i][j][k]的值只和i-1時的有關,我們設共有strs.size()張表,表的x軸和y軸就可以設爲j和k,即第i-1張表和第i張表是不同時間維度的表。我們用二維數組來表示,dp[j][k]可以暫時儲存它在i-1時刻的數值,然後在i時刻再改變,再看一眼公式:

          也就是說dp[j][k]的值只和上一張表的dp[j-zero][k-zero]有關,它自己本身其實還儲存着dp[j][k](第i-1張表)的舊值。如果我們從0到m,0到n會發生什麼事情呢?首先被更新的應該是第i張表的dp[j-zero][k-zero],它的值變成了第i張表的新值,輪到更新dp[j][k]時,它需要的是第i-1張表的dp[j-zero][k-zero](舊值),但是dp[j-zero][k-zero]已經在它之前更新成爲i時刻的新值了。

          現在我們明白順序遍歷是錯誤的,那爲什麼逆序從m到0,從n到0是正確的呢?

          因爲dp[j][k]的值只和上一張表有關,換言之,它需要的值都已經算出來了(都是舊值),而逆序遍歷之後,如dp[m][n]被更新後,dp[m-x][n-y]根本就不需要用到dp[m][n]的舊值,因此不會影響後面的更新。

          

class Solution {
public:
    int findMaxForm(vector<string>& strs, int m, int n) {
        vector<vector<int>>dp(m+1,vector<int>(n+1,0));
        
        for(string s : strs)
        {
            int zero = 0,one = 0;
            for(char c : s)
            {
                if(c == '0') zero++;
                else one++;
            }
            
            for(int j = m;j>=zero;j--)
            {
                for(int k = n;k>=one;k--)
                {
                    dp[j][k] = max(dp[j][k],dp[j-zero][k-one]+1);
                }
            }
        }
        return dp[m][n];
    }
};

         爲什麼是從m到zero,而不是0呢?因爲之前從m到0是爲了初始化dp[i][j][k] = dp[i-1][j][k],最糟糕的情況就是可以選擇的物品多了,但是還和之前的情況一樣沒有變動。但是在二維數組情況裏,dp[j][k]本身就儲存了i-1時刻的舊值了,最糟糕也就是這種情況了,在j<zero || k<one時刻,根本沒有辦法改變,所以是m到zero遍歷。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章