C++动态规划算法的应用:得到 K 个半回文串的最少修改次数 原理源码测试用例

简介: 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(n
n),第三层第四层,时间复杂度共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

相关文章
|
3月前
|
监控 安全 Shell
管道符在渗透测试与网络安全中的全面应用指南
管道符是渗透测试与网络安全中的关键工具,既可用于高效系统管理,也可能被攻击者利用实施命令注入、权限提升、数据外泄等攻击。本文全面解析管道符的基础原理、实战应用与防御策略,涵盖Windows与Linux系统差异、攻击技术示例及检测手段,帮助安全人员掌握其利用方式与防护措施,提升系统安全性。
190 6
|
1月前
|
缓存 算法 程序员
C++STL底层原理:探秘标准模板库的内部机制
🌟蒋星熠Jaxonic带你深入STL底层:从容器内存管理到红黑树、哈希表,剖析迭代器、算法与分配器核心机制,揭秘C++标准库的高效设计哲学与性能优化实践。
C++STL底层原理:探秘标准模板库的内部机制
|
2月前
|
运维 监控 JavaScript
基于 Node.js 图结构的局域网设备拓扑分析算法在局域网内监控软件中的应用研究
本文探讨图结构在局域网监控系统中的应用,通过Node.js实现设备拓扑建模、路径分析与故障定位,提升网络可视化、可追溯性与运维效率,结合模拟实验验证其高效性与准确性。
228 3
|
2月前
|
机器学习/深度学习 资源调度 算法
遗传算法模型深度解析与实战应用
摘要 遗传算法(GA)作为一种受生物进化启发的优化算法,在复杂问题求解中展现出独特优势。本文系统介绍了GA的核心理论、实现细节和应用经验。算法通过模拟自然选择机制,利用选择、交叉、变异三大操作在解空间中进行全局搜索。与梯度下降等传统方法相比,GA不依赖目标函数的连续性或可微性,特别适合处理离散优化、多目标优化等复杂问题。文中详细阐述了染色体编码、适应度函数设计、遗传操作实现等关键技术,并提供了Python代码实现示例。实践表明,GA的成功应用关键在于平衡探索与开发,通过精心调参维持种群多样性同时确保收敛效率
|
2月前
|
人工智能 数据可视化 测试技术
AI 时代 API 自动化测试实战:Postman 断言的核心技巧与实战应用
AI 时代 API 自动化测试实战:Postman 断言的核心技巧与实战应用
464 11
|
2月前
|
机器学习/深度学习 边缘计算 人工智能
粒子群算法模型深度解析与实战应用
蒋星熠Jaxonic是一位深耕智能优化算法领域多年的技术探索者,专注于粒子群优化(PSO)算法的研究与应用。他深入剖析了PSO的数学模型、核心公式及实现方法,并通过大量实践验证了其在神经网络优化、工程设计等复杂问题上的卓越性能。本文全面展示了PSO的理论基础、改进策略与前沿发展方向,为读者提供了一份详尽的技术指南。
粒子群算法模型深度解析与实战应用
|
2月前
|
机器学习/深度学习 算法 安全
小场景大市场:猫狗识别算法在宠物智能设备中的应用
将猫狗识别算法应用于宠物智能设备,是AIoT领域的重要垂直场景。本文从核心技术、应用场景、挑战与趋势四个方面,全面解析这一融合算法、硬件与用户体验的系统工程。
|
9月前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
5月前
|
人工智能 机器人 编译器
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
158 0
|
5月前
|
存储 编译器 程序员
c++的类(附含explicit关键字,友元,内部类)
本文介绍了C++中类的核心概念与用法,涵盖封装、继承、多态三大特性。重点讲解了类的定义(`class`与`struct`)、访问限定符(`private`、`public`、`protected`)、类的作用域及成员函数的声明与定义分离。同时深入探讨了类的大小计算、`this`指针、默认成员函数(构造函数、析构函数、拷贝构造、赋值重载)以及运算符重载等内容。 文章还详细分析了`explicit`关键字的作用、静态成员(变量与函数)、友元(友元函数与友元类)的概念及其使用场景,并简要介绍了内部类的特性。
249 0

热门文章

最新文章