力扣 664. 奇怪的打印机

力扣 664. 奇怪的打印机

题目描述

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

打印机每次只能打印由 同一个字符 组成的序列。

每次可以在任意起始和结束位置打印新字符,并且会覆盖掉原来已有的字符。

给你一个字符串 s ,你的任务是计算这个打印机打印它需要的最少打印次数。

示例 1

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

示例 2:

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

提示:

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

解决方案 : 动态规划

我们可以使用 动态规划 解决本题

一、状态表示

f[i][j] 表示打印完成区间 [i,j]最少操作数

Q:为什么这样设计状态?

A: 状态的设计一般是根据 尝试经验

经验:以前做过类似的题,知道状态该怎么设计。

尝试:才用最简单的一维状态表示来描述,如果发现行不通,再转为二维状态表示,实在不行再三维状态表示。

  • 尝试用一维状态表示它:f[i]:结束位置为i时的最少操作数
    这样表示行不行呢?不行呗!为啥呢?因为人家题目说了,可以从任意位置 开始、结束,你这里只记录了结束,开始的概念丢失了。

  • 尝试用二维状态表示它:f[i][j]:i开始到j结束的最少操作次数

状态设计原则

  • 状态设计是不是最终能包含答案
  • 通过简单枚举可以找出正确答案

二、状态转移方程

当我们尝试计算出 f[i][j] 时,需要考虑两种情况:

  • s[i]=s[j]
    区间两端的字符相同时,那么当我们打印左侧字符 s[i] 时,可以顺便打印右侧字符 s[j],这样我们即可忽略右侧字符对该区间的影响,只需要考虑如何尽快打印完区间 [i,j1] 即可,即此时有 f[i][j]=f[i][j1]

  • s[i]s[j]
    即区间两端的字符不同,那么我们需要分别完成该区间的左右两部分的打印。我们记两部分分别为区间 [i,k] 和区间 [k+1,j] (其中i<=k<j),此时

f[i][j]=mink=ij1(f[i][k]+f[k+1][j])

注意:这里k的取值范围值得仔细思考,因为是划分成两段,设第一段结束点为k,第二段的开始点为k+1,则有k>=i,k+1<=j,即:i<=k<j


i==k时是成立的,表示只有i点打印一次,从k+1开始至j打印其它的(不一定是同一种噢~)

总结状态转移方程为:

f[i][j]={f[i][j1]s[i]=s[j]mink=ij1f[i][k]+f[k+1][j]s[i]s[j]

三、边界与答案

边界条件为 f[i][i]=1,对于长度为 1 的区间,最少打印 1 次。最后的答案为f[0][n1]

四、枚举的顺序

注意到 f[i][j] 的计算需要用到 f[i][k]f[k+1][j] (其中

i<=k<j

)。 为了保证动态规划的计算过程满足无后效性,在实际代码中,我们需要 改变动态规划的计算顺序,从大到小地枚举 i,并从小到大地枚举 j这样可以保证当计算 f[i][j] 时, f[i][k]f[k+1][j] 都已经被计算过。

复杂度分析

时间复杂度:O(n3),其中 n 是字符串的长度。
空间复杂度:O(n2),其中 n 是字符串的长度。我们需要保存所有 n2 个状态。

实现代码

class Solution {
    const int INF = 0x3f3f3f3f;

public:
    int f[110][110];
    int strangePrinter(string s) {
        int n = s.size();
        //预求最小,先设最大
        memset(f, 0x3f, sizeof f);
        //1.因为f[i][j]依赖于f[i][k],f[k+1][j],其中k+1>i的,为了保证无后效性,需要在计算f[i]之前准备好f[k+1],所以,倒序枚举i

        //2.因为f[i][j]依赖于f[i][k],f[k+1][j],f[i][j-1],k<j的,所以在计算f[i][j]之前,f[i][k],f[i][j-1]需要提前准备好,正序枚举j
        
        //倒序枚举i,从字符串最后一位出发,向0前进
        for (int i = n - 1; i >= 0; i--) {
            //初始状态,每个起点出发,到自己结束,只包含一个字符的情况就是最简单的基本情况,此时打印次数为1,可以确定,其它无法确定的,靠状态转移方程进行转移即可填充数据
            f[i][i] = 1;

            //正序枚举j,由于i==j,也就是一个子串中只有一个字符的情况上面已经处理过了,这里只需要处理j>i的即可,并且,j的极限值是n-1, 因为j的字符串的长度上限
            for (int j = i + 1; j < n; j++) {

                if (s[i] == s[j])
                    f[i][j] = f[i][j - 1];
                else {
                    for (int k = i; k < j; k++)//注意思考与理解k的范围,尤其是带等号和不带等号的地方
                        f[i][j] = min(f[i][j], f[i][k] + f[k + 1][j]);
                }
            }
        }
        //范围i=0,j=n-1的最小打印次数就是答案
        return f[0][n - 1];
    }
};
posted @   糖豆爸爸  阅读(55)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
历史上的今天:
2019-11-08 Clion中配置使用更大的栈(防止开大一点的数组异常)
2018-11-08 Update openssh7.9 on centos6
2017-11-08 关于云平台中OFFICE预览与视频预览的解决办法
Live2D
点击右上角即可分享
微信分享提示