【动态规划】2484. 统计回文子序列数目

avatar
作者
猴君
阅读量:3

本文涉及知识点

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

LeetCode2484. 统计回文子序列数目

给你数字字符串 s ,请你返回 s 中长度为 5 的 回文子序列 数目。由于答案可能很大,请你将答案对 109 + 7 取余 后返回。
提示:
如果一个字符串从前往后和从后往前读相同,那么它是 回文字符串 。
子序列是一个字符串中删除若干个字符后,不改变字符顺序,剩余字符构成的字符串。
示例 1:
输入:s = “103301”
输出:2
解释:
总共有 6 长度为 5 的子序列:“10330” ,“10331” ,“10301” ,“10301” ,“13301” ,“03301” 。
它们中有两个(都是 “10301”)是回文的。
示例 2:
输入:s = “0000000”
输出:21
解释:所有 21 个长度为 5 的子序列都是 “00000” ,都是回文的。
示例 3:
输入:s = “9999900000”
输出:2
解释:仅有的两个回文子序列是 “99999” 和 “00000” 。
提示:
1 <= s.length <= 104
s 只包含数字字符。

动态规划

一,dp1[i][j] 表示以s[i]开头s[j]结尾的长度为3 的回文子序列数量。
{ 0 s [ i ] ! = s [ j ] 0 i > = j j − i − 1 o t h e r \begin{cases} 0 && s[i] != s[j] \\ 0 && i >= j\\ j-i-1 && other \\ \end{cases} 00ji1s[i]!=s[j]i>=jother
dp2[i][j]表示以s[i]开头 s[j1]结尾的长度为3的回文子序列数量。j1 ∈ \in [i,j]。
d p 2 [ i ] [ j ] = ∑ j 1 : i j d p 1 [ i ] [ j 1 ] dp2[i][j] = \sum_{j1:i}^{j}dp1[i][j1] dp2[i][j]=j1:ijdp1[i][j1]
dp3[i][j]表示以s[i1]开头s[j1]结尾的长度为3的回文子序列数量。i1,j1 ∈ \in [i,j]
d p 3 [ i ] [ j ] = ∑ i 1 : 0 j d p 2 [ i 1 ] [ j ] dp3[i][j] = \sum_{i1:0}^{j}dp2[i1][j] dp3[i][j]=i1:0jdp2[i1][j]
dp[i,j]表示以s[i]开始s[j]结尾的长度为5的回文子序列数量。其和就是答案。
如果s[i]==s[j] 就是 dp3[i+1][j-1],否则为0
** 时间复杂度**:O(nn) 超时

代码

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 countPalindromes(string s) { 		const int N = s.length(); 		vector<vector<C1097Int<>>> dp1(N, vector<C1097Int<>>(N)); 		auto dp2 = dp1, dp3 = dp1; 		for (int i = 0; i < N; i++) { 			for (int j = i + 1; j < N; j++) { 				if (s[i] != s[j]) { continue; } 				dp1[i][j] = j - i - 1; 			} 		} 		for (int i = 0; i < N; i++) { 			for (int j = i + 1; j < N; j++) { 				dp2[i][j] = dp2[i][j - 1] + dp1[i][j]; 			} 		}		 		for (int i = N - 1; i >= 0; i--) { 			for (int j = i + 1; j < N; j++) { 				dp3[i][j] = ((i + 1) < N ? dp3[i + 1][j] : 0) + dp2[i][j]; 			} 		} 		C1097Int<> biRet; 		for (int i = 0; i < N; i++) { 			for (int j = i + 1; j < N; j++) { 				if (s[i] != s[j]) { continue; } 				biRet += dp3[i + 1][j - 1]; 			} 		} 		return biRet.ToInt(); 	} }; 

动态规划+前缀和

indexs[ch-‘a’]记录ch所有下标。
令最终回文子序列为t,则三册循环:
第一层循环通过ch枚举t[0]和t[4],时间复杂度O( ∑ \sum ) 即O(10)。
第二层循环通过ch2枚举t[1]和t[3],时间复杂度和第一层相同。
第三层循环通过inx = indexs[ch2-‘a’] 枚举t[3]在s中下标。
令s0为s[j]=ch ,且j < i的数量。
令s01 为 s[i]是t[3] 的t[0]和t[1]的方案数。
令s012 s[i]是他t[3],t[0] t[1] t[2]的方案数。
t[4]为ch的方案数为:inx中大于i的数量,令其为s4。
则以s[i]为t3的总方案数为:s012s4。
∀ \forall i ∈ \in inx ,i1是其下一个元素。
令i1对应的s0、s01、s012分别为s1_、s01_ 和s012_。
s012_由两部分组成:
一,t[1]不是s[i] s012+ s01
(i1-i)。
二,t1[1]是s[i] ,s0_*(i1-i-1)。
s01_ += s0_
时间复杂度:O(n ∑ ∑ \sum\sum ∑∑)

代码

核心代码

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 countPalindromes(string s) { 		vector<int> indexs[10]; 		for (int i = 0; i < s.length(); i++) { 			indexs[s[i] - '0'].emplace_back(i); 		} 		C1097Int<> biRet; 		auto Do = [&](const vector<int>& inx04, const vector<int>& inx13) { 			C1097Int<>  s01 = 0, s012 = 0; 			int i0=0,i4 = 0; 			int pre = -1; 			for (int i3 : inx13) { 				while ((i4 < inx04.size()) && (inx04[i4] <= i3 )) { 					i4++; 				} 				if (-1 != pre) { 					s012 += s01 * (i3 - pre); 					s012 += i0 * (i3 - pre - 1); 					s01 += i0; 					biRet += s012 * (inx04.size() - i4); 				} 				while ((i0 < inx04.size()) && (inx04[i0] < i3)) { 					i0++; 				} 				pre = i3; 			} 		}; 		for (int i04 = 0; i04 < 10; i04++) { 			for (int i13 = 0; i13 < 10; i13++) { 				Do(indexs[i04], indexs[i13]); 			} 		}		 		return biRet.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; 	TEST_CLASS(UnitTest) 	{ 	public: 		TEST_METHOD(TestMethod00) 		{ 			s = "103301"; 			auto res = Solution().countPalindromes(s); 			AssertEx(2, res); 		} 		TEST_METHOD(TestMethod01) 		{ 			s = "0000000"; 			auto res = Solution().countPalindromes(s); 			AssertEx(21, res); 		} 		TEST_METHOD(TestMethod02) 		{ 			s = "9999900000"; 			auto res = Solution().countPalindromes(s); 			AssertEx(2, res); 		} 	}; } 


如果有不明白的,请加文末QQ群。

扩展阅读

视频课程

先学简单的课程,请移步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++**实

现。

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!