C++动态规划算法的应用:得到 K 个半回文串的最少修改次数 原理源码测试用例
本文涉及的基础知识点
动态规划
题目
得到 K 个半回文串的最少修改次数
给你一个字符串 s 和一个整数 k ,请你将 s 分成 k 个 子字符串 ,使得每个 子字符串 变成 半回文串 需要修改的字符数目最少。
请你返回一个整数,表示需要修改的 最少 字符数目。
注意:
如果一个字符串从左往右和从右往左读是一样的,那么它是一个 回文串 。
如果长度为 len 的字符串存在一个满足 1 <= d < len 的正整数 d ,len % d == 0 成立且所有对 d 做除法余数相同的下标对应的字符连起来得到的字符串都是 回文串 ,那么我们说这个字符串是 半回文串 。比方说 “aa” ,“aba” ,“adbgad” 和 “abab” 都是 半回文串 ,而 “a” ,“ab” 和 “abca” 不是。
子字符串 指的是一个字符串中一段连续的字符序列。
示例 1:
输入:s = “abcac”, k = 2
输出:1
解释:我们可以将 s 分成子字符串 “ab” 和 “cac” 。子字符串 “cac” 已经是半回文串。如果我们将 “ab” 变成 “aa” ,它也会变成一个 d = 1 的半回文串。
该方案是将 s 分成 2 个子字符串的前提下,得到 2 个半回文子字符串需要的最少修改次数。所以答案为 1 。
示例 2:
输入:s = “abcdef”, k = 2
输出:2
解释:我们可以将 s 分成子字符串 “abc” 和 “def” 。子字符串 “abc” 和 “def” 都需要修改一个字符得到半回文串,所以我们总共需要 2 次字符修改使所有子字符串变成半回文串。
该方案是将 s 分成 2 个子字符串的前提下,得到 2 个半回文子字符串需要的最少修改次数。所以答案为 2 。
示例 3:
输入:s = “aabbaa”, k = 3
输出:0
解释:我们可以将 s 分成子字符串 “aa” ,“bb” 和 “aa” 。
字符串 “aa” 和 “bb” 都已经是半回文串了。所以答案为 0 。
参数范围:
2 <= s.length <= 200
1 <= k <= s.length / 2
s 只包含小写英文字母。
分析
第一轮:vector<vector> m_aDCenger[2][101]; 四维数组:第一维,0表示奇数长度,1表示偶数长度。 第二维:d。第三维:中心。第四维半长长度。值记录变成:半回文需要改变的次数。
第二轮:int m_vNeedNum[200][201] 记录s[left,r)变成半回文需要改变的次数。
第三轮:三层循环,第一层循环:枚举k,第二层循环枚举s[0,j]。第三轮循环枚举m,[0,m]和(m,j]。
三轮时间复杂度都是:O(nnn);
核心代码
class Solution {
public:
int minimumChanges(string s, int k) {
m_c = s.length();
Init(s);
Init2(s);
vector pre(m_c);//pre[j]将s[0,j]拆分成i-1个子字符串,这些子串全部半回文的需要改变的字符数
for (int j = 0; j < m_c; j++)
{
pre[j] = m_vNeedNum[0][j + 1];
}
for (int i = 2; i <= k; i++)
{
vector dp(m_c);
for (int j = i - 1; j < m_c; j++)
{//拆分成[0,m]和(m,j]([m+1,j+1)),前者i-1个子串,后者一个子串
int iMin = INT_MAX;
for (int m = max(0,i-2); m < j; m++)
{
const int cur = pre[m] + m_vNeedNum[m + 1][j + 1];
iMin = min(iMin, cur);
}
dp[j] = iMin;
}
pre.swap(dp);
}
return pre.back();
}
void Init(std::string& s)
{
for(int i = 0 ; i < 2 ; i++ )
for (int j = 0; j <= m_c / 2; j++)
{
m_aDCenger[i][j].assign(m_c+1, vector((m_c+1)/2+1));
}
for (int d = 1; d <= m_c/2; d++)
{
for (int center = 0; center < m_c; center++)
{
int halfLen = 1;
while ((center + d* (halfLen-1) < m_c) && (center - d* (halfLen - 1) >= 0) )
{
m_aDCenger[1][d][center][halfLen] = m_aDCenger[1][d][center][halfLen - 1] +(s[center + d * (halfLen - 1)] != s[center - d * (halfLen - 1)]) ;
halfLen++;
}
}
for (int center = 0; center < m_c; center++)
{//偶数半回文
int halfLen = 1;
while ((center + d * halfLen < m_c) && (center - d * (halfLen - 1) >= 0) )
{
m_aDCenger[0][d][center][halfLen] = m_aDCenger[0][d][center][halfLen - 1] + (s[center + d * halfLen] != s[center - d * (halfLen - 1)]);
halfLen++;
}
}
}
}
void Init2(std::string& s)
{
memset(m_vNeedNum, sizeof(m_vNeedNum), 0);
for (int left = 0; left < m_c; left++)
{
for (int r = left + 1; r <= m_c; r++)
{
const int subLen = r - left;
int iNeed = 1000*1000;
for (int d = 1; (d * d <= subLen)&&(subLen >1); d++)
{
if (0 != subLen % d)
{
continue;
}
{
const int iCurNeed = DoLeftRightD(left, r, d);
iNeed = min(iNeed, iCurNeed);
}
if(d >1 )
{
const int iCurNeed = DoLeftRightD(left, r, subLen/d);
iNeed = min(iNeed, iCurNeed);
}
}
m_vNeedNum[left][r] = iNeed;
}
}
}
int DoLeftRightD(int left,int r,int d)
{
const int subLen = r - left;
const int len = subLen / d;
const auto& arr = m_aDCenger[len % 2];
const int midIndex = (len-1)/2;
int iCurNeed = 0;
for (int begin = 0; begin < d; begin++)
{
const int center = left + begin + midIndex * d;
iCurNeed += arr[d][center][(len + 1) / 2];
}
return iCurNeed;
}
int m_c;
vector<vector> m_aDCenger[2][101];
int m_vNeedNum[200][201];
};
测试用例
template
void Assert(const vector& v1, const vector& v2)
{
if (v1.size() != v2.size())
{
assert(false);
return;
}
for (int i = 0; i < v1.size(); i++)
{
assert(v1[i] == v2[i]);
}
}
template
void Assert(const T& t1, const T& t2)
{
assert(t1 == t2);
}
int main()
{
//string s = “bbacccbbaabbddddddddddddddddddddddddddddddddddddddddddddbbacccbbaabbdddddddddddddddddddddddddddddddddddddddddddddbbbacccbbaabbddddddddddddddddddddbbacccbbaabbdddddddddddddddddddddddddddddddddddddddddd”;
string s = “abcac”;
int k = 2;
Solution slu;
auto res = slu.minimumChanges(s,k);
Assert(1, res);
//CConsole::Out(res);
}
小幅改进
空间复杂度降为O(nn)。一,预处理的时候,利用当前d,更新m_vLeftRightToNeedChange。更新完旧d的信息就不需要了。二,不需要分奇数长度和偶数长度回文。长度本身可以分奇偶了。
Init2共3层循环,时间复杂度是:O(nn),第三层第四层,时间复杂度共O(n)。第三层循环的时间复杂度是: n/d,第四层时间复杂度是d。
新代码
class Solution {
public:int minimumChanges(string s, int k) {m_c = s.length(); m_vLeftRightToNeedChange.assign(m_c, vector<int>(m_c, m_iNotMay));for (int d = 1; d <= m_c / 2; d++){Init(s,d);Init2(s,d);}vector<int> pre(m_c);//pre[j]将s[0,j]拆分成i-1个子字符串,这些子串全部半回文的需要改变的字符数for (int j = 0; j < m_c; j++){pre[j] = m_vLeftRightToNeedChange[0][j];}for (int i = 2; i <= k; i++){vector<int> dp(m_c);for (int j = i - 1; j < m_c; j++){//拆分成[0,m]和(m,j]([m+1,j])),前者i-1个子串,后者一个子串int iMin = INT_MAX;for (int m = max(0,i-2); m < j; m++){const int cur = pre[m] + m_vLeftRightToNeedChange[m + 1][j];iMin = min(iMin, cur);}dp[j] = iMin;}pre.swap(dp);}return pre.back();}void DoCenter(int center, bool bEven,int d,const string& s){int left = center, r = left + d*bEven;if (r >= m_c){return;}m_vDLeftRightToNeedChange[left][r] = (s[left] != s[r]);left -= d;r += d;while ((r < m_c) && (left >= 0 )){m_vDLeftRightToNeedChange[left][r] = m_vDLeftRightToNeedChange[left + d][r - d] + (s[left] != s[r]);left -= d;r += d;}}void Init(std::string& s,int d ){m_vDLeftRightToNeedChange.assign(m_c, vector<int>(m_c, m_iNotMay)); for (int center = 0; center < m_c; center++){DoCenter(center, true, d,s);DoCenter(center, false, d,s);}}void Init2(std::string& s, int d){ for (int left = 0; left < m_c; left++){for (int iSubLen = 2; left+ iSubLen*d <= m_c ; iSubLen++){const int r = left + iSubLen * d;int iNeedChange = 0;for (int j = 0; j < d; j++){iNeedChange += m_vDLeftRightToNeedChange[left+j][r - d+j];}m_vLeftRightToNeedChange[left][r - 1] = min(m_vLeftRightToNeedChange[left][r - 1],iNeedChange);}}} const int m_iNotMay = 1000 * 1000;int m_c;vector<vector<int>> m_vDLeftRightToNeedChange;//m_vDLeftRightToNeedChange[left][r],表示当前d,str[left,left+d....r]是回文的最少次数vector<vector<int>> m_vLeftRightToNeedChange;//m_vLeftRightToNeedChange[left][r]表示s[left,r]变成半回文的最少改变次数
};
扩展阅读
视频课程
有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步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++动态规划算法的应用:得到 K 个半回文串的最少修改次数 原理源码测试用例
本文涉及的基础知识点 动态规划 题目 得到 K 个半回文串的最少修改次数 给你一个字符串 s 和一个整数 k ,请你将 s 分成 k 个 子字符串 ,使得每个 子字符串 变成 半回文串 需要修改的字符数目最少。 请你返回一个整数,表示需要修改的 最少…...
Pyside6 QFileDialog
Pyside6 QFileDialog Pyside6 QFileDialog常用函数getOpenFileNamegetOpenFileNamesgetExistingDirectorygetSaveFileName 程序界面程序主程序 Pyside6 QFileDialog提供了一个允许用户选择文件或目录的对话框。关于QFileDialog的使用可以参考下面的文档 https://doc.qt.io/qtfo…...
Leetcode1793. Maximum Score of a Good Subarray
给定一个数组和一个下标 k k k 子数组 ( i , j ) (i,j) (i,j)分数定义为 min ( n u m s [ i ] , n u m s [ i 1 ] , ⋯ , n u m s [ j ] ) ∗ ( j − i 1 ) \min\left(nums[i], nums[i 1],\cdots, nums[j]\right)*\left(j-i1\right) min(nums[i],nums[i1],⋯,nums[j])∗(…...
只需五步,在Linux安装chrome及chromedriver(CentOS)
一、安装Chrome 1)先执行命令下载chrome: wget https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpm2)安装chrome yum localinstall google-chrome-stable_current_x86_64.rpm看到下图中的Complete出现则代表安装…...
第01章-Java语言概述
目录 1 常见DOS命令 常用指令 相对路径与绝对路径 2 转义字符 3 安装JDK与配置环境变量 JDK与JRE JDK的版本 JDK的下载 JDK的安装 配置path环境变量 4 Java程序的编写与执行 5 Java注释 6 Java API文档 7 Java核心机制:JVM 1 常见DOS命令 DOS(…...
Spring | Spring Cache 缓存框架
Spring Cache 缓存框架: Spring Cache功能介绍Spring Cache的Maven依赖Spring Cache的常用注解EnableCaching注解CachePut注解Cacheable注解CacheEvict注解 Spring Cache功能介绍 Spring Cache是Spring的一个框架,实现了基于注解的缓存功能。只需简单加一…...
雷达开发的基本概念fft,cfar,以及Clutter, CFAR,AoA
CFAR Constant False-Alarm Rate的缩写。在雷达信号检测中,当外界干扰强度变化时,雷达能自动调整其灵敏度,使雷达的虚警概率保持不变。具有这种特性的接收机称为恒虚警接收机。雷达信号的检测总是在干扰背景下进行的,这些干扰包括…...
什么是大数据测试?有哪些类型?应该怎么测?
随着目前世界上各个国家使用大数据应用程序或应用大数据技术场景的数量呈指数增长,相应的,对于测试大数据应用时所需的知识与大数据测试工程师的需求也在同步增加。 针对大数据测试的相关技术已慢慢成为当下软件测试人员需要了解和掌握的一门通用技术。…...
03-垃圾收集策略与算法
垃圾收集策略与算法 程序计数器、虚拟机栈、本地方法栈随线程而生,也随线程而灭;栈帧随着方法的开始而入栈,随着方法的结束而出栈。这几个区域的内存分配和回收都具有确定性,在这几个区域内不需要过多考虑回收的问题,因…...
1.AUTOSAR的架构及方法论
在15、16年之前,AUTOSAR这个东西其实是被国内很多大的OEM或者供应商所排斥的。为什么?最主要的原因还是以前采用手写底层代码+应用层模型生成代码的方式进行开发。每个供应商或者OEM都有自己的软件规范或者技术壁垒,现在提个AUTOSAR想搞统一,用一个规范来收割汽车软件供应链…...
Kotlin中的List集合
在Kotlin中,List集合用于存储一组有序的元素。List集合分为可变集合(MutableList)和不可变集合(List)。本篇博客将分别介绍可变集合和不可变集合,并提供相关的API示例代码。 不可变集合(List&a…...
微信小程序WeUI项目weui-miniprogram如何运行起来?
微信小程序WeUI项目weui-miniprogram如何运行起来? 解决方法: 1、下载 https://github.com/wechat-miniprogram/weui-miniprogram 2、在项目根目录weui-miniprogram-master执行以下命令安装依赖: npm install 3、继续执行编译命令: npm r…...
MapReduce编程:检索特定群体搜索记录和定义分片操作
文章目录 MapReduce 编程:检索特定群体搜索记录和定义分片操作一、实验目标二、实验要求及注意事项三、实验内容及步骤 附:系列文章 MapReduce 编程:检索特定群体搜索记录和定义分片操作 一、实验目标 熟悉MapReduce编程涉及的主要类和接口…...
pytorch 入门 (四)案例二:人脸表情识别-VGG16实现
实战教案二:人脸表情识别-VGG16实现 本文为🔗小白入门Pytorch内部限免文章 参考本文所写记录性文章,请在文章开头注明以下内容,复制粘贴即可 🍨 本文为🔗小白入门Pytorch中的学习记录博客🍦 参…...
数据结构--线性表回顾
目录 线性表 1.定义 2.线性表的基本操作 3.顺序表的定义 3.1顺序表的实现--静态分配 3.2顺序表的实现--动态分配 4顺序表的插入、删除 4.1插入操作的时间复杂度 4.2顺序表的删除操作-时间复杂度 5 顺序表的查找 5.1按位查找 5.2 动态分配的方式 5.3按位查找的时间…...
ChatGPT(1):ChatGPT初识
1 ChatGPT原理 ChatGPT 是基于 GPT-3.5 架构的一个大型语言模型,它的工作原理涵盖了深度学习和自然语言处理技术。以下是 ChatGPT 的工作原理的一些关键要点: 神经网络架构:ChatGPT 的核心是一个深度神经网络,采用了变种的 Tran…...
PostgreSQL 插件 CREATE EXTENSION 原理
PostgreSQL 提供了丰富的数据库内核编程接口,允许开发者在不修改任何 Postgres 核心代码的情况下以插件的形式将自己的代码融入内核,扩展数据库功能。本文探究了 PostgreSQL 插件的一般源码组成,梳理插件的源码内容和实现方式;并介…...
Android常见分区
一、Google官方标准分区 1. Boot分区 包含Linux内核和一个最小的root文件系统(装载到ramdisk中),用于挂载系统和其他的分区并开始Runtime。正如名字所代表的意思(注:boot的意思是启动),这个分区使Android设备可以启动…...
华为鸿蒙4谷歌GMS安装教学
目录 问题描述 参考视频 教学视频1 配套文档 教学视频2 资源包(配套视频1) 设备未经 play 保护机制认证 问题描述 很多国外的最新应用需要再Google商店才能下载比如ChatGPT 华为手机不支持 Google Play 服务的原因主要是由于谷歌服务框架(GMS)未…...
原型设计工具:Balsamiq Wireframes 4.7.4 Crack
原型设计工具:Balsamiq Wireframes是一种快速的低保真UI 线框图工具,可重现在记事本或白板上绘制草图但使用计算机的体验。 它确实迫使您专注于结构和内容,避免在此过程后期对颜色和细节进行冗长的讨论。 线框速度很快:您将产生更多想法&am…...
mongodb源码分析session执行handleRequest命令find过程
mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程,并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令,把数据流转换成Message,状态转变流程是:State::Created 》 St…...
Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件
今天呢,博主的学习进度也是步入了Java Mybatis 框架,目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学,希望能对大家有所帮助,也特别欢迎大家指点不足之处,小生很乐意接受正确的建议&…...
解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八
现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet,点击确认后如下提示 最终上报fail 解决方法 内核升级导致,需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...
全球首个30米分辨率湿地数据集(2000—2022)
数据简介 今天我们分享的数据是全球30米分辨率湿地数据集,包含8种湿地亚类,该数据以0.5X0.5的瓦片存储,我们整理了所有属于中国的瓦片名称与其对应省份,方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...
【python异步多线程】异步多线程爬虫代码示例
claude生成的python多线程、异步代码示例,模拟20个网页的爬取,每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程:允许程序同时执行多个任务,提高IO密集型任务(如网络请求)的效率…...
蓝桥杯 冶炼金属
原题目链接 🔧 冶炼金属转换率推测题解 📜 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V,是一个正整数,表示每 V V V 个普通金属 O O O 可以冶炼出 …...
VM虚拟机网络配置(ubuntu24桥接模式):配置静态IP
编辑-虚拟网络编辑器-更改设置 选择桥接模式,然后找到相应的网卡(可以查看自己本机的网络连接) windows连接的网络点击查看属性 编辑虚拟机设置更改网络配置,选择刚才配置的桥接模式 静态ip设置: 我用的ubuntu24桌…...
Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement
Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement 1. LAB环境2. L2公告策略2.1 部署Death Star2.2 访问服务2.3 部署L2公告策略2.4 服务宣告 3. 可视化 ARP 流量3.1 部署新服务3.2 准备可视化3.3 再次请求 4. 自动IPAM4.1 IPAM Pool4.2 …...
数据分析六部曲?
引言 上一章我们说到了数据分析六部曲,何谓六部曲呢? 其实啊,数据分析没那么难,只要掌握了下面这六个步骤,也就是数据分析六部曲,就算你是个啥都不懂的小白,也能慢慢上手做数据分析啦。 第一…...
云原生时代的系统设计:架构转型的战略支点
📝个人主页🌹:一ge科研小菜鸡-CSDN博客 🌹🌹期待您的关注 🌹🌹 一、云原生的崛起:技术趋势与现实需求的交汇 随着企业业务的互联网化、全球化、智能化持续加深,传统的 I…...
