1. 程式人生 > >LeetCode: -Dynamic Programming-Strange Printer[664]

LeetCode: -Dynamic Programming-Strange Printer[664]

題目

一個印表機,每一輪能列印一個同一個字元的序列,能在下一輪在任何位置開始任何位置結束列印一個其他的字元。後列印的將覆蓋先列印的。
給定一個字串,求最小的列印輪次。

示例:

Input: "aaabbb"
Output: 2
Explanation: 先列印 "aaa" 再列印 "bbb".
Input: "aba"
Output: 2
Explanation: 先列印 "aaa" 再從第二個位置列印 "b" 會將已經存在的 'a' 覆蓋.

分析

二維DP問題
1、dp[i][j] 表示範圍 [i, j) 內的最小輪次. 運用memoization 和 DFS來避免計算不必要的子問題.
2、貪婪的選擇: 對於任意一個序列, 首先列印首字元. 比如 s[0] = ‘a’. 如果存在最優的解決方案, 不是將s[0] 列印的範圍是 [0, k)作為第一步, 我們可以將這一個移到第一步,並且保證其他步為原來的順序. 如果有字元在s[0]的原來的順序之前列印了,且範圍在k之前,我們將該字元的列印開始位置移動到k開始.

根據不同的方法將 s[0]= ‘a’ 和其他部分的 ‘a’結合起來,我們有:

dp[i][j] = min(dp[i][j], dp[start][k]+dp[k][end]), 對於每個 k 有 s[k] == s[i],

這裡的start和end分別是開始的一個字元和最後的一個字元,不是s[i].

例如

給定一個序列 "aaa bcd aaa def aaa ccd aaa", 我們有三種選擇來結合 s[0] = 'a' 和其他部分的 'a'.
bcd + aaa aaa def aaa ccd aaa,與之一樣的是 bcd + aaa def aaa ccd
bcd aaa def
+ aaa ccd bcd aaa def aaa ccd + aaa

程式碼

class Solution {
public:
    int strangePrinter(string s) {
        int n = s.size();
        vector<vector<int>> dp(n+1, vector<int>(n+1, 0));
        return helper(s, dp, 0, n);
    }
private:
    int helper(string& str, vector<vector
<int>
>
& dp, int s, int e) { if (s >= e) return 0; if (dp[s][e]) return dp[s][e]; // 處理 str[s] 的首尾字元 // 注意範圍是左閉右開 [s,e) [l,r) int l = s, r = e; while (l < e && str[l] == str[s]) l++; while (r > l && str[r-1] == str[s]) r--; dp[s][e] = 1+helper(str, dp, l, r); for (int i = l; i < r; i++) { if (str[i] == str[s]) { dp[s][e] = min(dp[s][e], helper(str,dp,l,i)+helper(str,dp,i,r)); while (i < e && str[i] == str[s]) i++; } } return dp[s][e]; } };