You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
python/TangDou/CSP-J/J2-2022/力扣 664. 奇怪的打印机.md

4.6 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

力扣 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]最少操作数

状态设计原则

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

二、状态转移

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

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

六、动态规划代码

#include <bits/stdc++.h>

using namespace std;
const int N = 110;
/*
https://leetcode.cn/problems/strange-printer/

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

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

*/
int f[N][N];

int main() {
    string s;

    cin >> s;

    int n = s.size();
    memset(f, 0x3f, sizeof f);
    for (int i = n - 1; i >= 0; i--) {
        f[i][i] = 1;

        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++)
                    f[i][j] = min(f[i][j], f[i][k] + f[k + 1][j]);
            }
        }
    }
    cout << f[0][n - 1] << endl;
    return 0;
}

七、记忆化搜索代码

#include <bits/stdc++.h>

using namespace std;
const int N = 110;
const int INF = 0x3f3f3f3f;
string s;
int f[N][N];

int dfs(int l, int r) {
    if (l == r) return 1;
    if (f[l][r]) return f[l][r];
    int res = INF;

    if (s[l] == s[r]) return dfs(l, r - 1);

    for (int i = l; i < r; i++)
        res = min(res, dfs(l, i) + dfs(i + 1, r));

    return f[l][r] = res;
}

int main() {
    cin >> s;
    cout << dfs(0, s.size() - 1) << endl;
    return 0;
}