• 力扣 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]\) 时,需要考虑两种情况:

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

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

    \[\large f[i][j]=\min_{k=i}^{j-1}(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\)打印其它的(不一定是同一种噢~)

    总结状态转移方程为:

    \[\large \displaystyle f[i][j]= \left\{\begin{matrix} f[i][j-1] & s[i]=s[j] \\ \displaystyle \min_{k=i}^{j-1}f[i][k]+f[k+1][j] & s[i] \neq s[j] \end{matrix}\right. \]

    三、边界与答案

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

    四、枚举的顺序

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

    \[\large \large i<=k<j \]

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

    复杂度分析

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

    实现代码

    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];
        }
    };
    
  • 相关阅读:
    Golang 需要避免踩的 50 个坑(二)
    Golang 需要避免踩的 50 个坑1
    Golang两种执行流程以及区别
    go安装配置
    h5表单亲测
    HTML5-表单 自带验证
    后台日志实现
    jQuery Validate验证框架详解,提交前验证
    用cProfile做性能分析【转】
    tmux 如何翻页
  • 原文地址:https://www.cnblogs.com/littlehb/p/16869777.html
Copyright © 2020-2023  润新知