【动态规划】【滑动窗口】C++算法:3003 执行操作后的最大分割数量

作者推荐

【动态规划】【字符串】扰乱字符串

本文涉及的基础知识点

C++算法:滑动窗口总结
动态规划

LeetCode3003 执行操作后的最大分割数量

给你一个下标从 0 开始的字符串 s 和一个整数 k。
你需要执行以下分割操作,直到字符串 s 变为 空:
选择 s 的最长前缀,该前缀最多包含 k 个 不同 字符。
删除 这个前缀,并将分割数量加一。如果有剩余字符,它们在 s 中保持原来的顺序。
执行操作之 前 ,你可以将 s 中 至多一处 下标的对应字符更改为另一个小写英文字母。
在最优选择情形下改变至多一处下标对应字符后,用整数表示并返回操作结束时得到的最大分割数量。
示例 1:
输入:s = “accca”, k = 2
输出:3
解释:在此示例中,为了最大化得到的分割数量,可以将 s[2] 改为 ‘b’。
s 变为 “acbca”。
按照以下方式执行操作,直到 s 变为空:

  • 选择最长且至多包含 2 个不同字符的前缀,“acbca”。
  • 删除该前缀,s 变为 “bca”。现在分割数量为 1。
  • 选择最长且至多包含 2 个不同字符的前缀,“bca”。
  • 删除该前缀,s 变为 “a”。现在分割数量为 2。
  • 选择最长且至多包含 2 个不同字符的前缀,“a”。
  • 删除该前缀,s 变为空。现在分割数量为 3。
    因此,答案是 3。
    可以证明,分割数量不可能超过 3。
    示例 2:
    输入:s = “aabaab”, k = 3
    输出:1
    解释:在此示例中,为了最大化得到的分割数量,可以保持 s 不变。
    按照以下方式执行操作,直到 s 变为空:
  • 选择最长且至多包含 3 个不同字符的前缀,“aabaab”。
  • 删除该前缀,s 变为空。现在分割数量为 1。
    因此,答案是 1。
    可以证明,分割数量不可能超过 1。
    示例 3:
    输入:s = “xxyz”, k = 1
    输出:4
    解释:在此示例中,为了最大化得到的分割数量,可以将 s[1] 改为 ‘a’。
    s 变为 “xayz”。
    按照以下方式执行操作,直到 s 变为空:
  • 选择最长且至多包含 1 个不同字符的前缀,“xayz”。
  • 删除该前缀,s 变为 “ayz”。现在分割数量为 1。
  • 选择最长且至多包含 1 个不同字符的前缀,“ayz”。
  • 删除该前缀,s 变为 “yz”,现在分割数量为 2。
  • 选择最长且至多包含 1 个不同字符的前缀,“yz”。
  • 删除该前缀,s 变为 “z”。现在分割数量为 3。
  • 选择最且至多包含 1 个不同字符的前缀,“z”。
  • 删除该前缀,s 变为空。现在分割数量为 4。
    因此,答案是 4。
    可以证明,分割数量不可能超过 4。
    参数范围
    1 <= s.length <= 104
    s 只包含小写英文字母。
    1 <= k <= 26

分析

变量解析

dpNotChangedpNotChange[i]表示子串s[i,m_c)的最大分割数
vSplitvSplit[inx][i]=j表示:将s[i]修改成’a’+inx,其它字符不变的情况下,s[i,m_c)的最长前缀是s[i,j)

不修改任何字符的情况下,s拆分成m个子串,分别为[li1,ri1),[li2,ri2)… [lim,rim)。
显然 li1等于0,rim等于m_c, ri1等于li2…
分以下情况:

  • 一,没有改变任何字符。
  • 二,改变某个字后,[lix,rix)发生改变,变成[lix,nix),只需要考虑nix < rix,原因见下文证明一

情况二:
分两层循环:

  • 第一层循环:枚举不修改字符的子串。
  • 第二层循环:枚举nix。

两层循环的时间复杂度为:O(s.length)。
枚举nix的时候,分两种情况:
一,s[nix]没有改变,s[nix,m_c)的最大分割数就是dpNotChange[rix]。前提条件是:

  • k<26 否则无论变成k+1。
  • 包括nix,目前已经有k个不同字符
  • [lix,nix]至少有k+1个字符,由于其只有k个不同字符,所以至少有两个字符相同,去掉nix,[lix,nix)至少还有一个相同字符,将其改成k个字符以外的字符。只能证明[lix,rix]有k+1个字符,不能证明是第一个rix。假定第一个rix是rix1,rix会被rix1淘汰,所以不会造成错误。原因见证明一
    二,枚举s[nix]改成’a’到’z’。通过vSplit查询第一个前缀。

通过滑动窗口计算vSplit[inx][i-1]

s[i,rightk] 共有k个字符,rightk取最小值;是s[i,rightk1]共有k+1个字符,rightk1取最小值。如果没有足够的字符取m_c。
countk 记录了这k个字符,countk1记录了k+1个字符。

countk 包括inx+‘a’countk1的数量为k+1结果为:rightk1
countk 包括inx+‘a’countk1的数量不够k+1结果为:m_c
countk 不包括inx+‘a’countk的数量为k结果为:rightk
countk 不包括inx+‘a’countk的数量不够k结果为:m_c

证明一 i1<=12则dpNotChange[i1] >= dpNotChange[i2]

特殊情况一:如果s[i1,m_c)和s[i2,m_c) 都不到k+1个字符。则两者都为1。
特殊情况二:s[i2,m_c) 不到k+1个字符,s[i1,m_c)有k+1个字符,则前者为1,后者超过1。
s[i1,m_c) 不到k+1个字符,s[i2,m_c)有k+1个字符,这种i情况不存在。
如果两者都有k+1字符
则s[i2,m_c)可以拆分s[i2,i4)和s[i4,m_c)
s[i1,m_c)可以拆分成s[i1,i3)和s[i3,m_c)
则i3<=i4,i1变i3变大;i2变i4,每次迭代i1和i2都变大,若干次后一定会变成特殊情况一或特殊情况二。
假定i3>i4
s[i2,i4]刚好k+1个字符,那么s[i1,i2)+s[i2,i4]+s(i4+i3)的字符数大于等于k+1,这三个子串加在一起就是s[i1,i3),与s[i1,i3)只有k个字符矛盾。

代码

核心代码

template<class KEY>
class CKeyCount
{
public:void Add(const KEY& key, int iCount){Cnt[key] += iCount;if (0 == Cnt[key]){Cnt.erase(key);}}std::unordered_map<KEY, int> Cnt;
};class Solution {
public:int maxPartitionsAfterOperations(string s, int k) {m_c = s.length();vSplit[inx][i]=j表示:将s[i]修改成'a'+inx,其它字符不变的情况下,s[i,m_c)的最长前缀是s[i,j)vector<vector<int>> vSplit(26,vector<int>(m_c, m_c));		for (int inx = 0; inx < 26; inx++){			CKeyCount<char> countk1,countk;	//s[i,rightk] 共有k个字符,rightk取最小值;是s[i,rightk1]共有k+1个字符,rightk取最小值。如果没有足够的字符取m_cfor (int i =1, rightk1 =i-1,rightk=i-1; i < m_c;i++ ){	//s(left,right]和inx+'a' 刚好k+1while ((rightk +1 < m_c)&&(countk.Cnt.size() < k )){countk.Add(s[++rightk],1);}while ((rightk1 + 1 < m_c) && (countk1.Cnt.size() <= k)){countk1.Add(s[++rightk1], 1);}if (countk.Cnt.count('a' + inx)){vSplit[inx][i - 1] = (k + 1 == countk1.Cnt.size()) ? rightk1 : m_c;}else{vSplit[inx][i - 1] = (k  == countk.Cnt.size()) ? rightk : m_c;}countk.Add(s[i], -1);countk1.Add(s[i], -1);}}vector<int> dpNotChange(m_c + 1, 0);//dp1[i]=x表示 ,在不修改的情况下 s[i,m_c)最多可以分割x次for (int i = m_c - 1; i >= 0; i--){int inx = vSplit[s[i]-'a'][i];dpNotChange[i] =  dpNotChange[inx] + 1;}int iRet = dpNotChange.front();int iPreNum = 0;for (int i = 0; i < m_c; i = vSplit[s[i]-'a'][i]){iPreNum++;set<char> setHas;for (int j = i; j < vSplit[s[i] - 'a'][i]; j++){if (setHas.size()==k){for (char ch = 'a'; ch <= 'z'; ch++){//修改了j,且s[j]是新的子串的开始if (!setHas.count(ch)){const int inx = vSplit[ch - 'a'][j];iRet = max(iRet, iPreNum + 1 + dpNotChange[inx]);}}}				setHas.emplace(s[j]);if ((setHas.size() == k)&&(k< 26)&&(j-i+1 > k  )){iRet = max(iRet, iPreNum + dpNotChange[j]);}}}return iRet;}int m_c;
};

测试用例

template<class T>
void Assert(const T& t1, const T& t2)
{assert(t1 == t2);
}template<class T>
void Assert(const vector<T>& v1, const vector<T>& v2)
{if (v1.size() != v2.size()){assert(false);return;}for (int i = 0; i < v1.size(); i++){Assert(v1[i], v2[i]);}
}int main()
{string s;int k;{Solution sln;s = "baac", k = 3;auto res = sln.maxPartitionsAfterOperations(s, k);Assert(2, res);}{Solution sln;s = "accca", k = 2;auto res = sln.maxPartitionsAfterOperations(s, k);Assert(3, res);}{Solution sln;s = "aabaab", k = 3;auto res = sln.maxPartitionsAfterOperations(s, k);Assert(1, res);}{Solution sln;s = "xxyz", k = 1;auto res = sln.maxPartitionsAfterOperations(s, k);Assert(4, res);}
}

两个错误代码

都没有考虑s[nix]会修改。

class Solution {
public:
int maxPartitionsAfterOperations(string s, int k) {
m_c = s.length();
vector<vector> vSplit(2, vector(m_c, m_c));
{
CKeyCount count;
for (int i = 0, right = 0; i < m_c; i++)
{
while ((right < m_c) && (count.Cnt.size() <= k))
{
count.Add(s[right++], 1);
}
vSplit[0][i] = right;
count.Add(s[i], -1);
}
}
{
CKeyCount count;
for (int i = 0, right = 0; i < m_c; i++)
{
while ((right < m_c) && (count.Cnt.size() < k))
{
if ((count.Cnt.size() == k) && (right - i > k))
{
break;
}
count.Add(s[right++], 1);
}
vSplit[1][i] = right;
count.Add(s[i], -1);
}
}
vector<vector> dp(2, vector(m_c+1, 0));
dp[0][0] = 0;
dp[1][1] = -m_c;
for (int i = 0; i < m_c; i++)
{
//没改字符=>没改字符
dp[0][vSplit[0][i]] = max(dp[0][vSplit[0][i]], dp[0][i] + 1);
//没改字符=>改字符
dp[1][vSplit[1][i]] = max(dp[1][vSplit[1][i]], dp[0][i] + 1);
//改字符=>改字符
dp[1][vSplit[0][i]] = max(dp[1][vSplit[0][i]], dp[1][i] + 1);
}
return max(dp[0].back(), dp[1].back());
}
int m_c;
};

class Solution {
public:
int maxPartitionsAfterOperations(string s, int k) {
m_c = s.length();
vector vSplit(m_c, m_c);//vSplit[i]=j表示,在不修改的情况s[i,m_c)的最长前缀是s[i,j)
CKeyCount count;
for (int i = 0, right = 0; i < m_c; i++)
{
while ((right < m_c) && (count.Cnt.size() <= k))
{
count.Add(s[right++], 1);
}
vSplit[i] = right;
count.Add(s[i], -1);
}

	vector<int> dpNotChange(m_c+1, 0);//dp1[i]=x表示 ,在不修改的情况下 s[i,m_c)最多可以分割x次for (int i = m_c - 1; i >= 0; i--){dpNotChange[i] = max(dpNotChange[i], dpNotChange[vSplit[i]] + 1);}int iRet = dpNotChange.front();int iPreNum = 0;for (int i = 0; i < m_c; i = vSplit[i]){iPreNum++;CKeyCount<char> cnt;for (int j = i; j < vSplit[i]; j++){cnt.Add(s[j], 1);if ((cnt.Cnt.size() == k) && (j - i + 1 > k)){iRet = max(iRet, iPreNum + dpNotChange[j]);}}}return iRet;
}
int m_c;

};

扩展阅读

视频课程

有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771

如何你想快

速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176

相关

下载

想高屋建瓴的学习算法,请下载《喜缺全书算法册》doc版
https://download.csdn.net/download/he_zhidan/88348653

我想对大家说的话
闻缺陷则喜是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。
如果程序是一条龙,那算法就是他的是睛

测试环境

操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 **C+

+17**
如无特殊说明,本算法用**C++**实现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/619822.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

四大会计假设

目录 一. 会计主体假设二. 持续经营假设三. 会计期间假设四. 货币计量假设 \quad \quad 一. 会计主体假设 \quad 会计主体: 会计工作为其服务的特定单位或组织。 会计主体的定义 1.具有一定数量的资金。 2.进行独立的生产经营或其他活动。 3.实行独立核算。 \quad 会计主体假设…

leetcode 125. 验证回文串

题目&#xff1a; 如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后&#xff0c;短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。 字母和数字都属于字母数字字符。 给你一个字符串 s&#xff0c;如果它是 回文串 &#xff0c;返回 true &…

阅读文献-胃癌

写在前面 今天先不阅读肺癌的了&#xff0c;先读一篇胃癌的文章 文献 An individualized stemness-related signature to predict prognosis and immunotherapy responses for gastric cancer using single-cell and bulk tissue transcriptomes IF:4.0 中科院分区:2区 医学…

JFinal综合信息管理系统

项目地址&#xff1a;mendianyu/AdvancedManagement: 综合信息管理系统 (github.com) 项目演示地址&#xff1a;软件构造大作业演示视频_哔哩哔哩_bilibili 项目功能 一&#xff1a;基于Jfinal构建信息管理系统&#xff0c;要求包含用户管理&#xff0c;翻译业务模块管理&…

初识Spring

1.Spring官网&#xff1a; 2.官网学习的顺序&#xff1a;先学Spring,再学SpringBoot,然后SpringCloud,Spring Cloud Data Flow。 3.Spring Framework界面&#xff1a; 4.github上的源代码&#xff1a; 5.进入使用说明文档&#xff1a; 主要是根据这个进行学习的。 6.我们用mave…

Linux Debian12使用VSCode和Python搭建flask开发环境

一、安装VSCode 在Linux Debian12系统上安装VSCode教程可以参考网上相关教程。 二、安装Python 打开VSCode&#xff0c;安装python和python扩展包&#xff0c;如下图所示&#xff1a; 三、创建Python虚拟环境 1.新建文件夹testFlask 2.用vscode打开文件夹testFlask&#xf…

前端 TS 语法继承 多态 修饰符 readonly 抽象类 ts 基本写法 可选 剩余参数 函数重载 接口 类(3)

继承 继承之间的叫法 A类继承了B类&#xff0c;那么A类叫做子类&#xff0c;B类叫成基类 子类 ---》派生类 基类 ---》超类&#xff08;父类&#xff09; // 继承之间的叫法 // A类继承了B类&#xff0c;那么A类叫做子类&#xff0c;B类叫成基类 // 子类 ---》派生类 // 基类 …

阅读笔记lv.1

阅读笔记 sql中各种 count结论不同存储引擎计算方式区别count() 类型 责任链模式常见场景例子&#xff08;闯关游戏&#xff09; sql中各种 count 结论 innodb count(*) ≈ count(1) > count(主键id) > count(普通索引列) > count(未加索引列)myisam 有专门字段记录…

计算机体系结构----缓存一致性/多处理机

本文严禁转载&#xff0c;仅供学习使用。参考资料来自中国科学院大学计算机体系结构课程PPT以及《Digital Design and Computer Architecture》、《超标量处理器设计》、同济大学张晨曦教授资料。如有侵权&#xff0c;联系本人修改。 本文衔接上文计算机体系结构----存储系统 …

python爬虫,验证码识别,携带cookies请求

古诗词网案例!!! 识别验证码类型: # 此处用到的图片验证码识别网址为:http://ttshitu.com/ 图鉴 import base64 import json import requests # 一、图片文字类型(默认 3 数英混合): # 1 : 纯数字 # 1001:纯数字2 # 2 : 纯英文 # 1002:纯英文2 # 3 : 数英混合 # 1003:…

用通俗易懂的方式讲解:大模型微调方法总结

大家好&#xff0c;今天给大家分享大模型微调方法&#xff1a;LoRA,Adapter,Prefix-tuning&#xff0c;P-tuning&#xff0c;Prompt-tuning。 文末有大模型一系列文章及技术交流方式&#xff0c;传统美德不要忘了&#xff0c;喜欢本文记得收藏、关注、点赞。 文章目录 1、LoRA…

轻松查看WiFi密码的神奇脚本,让你忘记密码也不再是问题

说在前面 &#x1f388;本文介绍了一个便捷的脚本&#xff0c;可以帮助你获取电脑中保存的所有Wi-Fi网络的密码。不再需要担心忘记Wi-Fi密码或手动查找密码的麻烦&#xff0c;只需运行脚本即可一键获取。 一、引言 互联网的普及让我们离不开Wi-Fi网络&#xff0c;但忘记密码时…

怎么安装es、kibana(单点安装)

1.部署单点es 1.1.创建网络 因为我们还需要部署kibana容器&#xff0c;因此需要让es和kibana容器互联。这里先创建一个网络&#xff1a; docker network create es-net1.2.加载镜像 这里我们采用elasticsearch的7.12.1版本的镜像&#xff0c;这个镜像体积非常大&#xff0c…

用C语言采集亚马逊amazon产品数据

上一篇文章我是用C写的一个爬取亚马逊的爬虫程序&#xff0c;相信大家已经看过了&#xff0c;这次呢&#xff0c;我依然使用C语言来写一个爬虫&#xff0c;大体上思路是和之前一样&#xff0c;只是支持的库以及语法有些区别&#xff0c;具体的呢我会一一解释出来&#xff0c;方…

android自定义来电秀UI

简单来电秀功能&#xff0c;效果如图&#xff1a; 底部附上demo 一、新建一个PhoneCallService服务&#xff0c;在服务中监听来电等状态&#xff0c;且控制UI显示 public class PhoneCallService extends InCallService {private final Call.Callback callback new Call.Ca…

仿真机器人-深度学习CV和激光雷达感知(项目2)day01

文章目录 前言项目介绍功能与技术简介硬件要求环境配置虚拟机运行项目demo 前言 &#x1f4ab;你好&#xff0c;我是辰chen&#xff0c;本文旨在准备考研复试或就业 &#x1f4ab;本文内容是我为复试准备的第二个项目 &#x1f4ab;欢迎大家的关注&#xff0c;我的博客主要关注…

如何给AI下达精准的指令,哪些提示词对于AI是有效的?

刚上手那会&#xff0c;我倾向于将 prompt 翻译为“指令”&#xff0c;但这并不精确。“指令”通常对应instructions&#xff0c;属于 prompt 中的纯指令部分&#xff0c;通常是一个动宾结构&#xff08;做什么&#xff09;。剩下的部分更多是描述&#xff08;describe&#xf…

stable-diffusion 学习笔记

从效果看Stable Diffusion中的采样方法 参考&#xff1a;Ai 绘图日常 篇二&#xff1a;从效果看Stable Diffusion中的采样方法_软件应用_什么值得买 大概示例&#xff1a;

Linux:信号

目录 1.信号 2.信号的过程 a.信号的产生 1:键盘产生, 异常产生 2:系统调用产生信号 3.软件条件产生信号 4.硬件异常产生信号 b.信号的发送 c.信号的处理 d.总结与思考 3.信号保存 1.信号及其它相关常见概念 2.在内核中的表示 3.sigset_t 4. 信号集操作函数 4.信…

Arduino开发实例-AS608光学指纹传感器驱动

AS608光学指纹传感器驱动 文章目录 AS608光学指纹传感器驱动1、AS608光学指纹传感器介绍2、硬件准备及接线3、代码实现3.1 指纹录入3.2 指纹匹配验证1、AS608光学指纹传感器介绍 AS608 光学指纹传感器可用于扫描指纹,它也可以通过串行通信将处理后的数据发送到微控制器。 所有…