C++ 检查给定字符串是否存在一个排列,它不包含任何单调子字符串
单调子字符串是给定字符串的连续子字符串,其中的字符值严格递增或严格递减。单调子字符串是一个字符串序列,它要么严格增加,要么严格减少。
方法
- 动态规划
-
回溯
方法1:动态规划
一种技术是应用动态规划来构建子问题的表格,这里表格中的每个项目(i,j)表示是否存在一个不包含任何单调子字符串的子串S[i...j]
的排列。当i=j时,子串仅包含一个字符,因此是单调的。
语法
假设s是长度为n的输入字符串,dp[k][i]
是一个布尔变量,如果s的前k个字符的排列以第i个字符终止,并且不包含任何单调子字符串,则返回true。
- dp[1][i]等于对于所有的1<=i<=n都为true。
-
如果k>1且i>j,则dp[k][i]=true当且仅当dp[k-1][j]为true且s[i]不在以索引j终止的排列的最后两个字母生成的单调子字符串中时。
当且仅当存在某个值k使得对于某些1<=i<=n,dp[k][i]
为true时,该问题的解为true。
步骤
第1步 - 假设S是长度为n的输入字符串。
第2步 - 创建大小为n x 2的二维布尔数组DP,这里DP[i][0]表示以位置i结尾的递减子序列的存在与否,DP[i][1]
表示以位置i结尾的递增子序列的存在与否。
第3步 - 将DP[0][0]
和DP[0][1]
都设为false。
第4步 - 对于每个1<=i<=n-1
,执行以下操作-
- 如果存在一个j,使得
0<=jS[i]
,并且DP[j][1]
为true,则将DP[i][0]
设为true。 -
如果存在一个j,使得
0<=jS[i]
,并且DP[j][0]
为true,则将DP[i][1]
设为true。
第5步 - 确定是否存在一个位置i,使得DP[i][0]
和DP[i][1]
都为true。如果存在这样的位置,则返回false,表示排列中存在单调子字符串。
第6步 - 否则,返回true,因为在输入字符串的任何排列中都不存在单调子字符串。
示例
为了开始我们对这个问题的处理,我们首先建立一个名为“dp”的二维DP矩阵。在这个矩阵中,每个元素表示形成长度为i的字符串所需的升序和降序字符。最开始为了正确设置我们的矩阵以供将来使用,我们给单元格[1]
中的两个元素都赋予’one’的值。在向后填充后续单元格时,我们的递推关系使用了诸如:用来计算'dp[i][0]'
的公式'dp[i-1][0]'
,而'dp[i][1]'
的生成使用了公式'dp[i-13][0] + dp[i-11][1] + dp[i-41][1]'
。然而,在从上下文中提及的任何进一步策略之前,我们首先通过[cnt]
计算特定字符在给定字符串中出现的次数,考虑是否存在重新排列的概念,结果小于或等于“dp[i][0]+dp[i][1]”
的子序列计数值为微小/显著。如果评估结果为”true”,则验证了我们的做法的合法性,而其他情况则被视为不满意的输出。
#include <iostream>
#include <cstring>
using namespace std;
bool checkMonotonous(string s) {
int n = s.length();
int dp[n + 1][2];
memset(dp, 0, sizeof(dp));
dp[1][0] = dp[1][1] = 1;
for (int i = 2; i <= n; i++) {
dp[i][0] = dp[i - 1][1];
dp[i][1] = dp[i - 1][0] + dp[i - 1][1];
}
int cnt[26] = {0};
for (int i = 0; i < n; i++) {
cnt[s[i] - 'a']++;
}
for (int i = 1; i <= n; i++) {
if (dp[i][0] + dp[i][1] > cnt[i - 1]) {
return false;
}
}
return true;
}
int main() {
string s = "aabbcc";
if (checkMonotonous(s)) {
cout << "There exists a permutation of the given string which doesn't contain any monotonous substring.\n";
} else {
cout << "There does not exist permutation of given string which does not contain monotonous substring.\n";
}
return 0;
}
输出
There does not exist permutation of given string which does not contain monotonous substring.
方法2:回溯法
回溯法也可以用来生成字符串的所有可能排列,然后检查其中是否包含单调子字符串。由于需要进行大量的变换,这种方法可能会延迟处理大字符串,但如果存在解决方案,则可以确保找到答案。
语法
在C++中使用回溯法解决检查字符串排列是否包含单调子字符串的问题的一般语法。
bool hasMonotonousSubstring(string s) {
sort(s.begin(), s.end());
do {
bool valid = true;
for (int i = 2; i < s.length(); i++) {
if (s[i] == s[i-1] + 1 && s[i-1] == s[i-2] + 1) {
valid = false;
break;
}
else if (s[i] == s[i-1] - 1 && s[i-1] == s[i-2] - 1) {
valid = false;
break;
}
}
if (valid) return true;
} while (next_permutation(s.begin(), s.end()));
return false;
}
步骤
使用C++中的回溯法来进行不包含单调子字符串的给定字符串的排列的通用方法如下:
步骤1 - 生成所提供字符串的所有排列。
步骤2 - 检查每个排列,看它们是否包含任何单调子字符串。如果没有,该排列是可行解。
步骤3 - 返回有效的解并结束过程,如果找到一个。
步骤4 - 如果你已经生成了所有可能的排列,并且它们都无效,返回到上一个阶段并尝试不同的候选解。
示例2
isMonotonous()方法确定所提供的字符串是否包含任何单调子字符串。它通过迭代文本并寻找长度为3的子字符串来实现,这些子字符串要么严格上升,要么严格下降。
permute()函数通过回溯法生成所提供字符串的所有排列。它以字符串s、初始索引l和结束索引r作为输入。如果初始索引等于结束索引,我们已经创建了一个排列。使用isMonotonous()方法,我们确定这个排列是否包含任何单调子字符串。
main()函数初始化输入字符串并按字典顺序递增对其进行排序。然后它调用permute()函数,其起始索引为0,结束索引为字符串长度减去1。如果没有找到不包含单调子字符串的排列,我们打印”No permutation found”。
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
bool isMonotonous(string s) {
for (int i = 2; i < s.length(); i++) {
if ((s[i] > s[i-1] && s[i-1] > s[i-2]) || (s[i] < s[i-1] && s[i-1] < s[i-2])) {
return true;
}
}
return false;
}
bool permute(string s, int l, int r) {
if (l == r) {
if (!isMonotonous(s)) {
cout << s << endl;
return true;
}
}
else {
for (int i = l; i <= r; i++) {
swap(s[l], s[i]);
if (permute(s, l+1, r)) {
return true;
}
swap(s[l], s[i]);
}
}
return false;
}
int main() {
string s = "abc";
sort(s.begin(), s.end()); // sort the string in lexicographically increasing order
if (!permute(s, 0, s.length()-1)) {
cout << "No permutation found" << endl;
}
return 0;
}
输出
acb
结论
总结来说,确定给定字符串是否有不包含任何单调子字符串的排列是一个困难的任务,需要进行彻底的分析和算法设计。
一个替代方案是生成字符串的所有可能排列,并检查每个排列是否存在单调子字符串。然而,由于大规模输入的排列数可能非常巨大,这种策略效率较低。