当前位置: 首页 > news >正文

【动态规划 前缀和】2478. 完美分割的方案数

本文涉及知识点

划分型dp 动态规划汇总
C++算法:前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频

LeetCode 2478. 完美分割的方案数

给你一个字符串 s ,每个字符是数字 ‘1’ 到 ‘9’ ,再给你两个整数 k 和 minLength 。
如果对 s 的分割满足以下条件,那么我们认为它是一个 完美 分割:
s 被分成 k 段互不相交的子字符串。
每个子字符串长度都 至少 为 minLength 。
每个子字符串的第一个字符都是一个 质数 数字,最后一个字符都是一个 非质数 数字。质数数字为 ‘2’ ,‘3’ ,‘5’ 和 ‘7’ ,剩下的都是非质数数字。
请你返回 s 的 完美 分割数目。由于答案可能很大,请返回答案对 109 + 7 取余 后的结果。
一个 子字符串 是字符串中一段连续字符串序列。
示例 1:
输入:s = “23542185131”, k = 3, minLength = 2
输出:3
解释:存在 3 种完美分割方案:
“2354 | 218 | 5131”
“2354 | 21851 | 31”
“2354218 | 51 | 31”
示例 2:
输入:s = “23542185131”, k = 3, minLength = 3
输出:1
解释:存在一种完美分割方案:“2354 | 218 | 5131” 。
示例 3:
输入:s = “3312958”, k = 3, minLength = 1
输出:1
解释:存在一种完美分割方案:“331 | 29 | 58” 。
提示:
1 <= k, minLength <= s.length <= 1000
s 每个字符都为数字 ‘1’ 到 ‘9’ 之一。

划分型dp

预处理

bool bPrime[128] 记录那些字符是质数。
vNotPirme记录所有非质数下标。二维向量lr,记录所有可以划分的区间,左闭右开。如果s[i]不是质数,则s[i]为空。
否则将所有r ∈ \in vNotPrime ,且 r- i +1 >= minLength 的r+1放到s[i]中。只后就是经典的划分性dp。
时间复杂度 : O(n3)超时。

代码

class Solution {
public:int beautifulPartitions(string s, int K, int minLength) {bool bPrime[128] = { false };bPrime['2'] = bPrime['3'] = bPrime['5'] = bPrime['7']=true;	const int N = s.length();vector<int> notPrime;for (int i = 0; i < N; i++) {if (bPrime[s[i]]) { continue; }notPrime.emplace_back(i);}vector<vector<int>> lr(N);for (int i = 0,j=0; i < N; i++) {if (!bPrime[s[i]]) { continue; }while ((j < notPrime.size()) && (notPrime[j] - i + 1 < minLength)) {j++;}for (int k = j; k < notPrime.size(); k++) {lr[i].emplace_back(notPrime[k] + 1);}}vector<vector<C1097Int<>>> dp(K+1,vector<C1097Int<>>(N + 1));dp[0][0] = 1;for (int i = 0; i < N; i++) {for(int k = 0; k < K ;k++ )for (int r : lr[i]) {dp[k + 1][r] += dp[k][i];}}return dp.back().back().ToInt();}
};

动态规划+前缀和

如果s[0]不是质数或s.back()是质数,无法分割,直接返回0。
分成k段有k-1分割点。
s[i]非质数,s[i+1]是质数,则可以分割。
vSplit记录所有的分割点,为了避免处理边界vSplit = {-1}; 最后追加n-1

动态规划的状态

dp[i][k]表示处理完vSplit[i]的分割k段的方案。 空间复杂度:O(mk) m= vSplit.size()

动态规划的转移方程

dp[i][k] = ∑ j : 0 v S p l i t [ i ] − v S p l i t [ j ] > = m i n L e n g h t \Large\sum_{j:0}^{vSplit[i]-vSplit[j] >= minLenght } j:0vSplit[i]vSplit[j]>=minLenghtdp[j][k-1]
利用前缀和优化,单个状态转移的时间复杂度是:O(1)
时间复杂度:O(mk)

动态规划的填表顺序

i从1到m-1,k从1到K

动态规划的初始值

dp[0][0] = 1 ,其余全为0 .

动态规划的返回值

dp.back().back()

代码

核心代码

template<int MOD = 1000000007>
class C1097Int
{
public:C1097Int(long long llData = 0) :m_iData(llData% MOD){}C1097Int  operator+(const C1097Int& o)const{return C1097Int(((long long)m_iData + o.m_iData) % MOD);}C1097Int& operator+=(const C1097Int& o){m_iData = ((long long)m_iData + o.m_iData) % MOD;return *this;}C1097Int& operator-=(const C1097Int& o){m_iData = (m_iData + MOD - o.m_iData) % MOD;return *this;}C1097Int  operator-(const C1097Int& o){return C1097Int((m_iData + MOD - o.m_iData) % MOD);}C1097Int  operator*(const C1097Int& o)const{return((long long)m_iData * o.m_iData) % MOD;}C1097Int& operator*=(const C1097Int& o){m_iData = ((long long)m_iData * o.m_iData) % MOD;return *this;}C1097Int  operator/(const C1097Int& o)const{return *this * o.PowNegative1();}C1097Int& operator/=(const C1097Int& o){*this /= o.PowNegative1();return *this;}bool operator==(const C1097Int& o)const{return m_iData == o.m_iData;}bool operator<(const C1097Int& o)const{return m_iData < o.m_iData;}C1097Int pow(long long n)const{C1097Int iRet = 1, iCur = *this;while (n){if (n & 1){iRet *= iCur;}iCur *= iCur;n >>= 1;}return iRet;}C1097Int PowNegative1()const{return pow(MOD - 2);}int ToInt()const{return m_iData;}
private:int m_iData = 0;;
};class Solution {
public:int beautifulPartitions(string s, int K, int minLength) {bool bPrime[128] = { false };bPrime['2'] = bPrime['3'] = bPrime['5'] = bPrime['7']=true;	if ((!bPrime[s[0]]) || (bPrime[s.back()])) { return 0; }const int N = s.length();vector<int> vSplit = { -1 };for (int i = 0; i+1 < N; i++) {if (bPrime[s[i]]) { continue; }if (bPrime[s[i + 1]]) { vSplit.emplace_back(i); }}		vSplit.emplace_back(N - 1);const int M = vSplit.size();vector<vector<C1097Int<>>> dp(M , vector<C1097Int<>>(K + 1));dp[0][0] = 1;vector<C1097Int<>> vSum(K);for (int m = 1, i = 0; m < M; m++) {while ((i < M) && (vSplit[m] - vSplit[i] >= minLength)) {for (int k = 0; k < K; k++) {vSum[k] += dp[i][k];}i++;}for (int k = 1; k <= K; k++) {dp[m][k] = vSum[k - 1];}}		return dp.back().back().ToInt();}
};

单元测试

template<class T1, class T2>
void AssertEx(const T1& t1, const T2& t2)
{Assert::AreEqual(t1, t2);
}template<class T>
void AssertEx(const vector<T>& v1, const vector<T>& v2)
{Assert::AreEqual(v1.size(), v2.size());for (int i = 0; i < v1.size(); i++){Assert::AreEqual(v1[i], v2[i]);}
}template<class T>
void AssertV2(vector<vector<T>> vv1, vector<vector<T>> vv2)
{sort(vv1.begin(), vv1.end());sort(vv2.begin(), vv2.end());Assert::AreEqual(vv1.size(), vv2.size());for (int i = 0; i < vv1.size(); i++){AssertEx(vv1[i], vv2[i]);}
}namespace UnitTest
{	string s;int k, minLength;TEST_CLASS(UnitTest){public:TEST_METHOD(TestMethod00){s = "23542185131", k = 3, minLength = 2;auto res = Solution().beautifulPartitions(s, k, minLength);AssertEx(3, res);}TEST_METHOD(TestMethod01){s = "23542185131", k = 3, minLength = 3;auto res = Solution().beautifulPartitions(s, k, minLength);AssertEx(1, res);}TEST_METHOD(TestMethod02){s = "3312958", k = 3, minLength = 1;auto res = Solution().beautifulPartitions(s, k, minLength);AssertEx(1, res);}TEST_METHOD(TestMethod03){s = "24", k = 1, minLength = 1;auto res = Solution().beautifulPartitions(s, k, minLength);AssertEx(1, res);}TEST_METHOD(TestMethod031){s = "24", k = 1, minLength =3;auto res = Solution().beautifulPartitions(s, k, minLength);AssertEx(0, res);}TEST_METHOD(TestMethod04){s = "783938233588472343879134266968", k = 4, minLength = 6;auto res = Solution().beautifulPartitions(s, k, minLength);AssertEx(4, res);}TEST_METHOD(TestMethod05){s = "242538614532395749146912679859", k = 1, minLength = 6;auto res = Solution().beautifulPartitions(s, k, minLength);AssertEx(1, res);}};
}

扩展阅读

视频课程

先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771

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

相关推荐

我想对大家说的话
《喜缺全书算法册》以原理、正确性证明、总结为主。
按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。
有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注
闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。
如果程序是一条龙,那算法就是他的是睛

测试环境

操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。

相关文章:

  • 国产音频放大器工作原理以及应用领域
  • 外贸企业选择什么网络?
  • Git的安装配置及使用(超详细!!!)
  • LeetCode.224基本计算器
  • 【幂等性】详解
  • springboot升级到2.7.17后,quartz集群模式配置修改
  • java多线程之ThreadLocal详解
  • 【Linux详解】进程地址空间
  • 网络爬虫中Xpath的使用方法
  • 【微信小程序开发实战项目】——如何制作一个属于自己的花店微信小程序(1)
  • 深度学习21-30
  • 先导小型工业4.0教学生产线助力制造业技术创新
  • 老年生活照护实训室:探索现代养老服务的奥秘
  • NAS安全存储怎样实现更精细的数据权限管控?
  • Grafana面试题精选和参考答案
  • 【技术性】Search知识
  • 3.7、@ResponseBody 和 @RestController
  • C++类的相互关联
  • CoolViewPager:即刻刷新,自定义边缘效果颜色,双向自动循环,内置垂直切换效果,想要的都在这里...
  • jQuery(一)
  • php ci框架整合银盛支付
  • Python学习之路13-记分
  • SegmentFault 2015 Top Rank
  • SpringBoot几种定时任务的实现方式
  • ucore操作系统实验笔记 - 重新理解中断
  • Vue ES6 Jade Scss Webpack Gulp
  • 闭包--闭包作用之保存(一)
  • 创建一种深思熟虑的文化
  • 如何设计一个微型分布式架构?
  • 一天一个设计模式之JS实现——适配器模式
  • 译米田引理
  • 用Node EJS写一个爬虫脚本每天定时给心爱的她发一封暖心邮件
  • 3月7日云栖精选夜读 | RSA 2019安全大会:企业资产管理成行业新风向标,云上安全占绝对优势 ...
  • 积累各种好的链接
  • # include “ “ 和 # include < >两者的区别
  • #、%和$符号在OGNL表达式中经常出现
  • #考研#计算机文化知识1(局域网及网络互联)
  • ( 10 )MySQL中的外键
  • (附源码)计算机毕业设计SSM在线影视购票系统
  • (力扣)循环队列的实现与详解(C语言)
  • (免费领源码)python+django+mysql线上兼职平台系统83320-计算机毕业设计项目选题推荐
  • (十七)Flask之大型项目目录结构示例【二扣蓝图】
  • (原创)攻击方式学习之(4) - 拒绝服务(DOS/DDOS/DRDOS)
  • ... 是什么 ?... 有什么用处?
  • .NET C# 使用 SetWindowsHookEx 监听鼠标或键盘消息以及此方法的坑
  • .NET CLR基本术语
  • .net core 实现redis分片_基于 Redis 的分布式任务调度框架 earth-frost
  • .NET Core中的去虚
  • .NET 编写一个可以异步等待循环中任何一个部分的 Awaiter
  • .NET 同步与异步 之 原子操作和自旋锁(Interlocked、SpinLock)(九)
  • .Net环境下的缓存技术介绍
  • .net开源工作流引擎ccflow表单数据返回值Pop分组模式和表格模式对比
  • @private @protected @public
  • [ 隧道技术 ] cpolar 工具详解之将内网端口映射到公网
  • [20171106]配置客户端连接注意.txt