【动态规划】【滑动窗口】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 会计主体假设…

openpyxl库根据模板生成文件api接口返回

openpyxl库是基于excel 2010 xlsx/xlsm文件读写的Python库&#xff08;请找寻xls文件读写的用户去搜寻pandas、xlrd 和 xlwt、pyexcel等库&#xff09;。安装openpyxl库&#xff1a; # 如果是pip pip install openpyxl # 如果是conda环境 conda install openpyxl 创建工作簿和工…

leetcode 125. 验证回文串

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

FreeRTOS——消息队列

学习目标 理解队列的概念掌握消息队列开发流程掌握基本数据类型消息队列掌握复杂数据类型消息队列学习内容 队列 队列(Queue)是一种数据结构,用于存储和管理元素的线性集合。它遵循先进先出(FIFO,First-In-First-Out)的原则,即最先进入队列的元素将首先被移出队列。 …

阅读文献-胃癌

写在前面 今天先不阅读肺癌的了&#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…

单例模式实现案例

单例模式实现案例 文章目录 单例模式实现案例**饿汉式****枚举饿汉式****懒汉式****双检锁懒汉式****内部类懒汉式** 单例模式概念&#xff1a; 单例模式是一种创建型设计模式&#xff0c;确保一个类只有一个实例&#xff0c;并提供全局访问点。这对于需要在系统中共享某个资源…

Textract python 很好用的库

如何在Python中使用Textract的文章&#xff1a; 在Python中使用Textract Textract是一个强大的文本提取工具&#xff0c;它可以从各种类型的文件中提取文本&#xff0c;包括PDF&#xff0c;Word文档&#xff0c;PowerPoint演示文稿&#xff0c;JPEG图像等。以下是如何在Python…

如何配置一台高性能的IBM服务器

在当今信息化时代&#xff0c;服务器作为企业数据存储、处理和传输的核心设备&#xff0c;其性能的优劣直接关系到企业的运营效率和安全性。而IBM作为全球领先的企业级服务器提供商&#xff0c;其产品广泛应用于各行各业。本文将为你详细介绍如何配置一台高性能的IBM服务器&…

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

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

QT中线程的退出分析

QT中线程的退出分析 前言开发环境代码的整改第一次修改第一次修改引起的问题问题原因分析解决方法第二次修改前言 软件实际开发过程中有好几处要实现这么一个功能:PC端软件“应用程序重启” ,本来这是一段比较简单的事情,但是因为重启软件报通信异常(错误日志中有记录通信…

前端 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 有专门字段记录…

Open3D 计算点云空间分布密度(16)

Open3D 计算点云空间分布密度(16) 一、算法介绍二、算法实现1.代码2.结果一、算法介绍 使用八叉树来估计点云密度可以通过统计每个体素内部的点的数量来实现。Open3D库中的VoxelGrid数据结构可以帮助完成这一任务。 二、算法实现 1.代码 代码如下(示例): import open3…

langchain-Agent-Agent类型和自定义Agent(代码)

本篇主要用于记忆Agent相关代码&#xff0c;不对各个类型Agent的功能和原理进行描述。 文章目录 代理类型reactplan&executestructured-chat 自定义代理 代理类型 react from langchain.agents import load_tools from langchain.agents import initialize_agent from l…

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

本文严禁转载&#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;但忘记密码时…