LeetCode: -Dynamic Programming-Strange Printer[664]

来源:互联网 发布:say it again frances 编辑:程序博客网 时间:2024/06/05 11:16

题目

一个打印机,每一轮能打印一个同一个字符的序列,能在下一轮在任何位置开始任何位置结束打印一个其他的字符。后打印的将覆盖先打印的。
给定一个字符串,求最小的打印轮次。

示例:

Input: "aaabbb"Output: 2Explanation: 先打印 "aaa" 再打印 "bbb".
Input: "aba"Output: 2Explanation: 先打印 "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 ccdbcd aaa def + aaa ccdbcd 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];    }};