題目:
難度中等163
在計算機界中,我們總是追求用有限的資源獲取最大的收益。
現在,假設你分別支配着 m 個 0
和 n 個 1
。另外,還有一個僅包含 0
和 1
字符串的數組。
你的任務是使用給定的 m 個 0
和 n 個 1
,找到能拼出存在於數組中的字符串的最大數量。每個 0
和 1
至多被使用一次。
注意:
- 給定
0
和1
的數量都不會超過100
。 - 給定字符串數組的長度不會超過
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的總數),我們可以寫出狀態轉移方程:
(第i种放不下了,所以結果和只允許放前i-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遍歷。