[C++&Rust]LeetCode No.664 奇怪的打印机(每日一题)

原帖地址:http://blog.leanote.com/post/dawnmagnet/lc664

题目

有台奇怪的打印机有以下两个特殊要求:

打印机每次只能打印由 同一个字符 组成的序列。
每次可以在任意起始和结束位置打印新字符,并且会覆盖掉原来已有的字符。
给你一个字符串 s ,你的任务是计算这个打印机打印它需要的最少打印次数。

示例 1:

输入:s = "aaabbb"
输出:2
解释:首先打印 "aaa" 然后打印 "bbb"。

示例 2:

输入:s = "aba"
输出:2
解释:首先打印 "aaa" 然后在第二个位置打印 "b" 覆盖掉原来的字符 'a'。

提示:

1 <= s.length <= 100
s 由小写英文字母组成

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/strange-printer
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路分析

这道题是一个困难题。所以我们得多想
这个打印机到底有什么规律可循呢
我们先来列几个示例看看,因为题目给我们的这个示例实在是太寒碜了。
示例|a|ab|abc
–|
最小打印次数|1|2|3
这个还是比较好理解的,因为之前的数组中没有出现过b和c,每当一个新字母出现时,必定是加一
示例|a|aa|aaa
–|
最小打印次数|1|1|1
这个似乎引发了我们的一些思考。难道所有重复的项都可以合并吗。
就比如"aaabbb"和"ab"是等价的吗。当然,因为这个要求的是打印机的最小打印次数,而一次打印多少个字母其实都是可以的,所以连续的a和一个a是等价的,都得花费打印次数。
示例|ab|aba|abb|abc
–|
最小打印次数|2|2|2|3
这些就比较难找规律了。我们试图在"ab"之后加一个新的字母,观察是否会增加打印次数,可以令人疑惑的是,不管加的是"a"还是"b"都不会增加打印次数,当然,加入c的这种情况我们已经在上面讨论过了,因为c没有在之前的任何一点出现过。
那看了上面的案例,可能有人会认为,只要是在前面出现过的字母加到后方都不会增加最小打印次数。这种事情不能急,好好找一找有没有反例。
示例|abcb|abcbc
–|
最小打印次数|3|4
反例还是很好找的
但是反例是找到了,但是我们小小的脑袋充满了大大的疑惑,为什么呢?究竟怎么才能获得加入新的一位的最小打印次数呢?

遇到这种情况不能急,回头再看看题目。
我们设f就是题目的函数,例如f(“abcb”)=3,f(“abcbc”)=4
那么我们可以观察得出,就算退1万步,如果新加入一个c,这个c不占用新打印次数的情况,只有可能出现在我们在之前的数组出现过c的位置,打印机将之前出现过的某个或者数个c和最后一个一起打印了,这样才能不占用次数。
那么不难发现,f(“abcbc”)=f(“ab”)+f(“cb”)=f(“ab”)+f(“cbc”)这样才能出现我加了个c且没有让打印机再跑一遍。
那么我们通过上式发现,f(“abcbc”)虽然等于f(“ab”)+f(“cb”),但其实影响它的因素有很多,假如加入的字母c之前出现过许多次c,我们都需要对这么许多次c进行遍历。最后取一个最小值。
写一个表达式就是
f ( T + 字 符 c ) = m i n ( 所 有 的 f ( T 的 子 串 ) + f ( T 的 以 字 符 c 开 头 的 子 串 ) 与 f ( T ) + 1 ) f(T+字符c)=min(所有的 f(T的子串)+f(T的以字符c开头的子串)与f(T)+1) f(T+c)=min(f(T)+f(Tc)f(T)+1)
这样写计算机肯定看不懂,因为这里T以c开头的子串可以是任意子串,所以我们就设一个二维数组 d p dp dp,其中dp[i][j]=f(s中区间[i,j](包含i,j)的元素构成的子串)
这样我们再用计算机的语言重写上面的推到表达式
d p [ i ] [ j ] = d p [ i ] [ j − 1 ] + 1 ( 如 果 区 间 [ i , j ] 内 没 有 c h = = s [ j ] ) d p [ i ] [ j ] = M I N ( d p [ i ] [ m − 1 ] + d p [ m ] [ j − 1 ] ( 满 足 s [ m ] = = s [ j ] 且 m > i 且 m < j 的 所 有 m ) ) dp[i][j] = dp[i][j - 1] + 1 (如果区间[i,j]内没有ch == s[j]) \\ dp[i][j] = MIN(dp[i][m - 1] + dp[m][j - 1] (满足s[m] == s[j]且m > i 且m < j的所有m)) dp[i][j]=dp[i][j1]+1([i,j]ch==s[j])dp[i][j]=MIN(dp[i][m1]+dp[m][j1](s[m]==s[j]m>im<jm))
当然,作为一个合格的dp数组,它的边界条件就是每一个字符都需要1次的最小打印次数,也就是说 d p [ i ] [ i ] = 1 dp[i][i] = 1 dp[i][i]=1
最后我们需要的答案其实就是 d p [ 0 ] [ n − 1 ] dp[0][n-1] dp[0][n1],也就是包含所有元素的最小打印次数。
有了这些我们就可以进行dp工作了。
但是这个dp和平常的dp还有些许不同。普通的dp都是按顺序来,也就是说第一个下标从小向大推,第二个下标从小向大推,但我们这个不行,dp[m][j-1]中的m比i要大,就决定了我们没法用普通的方法推。
通过观察递推式可以得知,我们是间隔从小向大推,也就是我们首先遍历的元素是j-i,因为不管是dp[i][m-1]还是dp[m][j-1]还是dp[i][j-1]都是间隔要小于j-i的,我们通过遍历间隔的方式,就能将间隔由小向大推出。而且我们给的边界条件就是间隔最小的(dp[i][i]=1),我们所求的答案就是间隔最大的(dp[0][n-1]),也符合dp递推的关系。
然后就比较简单了。

C++代码

#define REP(i, j) for (int i = 0; i < j; ++i)
class Solution {
public:
    int strangePrinter(string s) {
        string st;
        for (auto & ch : s) {
            if (!(st.size() && ch == st.back()))
                st.push_back(ch);
        }
        int n = st.size();
        vector<vector<int>> dp(n, vector<int>(n, 0));
        cout << st << endl;
        REP(i, n) dp[i][i] = 1;
        for (int interval = 1; interval < n; ++interval)
            REP(i, n - interval) {
                int j = i + interval;
                dp[i][j] = dp[i][j - 1] + 1;
                if (st[i] == st[j]) dp[i][j] -= 1;
                for (int m = i + 1; m < j; ++m)
                    if (st[m] == st[j]) {
                        dp[i][j] = min(dp[i][j], dp[i][m - 1] + dp[m][j - 1]);
                        m += 1;
                    }
                        
            }
        return dp[0][n - 1];
    }
};

Rust代码

impl Solution {
    pub fn strange_printer(s: String) -> i32 {
        let n = s.len();
        let mut dp = vec![vec![0; n]; n];
        let mut st = s.into_bytes();
        for i in 0..n {
            dp[i][i] = 1;
        } 
        for interval in 1..n {
            for i in 0..(n - interval) {
                let j = i + interval;
                dp[i][j] = dp[i][j - 1] + 1;
                if st[i] == st[j] {
                    dp[i][j] -= 1;
                } 
                for m in (i + 1)..j {
                    if st[m] == st[j] {
                        dp[i][j] = dp[i][j].min(dp[i][m - 1] + dp[m][j - 1]);
                    }
                }
            }
        }
        dp[0][n - 1]
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值