替换二进制字符串中的通配符,避免三个相同的连续字母
Replace wildcards in a binary string avoiding three identical consecutive letters
给定一个长度为 N 的字符串 S,return 一个字符串,该字符串是将字符串 S 中的每个 '?'
替换为 'a'
或 'b'
的结果字符且不包含三个相同的连续字母(换句话说,'aaa'
和 'bbb'
都不会出现在处理后的字符串中)。
示例:
Given S="a?bb", output= "aabb"
Given S="??abb", output= "ababb" or "bbabb" or "baabb"
Given S="a?b?aa", output= "aabbaa"
1<=n<=500000
我使用回溯法解决了这个问题,但是我的解决方案很慢,并且对更大的 N 值不起作用,有没有更好的方法?
我很快尝试了我在评论中提出的解决方案:
#include <iostream>
#include <string>
int main(void)
{
std::string s = "?????aaa??bbaa????";
std::cout << s <<'\n';
for(char &c:s)
if(c=='?')
c='a';
std::cout << s <<'\n';
for(int i =0; i<s.size()-2; i++)
{
if(s[i] == 'a'
&& s[i+1] == 'a'
&& s[i+2] == 'a' )
{
s[i+1] = 'b';
i++;
}
}
std::cout << s <<'\n';
return 0;
}
似乎符合给定的规则。
但也许有些情况需要检查和修复。
(请参阅我的贪婪 ,其中包括随机测试,与 MTO 的代码进行比较。)
O(n)
动态程序有四种状态:一个字符是a_first
、a_second
、b_first
或b_second
。让 dp[i]
表示创建索引 i
之前的有效字符串的可能性。那么:
dp[i][a_first] = True
dp[i][a_second] = True
dp[i][b_first] = True
dp[i][b_second] = True
where i < 0
if s[i] == '?':
dp[i][a_first] = dp[i-1][b_first] or dp[i-1][b_second]
dp[i][a_second] = dp[i-1][a_first]
dp[i][b_first] = dp[i-1][a_first] or dp[i-1][a_second]
dp[i][b_second] = dp[i-1][b_first]
else if s[i] == 'a':
dp[i][a_first] = dp[i-1][b_first] or dp[i-1][b_second]
dp[i][a_second] = dp[i-1][a_first]
dp[i][b_first] = False
dp[i][b_second] = False
else:
dp[i][a_first] = False
dp[i][a_second] = False
dp[i][b_first] = dp[i-1][a_first] or dp[i-1][a_second]
dp[i][b_second] = dp[i-1][b_first]
where i ≥ 0
要获取字符串,我们可以按照始终保持True并保持连续字符约束的路径向后。
Python代码:
def f(s):
dp = [[None] * 4 for _ in range(len(s))]
def get_dp(i, j):
return True if i < 0 else dp[i][j]
for i in range(len(s)):
if s[i] == '?':
dp[i][0] = get_dp(i-1, 2) or get_dp(i-1, 3)
dp[i][1] = get_dp(i-1, 0)
dp[i][2] = get_dp(i-1, 0) or get_dp(i-1, 1)
dp[i][3] = get_dp(i-1, 2)
elif s[i] == 'a':
dp[i][0] = get_dp(i-1, 2) or get_dp(i-1, 3)
dp[i][1] = get_dp(i-1, 0)
dp[i][2] = False
dp[i][3] = False
else:
dp[i][0] = False
dp[i][1] = False
dp[i][2] = get_dp(i-1, 0) or get_dp(i-1, 1)
dp[i][3] = get_dp(i-1, 2)
# Build the output
result = []
i = len(s) - 1
need = None
while i >= 0:
if dp[i][0] and need != 'b':
result.append('a')
need = None if (len(result) == 1 or result[-2] == 'b') else 'b'
i-= 1
elif dp[i][1] and need != 'b':
result.extend(['a', 'a'])
need = 'b'
i -= 2
elif dp[i][2] and need != 'a':
result.append('b')
need = None if (len(result) == 1 or result[-2] == 'a') else 'a'
i -= 1
elif dp[i][3] and need != 'a':
result.extend(['b', 'b'])
need = 'a'
i -= 2
else:
return ""
return "".join(reversed(result))
输出:
strs = [
"a?bb", # "aabb"
"??abb", # "ababb" or "bbabb" or "baabb"
"a?b?aa", # "aabbaa"
"?bb?",
"aa?bb", # NO SOLUTION
"aa??aa", # "aabbaa"
"ab???bb?",
"????",
"??",
"?????",
"??????"
]
for s in strs:
print("%s\n%s\n" % (s, f(s)))
"""
a?bb
aabb
??abb
baabb
a?b?aa
aabbaa
?bb?
abba
aa?bb
aa??aa
aabbaa
ab???bb?
abbaabba
????
abaa
??
aa
?????
aabaa
??????
baabaa
"""
迭代回溯解决方案。
isValid
只检查在位置 pos
处插入字符 c
是否会产生问题,而不遍历整个字符串;
维护一个变量int dir = +-1
来知道我们是在字符串中向前还是向后移动(这很重要,这样我们就可以知道在跳过非字符串时向哪个方向移动-?
原字符串中的字符);
if/else if/else
的森林来决定我们在哪里(非?
跳过,或新鲜 ?
前进,或已经尝试 a
,或者已经尝试过 a
和 b
);
solve
有一个布尔值 return 如果找到解决方案 (pos == s.length()
),则该值为 true
,或者 false
如果未找到解决方案 (pos == (unsigned int) -1
)。
#include <iostream>
#include <vector>
bool isValid(char c, std::string const &s, unsigned int pos)
{
return ((pos < 2 || s[pos - 2] != c || s[pos - 1] != c)
&& (pos < 1 || pos + 1 >= s.length() || s[pos - 1] != c || s[pos + 1] != c)
&& (pos + 2 >= s.length() || s[pos + 1] != c || s[pos + 2] != c));
}
bool solve(std::string const &in, std::string &out)
{
unsigned int pos = 0;
int dir = 1;
out = in;
while (pos < in.size())
{
if (in[pos] != '?') // nothing to do: keep moving (forward or backward)
{
pos += dir;
}
else if (out[pos] == '?') // going forward, will try 'a' then 'b'
{
dir = 1;
if (isValid('a', out, pos))
{
out[pos] = 'a';
pos += dir;
}
else if (isValid('b', out, pos))
{
out[pos] = 'b';
pos += dir;
}
else
{
dir = -1;
pos += dir;
}
}
else if (out[pos] == 'a' && isValid('b', out, pos)) // going backward, already tried 'a'
{
out[pos] = 'b';
dir = 1;
pos += dir;
}
else // already tried everything: backtrack
{
dir = -1;
out[pos] = '?';
pos += dir;
}
}
return (pos == in.size());
}
int main(void)
{
std::vector<std::string> ins = {"a?bb", "??abb", "a?b?aa", "aa?bb"};
std::vector<std::string> outs = {"", "", "", "", ""};
for (unsigned int i = 0; i < ins.size(); ++i)
{
if (solve(ins[i], outs[i]))
std::cout << ins[i] << " --> " << outs[i] << std::endl;
else
std::cout << ins[i] << " --> NO SOLUTION" << std::endl;
}
return 0;
}
输出:
a?bb --> aabb
??abb --> ababb
a?b?aa --> aabbaa
aa?bb --> NO SOLUTION
首先x??y
有超过1个?总会过去的
a??b => abab
(两边不增加长度,*不影响*后面)
a??a => abba
(无影响)
a???????????????????b => ab?????????????????ab
所以你只需要考虑单?
的情况
aa? => aab
(没有其他可能性)
a?a => aba
(无影响)
所以唯一的问题是a?b
aa?b => aabb
(如 aa?
中所述)
ba?b => baab
(无影响)
^a?b
=> ^aab
(仅起步,无影响)
您最多进行了 2 次回顾和 2 次回顾,所以这是一个 O(n)
解决方案,
如果它可以包含无效输入,只需 运行 另一个 O(n)
检查
我在另一个回答里加了一个)
在JavaScript中实现(但移植到C++应该相对简单):
let solve = (str) => {
let opposite = {"a":"b","b":"a"};
let curr_idx = 0;
let output = [];
let lookahead = (x) => ((curr_idx+x<0)||(curr_idx+x>=str.length)?null:str[curr_idx + x]);
let lookbehind = (x) => (curr_idx-x<0?null:output[curr_idx - x])
let matches = (x,y) => (x == y || x == null || y == null);
let is_replacement = (x) => (x == '?');
while ( curr_idx < str.length )
{
let curr = lookbehind(1) || 'b';
let i = 0;
let next = lookahead(i);
while (is_replacement(next))
{
++i;
next = lookahead(i);
}
if (next == null)
{
// Found the end of the string.
// Append characters opposite to the previous for each ?
for (; i > 0; --i)
{
curr = opposite[curr];
output.push( curr );
}
break;
}
if (i > 1)
{
// Found multiple successive ?s
// Handle the first half of the ?s by
// Append alternating characters from the previous character.
let j = 0;
for (; j < i/ 2; ++j)
{
curr = opposite[curr];
output.push( curr );
}
// Then handle the second half of the ?s
// append alternating characters to the next character after the ?s.
for (; j < i; ++j)
{
output.push( (j%2) == (i%2) ? next : opposite[next] );
}
}
else if (i == 1)
{
// Found a single ?
let prev = lookbehind(2);
if (curr == prev && curr == opposite[next] && next == lookahead(2))
{
// No solution.
return null;
}
if (curr == prev || matches(curr, next))
{
output.push( opposite[curr] );
}
else
{
output.push( curr );
}
}
if ( next != null )
{
// Output the next non-? character.
output.push( next );
}
curr_idx += i + 1;
}
return output.join("");
};
let tests = [
"?",
"a?",
"a??",
"a???",
"b?",
"b??",
"b???",
"a?a",
"a?b",
"b?a",
"b?b",
"a??a",
"a??b",
"b??a",
"b??b",
"aa?",
"ba?",
"a?bb",
"?bb?",
"??abb",
"a?b?aa",
"ab???bb?",
"aa?bb",
];
for ( let test of tests )
{
console.log( `${test} -> ${solve(test)}` );
}
更新 - 所有可能的解决方案
可以将问题减少到 10 条规则,从左到右对字符串进行操作,最多 window 6 个字符(2 个后向和 4 个前向),这样规则可以为字符串生成所有可能的解决方案:
Rules:
0: ss?dd_ -> No solution
1: __??ss -> __?dss
2: _s?s__ -> _sds__
3: ss?___ -> ssd___
4: __?ss_ -> __dss_
5: DS?DS_ -> DSsDS_ | DSdDS_
6: DS??sD -> DSsdsD | DSddsD | DSdssD
7: Ds??dS -> DsdsdS | DssddS
8: ^??X_ -> ^s?X_ | ^d?X_
9: Ds??X_ -> DssdX_ | Dsd?X_
Notations:
s: Same character.
d: The opposite of the same character.
S: Either the same character or the end-of-the-string.
D: Either the opposite of the same character or the start- or end-of-the-string.
_: Any character or the start- or end-of-the-string.
?: A character to replace.
X: Either a character to replace or the end-of-the-string.
^: The start-of-the-string.
(注:规则一与规则四基本相同,只是先处理后面的替换字符。)
这导致 JavaScript 代码:
function* solve(
str,
i = 0,
depth = 0
)
{
let data = Array.from(str);
let chr = (n) => (n < 0 || n > data.length ? null : data[n]);
let lookbehind2 = chr(i - 2);
let lookbehind1 = chr(i - 1);
let current = chr(i);
let lookahead1 = chr(i + 1);
let lookahead2 = chr(i + 2);
let lookahead3 = chr(i + 3);
const DEBUG = (rule) => {
if (false)
{
console.log(`${"".padStart(depth)}Rule ${rule}@${i}: ${str} -> ${data.join("")}`)
}
};
let stepForward = (steps) => {
for (let n = 0; n < steps; ++n)
{
++i;
lookbehind2 = lookbehind1;
lookbehind1 = current;
current = lookahead1;
lookahead1 = lookahead2;
lookahead2 = lookahead3;
lookahead3 = chr(i + 3);
}
}
let isSolved = (ch) => (ch == 'a' || ch == 'b');
let isSame = (ch1, ch2) => (isSolved(ch1) && ch1 == ch2);
let opposite = (ch) => ({"a":"b","b":"a"}[ch]);
let isOpposite = (ch1, ch2) => (isSolved(ch1) && ch1 == opposite(ch2));
let isEndOfString = (ch) => (ch == null);
let isStartOfString = (ch) => (ch == null);
while( !isEndOfString(current) )
{
if (current != '?')
{
stepForward(1);
continue;
}
// Rules:
// 0: ss?dd_ -> No solution
// 1: __??ss -> __?dss
// 2: _s?s__ -> _sds__
// 3: ss?___ -> ssd___
// 4: __?ss_ -> __dss_
// 5: DS?DS_ -> DSsDS_ | DSdDS_
// 6: DS??sD -> DSsdsD | DSddsD | DSdssD
// 7: Ds??dS -> DsdsdS | DssddS
// 8: $??X_ -> $s?X_ | $d?X_
// 9: Ds??X_ -> DssdX_ | Dsd?X_
//
// Notations:
// s: Same character.
// d: The opposite of the same character.
// S: Either the same character or the end-of-the-string.
// D: Either the opposite of the same character or the start- or end-of-the-string.
// _: Any character or the start- or end-of-the-string.
// ?: A character to replace.
// X: Either a character to replace or the end-of-the-string.
// $: The end-of-the-string.
// -----------------------------------------------------------
// Rule 0: ss?dd_ -> No solution
if (
isSame(lookbehind2, lookbehind1)
&& isSame(lookahead1, lookahead2)
&& lookbehind1 != lookahead1
)
{
DEBUG(0);
return;
}
// Rule 1: __??ss -> __?dss
if (lookahead1 == '?' && isSame(lookahead2, lookahead3))
{
data[i + 1] = lookahead1 = opposite(lookahead2);
DEBUG(1);
}
// Rule 2: _s?s__ -> _sds__
if (isSame(lookbehind1, lookahead1))
{
data[i] = current = opposite(lookahead1);
DEBUG(2);
stepForward(2);
continue;
}
// Rule 3: ss?___ -> ssd___
if (isSame(lookbehind2, lookbehind1))
{
data[i] = current = opposite(lookbehind1);
DEBUG(3);
stepForward(1);
continue;
}
// Rule 4: __?ss_ -> __dss_
if (isSame(lookahead1, lookahead2))
{
data[i] = current = opposite(lookahead1);
DEBUG(4);
stepForward(3);
continue;
}
// Rule 5: DS?DS_ -> DSsDS_ | DSdDS_
if (lookahead1 != '?')
{
data[i] = current = "a";
DEBUG(5);
for (solution of solve(data.join(""), i + 2, depth + 1))
{
yield solution;
}
data[i] = current = "b";
DEBUG(5);
for (solution of solve(data.join(""), i + 2, depth + 1))
{
yield solution;
}
return;
}
// Rule 6: DS??sD -> DSsdsD | DSddsD | DSdssD
if (
isSame(lookbehind1, lookahead2)
|| (isStartOfString(lookbehind1) && isSolved(lookahead2))
)
{
data[i] = current = lookahead2;
data[i+1] = lookahead1 = opposite(lookahead2);
DEBUG(6);
for (solution of solve(data.join(""), i + 3, depth + 1))
{
yield solution;
}
data[i] = current = opposite(lookahead2);
data[i+1] = lookahead1 = opposite(lookahead2);
DEBUG(6);
for (solution of solve(data.join(""), i + 3, depth + 1))
{
yield solution;
}
data[i] = current = opposite(lookahead2);
data[i+1] = lookahead1 = lookahead2;
DEBUG(6);
for (solution of solve(data.join(""), i + 3, depth + 1))
{
yield solution;
}
return;
}
// Rule 7: Ds??dS -> DsdsdS | DssddS
if (isOpposite(lookahead2, lookbehind1))
{
data[i] = current = lookahead2;
data[i+1] = lookahead1 = opposite(lookahead2);
DEBUG(7);
for (solution of solve(data.join(""), i + 3, depth + 1))
{
yield solution;
}
data[i] = current = opposite(lookahead2);
data[i+1] = lookahead1 = lookahead2;
DEBUG(7);
for (solution of solve(data.join(""), i + 3, depth + 1))
{
yield solution;
}
return;
}
// Rule 8: $??X_ -> $s?X_ | $d?X_
// Rule 9: Ds??X_ -> DssdX_ | Dsd?X_
if (lookahead2 == "?" || isEndOfString(lookahead2))
{
if (isStartOfString(lookbehind1))
{
data[i] = current = "a";
DEBUG(8);
for (solution of solve(data.join(""), i + 1, depth + 1))
{
yield solution;
}
data[i] = current = "b";
DEBUG(8);
for (solution of solve(data.join(""), i + 1, depth + 1))
{
yield solution;
}
}
else
{
data[i] = current = opposite(lookbehind1);
DEBUG(9);
for (solution of solve(data.join(""), i + 1, depth + 1))
{
yield solution;
}
data[i] = current = lookbehind1;
data[i+1] = lookahead1 = opposite(lookbehind1);
DEBUG(9);
for (solution of solve(data.join(""), i + 2, depth + 1))
{
yield solution;
}
}
return;
}
throw `Invalid state ${data.join("")}`;
}
yield data.join("");
}
let tests = [
"?",
"??",
"???",
"a?",
"a??",
"a???",
"b?",
"b??",
"b???",
"a?a",
"a?b",
"b?a",
"b?b",
"a??a",
"a??b",
"b??a",
"b??b",
"aa?",
"ba?",
"a?bb",
"?bb?",
"?a",
"?aa",
"??aa",
"???aa",
"????aa",
"?ab",
"??ab",
"???ab",
"????ab",
"a?b?aa",
"ab???bb?",
"aa?bb",
"a?b?a?bb",
"?a?b?aa",
];
for ( let test of tests )
{
console.log( `${test} -> ${[...solve(test)]}` );
}
如果你想要一个单一的解决方案,那么你应该能够只为每个规则获取第一个匹配项,并在 O(N) 时间和 O(1) 内存求解器中实现它。
更新 2:C++ 解决方案
与 JavaScript 解决方案相同的算法,但使用了一堆部分解决方案(而不是递归调用生成器函数的 JavaScript 解决方案)。
#include <iostream>
#include <list>
class PartialSolution
{
public:
const std::string partial_solution;
const int position;
PartialSolution(
std::string const &solution,
const int pos
):
partial_solution(solution),
position(pos)
{}
};
class Solver
{
const bool DEBUGGING = false;
std::string str;
int position;
char lookbehind2;
char lookbehind1;
char current;
char lookahead1;
char lookahead2;
char lookahead3;
char get_character(int pos) const
{
if ( pos < 0 || pos >= str.length() )
{
return '[=13=]';
}
return str[pos];
}
void load_partial_solution(PartialSolution const &ps)
{
str = ps.partial_solution;
position = ps.position;
lookbehind2 = get_character(position - 2);
lookbehind1 = get_character(position - 1);
current = get_character(position + 0);
lookahead1 = get_character(position + 1);
lookahead2 = get_character(position + 2);
lookahead3 = get_character(position + 3);
if (DEBUGGING)
{
std::cout << "Load @" << position << ": " << str << "\n";
}
}
void step_forward(int n)
{
for (int i = 0; i < n; ++i)
{
++position;
lookbehind2 = lookbehind1;
lookbehind1 = current;
current = lookahead1;
lookahead1 = lookahead2;
lookahead2 = lookahead3;
lookahead3 = get_character(position + 3);
}
}
bool is_solved(char ch) const
{
return ch == 'a' || ch == 'b';
}
bool is_same(char ch1, char ch2) const
{
return is_solved(ch1) && ch1 == ch2;
}
char opposite(char ch) const
{
switch(ch)
{
case 'a': return 'b';
case 'b': return 'a';
default:
return '[=13=]';
}
}
bool is_opposite(char ch1, char ch2) const
{
return is_solved(ch1) && ch1 == opposite(ch2);
}
bool is_end_of_string(char ch) const
{
return ch == '[=13=]';
}
bool is_start_of_string(char ch) const
{
return ch == '[=13=]';
}
void DEBUG(int rule, bool pushback = false) const
{
if (DEBUGGING)
{
std::cout << (pushback?"Save: ":"") << "Rule " << rule << "@" << position << ": " << str << "\n";
}
}
public:
std::list<std::string> solve(std::string const&);
};
std::list<std::string> Solver::solve(std::string const& data)
{
std::list<PartialSolution> partial_solutions = { PartialSolution(data, 0) };
std::list<std::string> solutions = {};
while( !partial_solutions.empty() )
{
load_partial_solution( partial_solutions.back() );
partial_solutions.pop_back();
while( !is_end_of_string(current) )
{
if (current != '?')
{
step_forward(1);
continue;
}
// Rules:
// 0: ss?dd_ -> No solution
// 1: __??ss -> __?dss
// 2: _s?s__ -> _sds__
// 3: ss?___ -> ssd___
// 4: __?ss_ -> __dss_
// 5: DS?DS_ -> DSsDS_ | DSdDS_
// 6: DS??sD -> DSsdsD | DSddsD | DSdssD
// 7: Ds??dS -> DsdsdS | DssddS
// 8: $??X_ -> $s?X_ | $d?X_
// 9: Ds??X_ -> DssdX_ | Dsd?X_
//
// Notations:
// s: Same character.
// d: The opposite of the same character.
// S: Either the same character or the end-of-the-string.
// D: Either the opposite of the same character or the start- or end-of-the-string.
// _: Any character or the start- or end-of-the-string.
// ?: A character to replace.
// X: Either a character to replace or the end-of-the-string.
// $: The end-of-the-string.
// -----------------------------------------------------------
// Rule 0: ss?dd_ -> No solution
if (
is_same(lookbehind2, lookbehind1)
&& is_same(lookahead1, lookahead2)
&& lookbehind1 != lookahead1
)
{
DEBUG(0);
goto no_solution;
}
// Rule 1: __??ss -> __?dss
if (lookahead1 == '?' && is_same(lookahead2, lookahead3))
{
lookahead1 = opposite(lookahead2);
str[position+1] = lookahead1;
DEBUG(1);
}
// Rule 2: _s?s__ -> _sds__
if (is_same(lookbehind1, lookahead1))
{
str[position] = current = opposite(lookahead1);
DEBUG(2);
step_forward(2);
continue;
}
// Rule 3: ss?___ -> ssd___
if (is_same(lookbehind2, lookbehind1))
{
str[position] = current = opposite(lookbehind1);
DEBUG(3);
step_forward(1);
continue;
}
// Rule 4: __?ss_ -> __dss_
if (is_same(lookahead1, lookahead2))
{
str[position] = current = opposite(lookahead1);
DEBUG(4);
step_forward(3);
continue;
}
// Rule 5: DS?DS_ -> DSsDS_ | DSdDS_
if (lookahead1 != '?')
{
str[position] = current = 'b';
DEBUG(5, true);
partial_solutions.emplace_back(str, position + 2);
str[position] = current = 'a';
DEBUG(5);
step_forward(2);
continue;
}
// Rule 6: DS??sD -> DSsdsD | DSddsD | DSdssD
if (
is_same(lookbehind1, lookahead2)
|| (is_start_of_string(lookbehind1) && is_solved(lookahead2))
)
{
str[position] = current = opposite(lookahead2);
str[position+1] = lookahead1 = lookahead2;
DEBUG(6, true);
partial_solutions.emplace_back(str, position + 3);
str[position] = current = opposite(lookahead2);
str[position+1] = lookahead1 = opposite(lookahead2);
DEBUG(6, true);
partial_solutions.emplace_back(str, position + 3);
str[position] = current = lookahead2;
str[position+1] = lookahead1 = opposite(lookahead2);
DEBUG(6);
step_forward(3);
continue;
}
// Rule 7: Ds??dS -> DsdsdS | DssddS
if (is_opposite(lookahead2, lookbehind1))
{
str[position] = current = opposite(lookahead2);
str[position+1] = lookahead1 = lookahead2;
DEBUG(7, true);
partial_solutions.emplace_back(str, position + 3);
str[position] = current = lookahead2;
str[position+1] = lookahead1 = opposite(lookahead2);
DEBUG(7);
step_forward(3);
continue;
}
// Rule 8: $??X_ -> $s?X_ | $d?X_
// Rule 9: Ds??X_ -> DssdX_ | Dsd?X_
if (lookahead2 == '?' || is_end_of_string(lookahead2))
{
if (is_start_of_string(lookbehind1))
{
str[position] = current = 'b';
DEBUG(8, true);
partial_solutions.emplace_back(str, position + 1);
str[position] = current = 'a';
DEBUG(8);
step_forward(1);
continue;
}
else
{
str[position] = current = lookbehind1;
str[position+1] = lookahead1 = opposite(lookbehind1);
DEBUG(9, true);
partial_solutions.emplace_back(str, position + 2);
str[position] = current = opposite(lookbehind1);
str[position+1] = lookahead1 = '?';
DEBUG(9);
continue;
}
}
throw std::invalid_argument(str);
}
solutions.push_back(str);
no_solution:;
}
return solutions;
}
void run_test(
Solver &solver,
std::string const &test
)
{
std::cout << test << " -> ";
std::list<std::string> solutions = solver.solve(test);
int size = solutions.size();
for (const auto& solution : solutions)
{
std::cout << solution;
if (--size != 0)
{
std::cout << ", ";
}
}
std::cout << "\n";
}
int main()
{
std::list<std::string> tests = {
"?",
"??",
"???",
"a?",
"a??",
"a???",
"b?",
"b??",
"b???",
"a?a",
"a?b",
"b?a",
"b?b",
"a??a",
"a??b",
"b??a",
"b??b",
"aa?",
"ba?",
"a?bb",
"?bb?",
"?a",
"?aa",
"??aa",
"???aa",
"????aa",
"?ab",
"??ab",
"???ab",
"????ab",
"a?b?aa",
"ab???bb?",
"aa?bb",
"a?b?a?bb",
"?a?b?aa",
};
Solver solver = Solver();
for (const auto& test : tests)
{
run_test(solver, test);
}
}
下面的贪婪版本似乎有效。这样,除了一个输出字符串外,我们还可以有常量 space。有人可以帮忙找一个反例吗?我在实现简单的动态程序之前写下了这个想法(参见this revision)。尽量保持两个相同字符的顺序。
下面代码的最后一部分包括随机测试 。
Python代码:
def is_valid(s):
if not s:
return True
char = "x"
count = 0
for c in s:
if c != char:
char, count = c, 1
else:
count += 1
if count == 3:
return False
return True
# My greedy solution
def f(s):
if len(s) == 2:
return s.replace('?', 'a')
aa = ['a', 'a']
bb = ['b', 'b']
out = []
i = 0
a, b = 0, 0
while i < len(s):
if s[i] == 'a' or (s[i] == '?' and b == 2):
if s[i] == 'a' and a == 2:
if s[i-3:i-1] == "??":
out[i-3], out[i-2] = out[i-2], out[i-3]
a -= 1
else:
return ""
out.append('a')
a, b = a + 1, 0
i += 1
elif s[i] == 'b' or (s[i] == '?' and a == 2):
if s[i] == 'b' and b == 2:
if s[i-3:i-1] == "??":
out[i-3], out[i-2] = out[i-2], out[i-3]
b -= 1
else:
return ""
out.append('b')
a, b = 0, b + 1
i += 1
elif i == len(s) - 1:
out.append('a' if b else 'b')
i += 1
elif i == len(s) - 2:
if s[i+1] == '?':
out.extend(aa if b else bb)
else:
out.append('a' if b else 'b')
out.append(s[i+1])
i += 2
elif s[i+1] == '?':
if a:
out.append('a')
a, b = a + 1, 0
else:
out.append('b')
a, b = 0, b + 1
i += 1
elif s[i+1] == 'a':
if a or b < 2:
out.append('b')
a, b = 0, b + 1
else:
out.append('a')
a, b = a + 1, 0
i += 1
elif s[i+1] == 'b':
if b or a < 2:
out.append('a')
a, b = a + 1, 0
else:
out.append('b')
a, b = 0, b + 1
i += 1
return ''.join(out)
#
# Code by MTO
def solve(_str):
opposite = {"a": "b", "b": "a"}
curr_idx = 0
output = []
lookahead = lambda x: None if ((curr_idx + x < 0) or (curr_idx + x >= len(_str))) else _str[curr_idx + x]
lookbehind = lambda x: None if curr_idx-x< 0 else output[curr_idx - x]
matches = lambda x, y: x == y or x == None or y == None
is_replacement = lambda x: x == '?'
while curr_idx < len(_str):
curr = lookbehind(1) or 'b'
i = 0
next = lookahead(i)
while is_replacement(next):
i += 1
next = lookahead(i)
if next == None:
# Found the end of the string.
# Append characters opposite to the previous for each ?
for _ in range(i, 0, -1):
curr = opposite[curr]
output.append( curr )
break
if (i > 1):
# Found multiple successive ?s
# Handle the first half of the ?s by
# Append alternating characters from the previous character.
j = 0
while j < i / 2:
curr = opposite[curr]
output.append( curr )
j += 1
# Then handle the second half of the ?s
# append alternating characters to the next character after the ?s.
while j < i:
output.append( next if (j%2) == (i%2) else opposite[next] )
j += 1
elif i == 1:
# Found a single ?
prev = lookbehind(2)
if curr == prev and curr == opposite[next] and next == lookahead(2):
# No solution.
return None
if curr == prev or matches(curr, next):
output.append( opposite[curr] )
else:
output.append( curr )
if next != None:
# Output the next non-? character.
output.append( next )
curr_idx += i + 1
return ''.join(output)
strs = [
"a?bb", # "aabb"
"??abb", # "ababb" or "bbabb" or "baabb"
"a?b?aa", # "aabbaa"
"?bb?",
"aa?bb", # NO SOLUTION
"aa??aa", # "aabbaa"
"ab???bb?",
"????",
"??",
"?????",
"??????",
"?",
"a?",
"a??",
"a???",
"b?",
"b??",
"b???",
"a?a",
"a?b",
"b?a",
"b?b",
"a??a",
"a??b",
"b??a",
"b??b",
"aa?",
"ba?",
"a?bb",
"?bb?",
"??abb",
"a?b?aa",
"ab???bb?",
"aa?bb"
]
for s in strs:
_solve = solve(s)
_f = f(s)
print("%s\n%s, %s, %s, %s\n" % (s, is_valid(_f), is_valid(_solve), _solve, _f))
import random
def get_random(n):
char = 'x'
count = 0
out = []
not_c = lambda c: 'a' if c == 'b' else 'b'
for _ in range(n):
c = ['a', 'b'][int(random.random() * 2)]
if c != char:
out.append(c)
char, count = c, 1
elif count == 2:
out.append(not_c(c))
char, count = not_c(c), 1
else:
out.append(c)
count += 1
for i in range(n):
if int(random.random() * 2):
out[i] = '?'
return ''.join(out)
num_tests = 1000
n = 20
print("Starting random tests...")
for _ in range(num_tests):
s = get_random(n)
_solve = solve(s)
_f = f(s)
valid_solve = is_valid(_solve)
valid_f = is_valid(_f)
if not valid_f or not valid_solve:
print(s, valid_f, valid_solve, _f, _solve)
break
print("Done testing")
的可能实现。
这个实现是
- 从左到右
- 单次通过 2 次后视和 1 次前视(尽管有初始检查)
O(n)
时间复杂度
- 可以是
O(1)
space 复杂度,因为上下文最多为 4 个字符
- 是否不检查无效输入
先合并规则
a?? => ab?
a??b => abab
(a??b => ab?b => abab
)
a??a => abba
a???????????????????b => ab??????????????????b
ba?b => baab
^a?b => ^aab
a? => ab
否则(也意味着 a??
)
aa? => aab
a?a => aba
aa?b => aabb
然后设置边界条件。
规则需要字符串 not 以 ?
s 开头(如果只是在另一遍中填写它们则不需要)
^?? => a?
(就像我们在前面加上 b
)
^?a => ba
在 a?b
的情况下,该规则需要 2 次回顾,所以我只是预先应用它以防止在主循环中进行检查
- 预填
^a?b => ^aab
代码 (WandBox link)
char inverse(char c){return c=='a'?'b':'a';}
std::string solve(std::string s){
/// boundary conditions
if(s.size()<3){
for(auto& c:s) if(c=='?') c='b';
return s;
}
if(s.starts_with("??")) s[0]='a'; // ?? => a? // not really necessary if use another pass to fill prefix '?'s
if(s.starts_with("?a") || s.starts_with("?b")) s[0]=inverse(s[1]); // ?a => ba // not really necessary as above
if(s.starts_with("a??") || s.starts_with("b??")) s[1]=inverse(s[0]); // not really necessary, just to prevent access s[-1]
if(s.starts_with("a?b") || s.starts_with("b?a")) s[1]=s[0]; // ^a?b => aab
/// primary loop
for(auto curr=s.data(); curr!=s.data()+s.size(); ++curr)
if(*curr=='?'){
if(curr[-1]!=curr[1] && curr[-2]==curr[1]) *curr=curr[-1]; // ba?b => baab
else *curr = inverse(curr[-1]); // a? => b (rule coaslesing)
}
return s;
}
给定一个长度为 N 的字符串 S,return 一个字符串,该字符串是将字符串 S 中的每个 '?'
替换为 'a'
或 'b'
的结果字符且不包含三个相同的连续字母(换句话说,'aaa'
和 'bbb'
都不会出现在处理后的字符串中)。
示例:
Given S="a?bb", output= "aabb"
Given S="??abb", output= "ababb" or "bbabb" or "baabb"
Given S="a?b?aa", output= "aabbaa"
1<=n<=500000
我使用回溯法解决了这个问题,但是我的解决方案很慢,并且对更大的 N 值不起作用,有没有更好的方法?
我很快尝试了我在评论中提出的解决方案:
#include <iostream>
#include <string>
int main(void)
{
std::string s = "?????aaa??bbaa????";
std::cout << s <<'\n';
for(char &c:s)
if(c=='?')
c='a';
std::cout << s <<'\n';
for(int i =0; i<s.size()-2; i++)
{
if(s[i] == 'a'
&& s[i+1] == 'a'
&& s[i+2] == 'a' )
{
s[i+1] = 'b';
i++;
}
}
std::cout << s <<'\n';
return 0;
}
似乎符合给定的规则。 但也许有些情况需要检查和修复。
(请参阅我的贪婪
O(n)
动态程序有四种状态:一个字符是a_first
、a_second
、b_first
或b_second
。让 dp[i]
表示创建索引 i
之前的有效字符串的可能性。那么:
dp[i][a_first] = True
dp[i][a_second] = True
dp[i][b_first] = True
dp[i][b_second] = True
where i < 0
if s[i] == '?':
dp[i][a_first] = dp[i-1][b_first] or dp[i-1][b_second]
dp[i][a_second] = dp[i-1][a_first]
dp[i][b_first] = dp[i-1][a_first] or dp[i-1][a_second]
dp[i][b_second] = dp[i-1][b_first]
else if s[i] == 'a':
dp[i][a_first] = dp[i-1][b_first] or dp[i-1][b_second]
dp[i][a_second] = dp[i-1][a_first]
dp[i][b_first] = False
dp[i][b_second] = False
else:
dp[i][a_first] = False
dp[i][a_second] = False
dp[i][b_first] = dp[i-1][a_first] or dp[i-1][a_second]
dp[i][b_second] = dp[i-1][b_first]
where i ≥ 0
要获取字符串,我们可以按照始终保持True并保持连续字符约束的路径向后。
Python代码:
def f(s):
dp = [[None] * 4 for _ in range(len(s))]
def get_dp(i, j):
return True if i < 0 else dp[i][j]
for i in range(len(s)):
if s[i] == '?':
dp[i][0] = get_dp(i-1, 2) or get_dp(i-1, 3)
dp[i][1] = get_dp(i-1, 0)
dp[i][2] = get_dp(i-1, 0) or get_dp(i-1, 1)
dp[i][3] = get_dp(i-1, 2)
elif s[i] == 'a':
dp[i][0] = get_dp(i-1, 2) or get_dp(i-1, 3)
dp[i][1] = get_dp(i-1, 0)
dp[i][2] = False
dp[i][3] = False
else:
dp[i][0] = False
dp[i][1] = False
dp[i][2] = get_dp(i-1, 0) or get_dp(i-1, 1)
dp[i][3] = get_dp(i-1, 2)
# Build the output
result = []
i = len(s) - 1
need = None
while i >= 0:
if dp[i][0] and need != 'b':
result.append('a')
need = None if (len(result) == 1 or result[-2] == 'b') else 'b'
i-= 1
elif dp[i][1] and need != 'b':
result.extend(['a', 'a'])
need = 'b'
i -= 2
elif dp[i][2] and need != 'a':
result.append('b')
need = None if (len(result) == 1 or result[-2] == 'a') else 'a'
i -= 1
elif dp[i][3] and need != 'a':
result.extend(['b', 'b'])
need = 'a'
i -= 2
else:
return ""
return "".join(reversed(result))
输出:
strs = [
"a?bb", # "aabb"
"??abb", # "ababb" or "bbabb" or "baabb"
"a?b?aa", # "aabbaa"
"?bb?",
"aa?bb", # NO SOLUTION
"aa??aa", # "aabbaa"
"ab???bb?",
"????",
"??",
"?????",
"??????"
]
for s in strs:
print("%s\n%s\n" % (s, f(s)))
"""
a?bb
aabb
??abb
baabb
a?b?aa
aabbaa
?bb?
abba
aa?bb
aa??aa
aabbaa
ab???bb?
abbaabba
????
abaa
??
aa
?????
aabaa
??????
baabaa
"""
迭代回溯解决方案。
isValid
只检查在位置pos
处插入字符c
是否会产生问题,而不遍历整个字符串;维护一个变量
int dir = +-1
来知道我们是在字符串中向前还是向后移动(这很重要,这样我们就可以知道在跳过非字符串时向哪个方向移动-?
原字符串中的字符);if/else if/else
的森林来决定我们在哪里(非?
跳过,或新鲜?
前进,或已经尝试a
,或者已经尝试过a
和b
);solve
有一个布尔值 return 如果找到解决方案 (pos == s.length()
),则该值为true
,或者false
如果未找到解决方案 (pos == (unsigned int) -1
)。
#include <iostream>
#include <vector>
bool isValid(char c, std::string const &s, unsigned int pos)
{
return ((pos < 2 || s[pos - 2] != c || s[pos - 1] != c)
&& (pos < 1 || pos + 1 >= s.length() || s[pos - 1] != c || s[pos + 1] != c)
&& (pos + 2 >= s.length() || s[pos + 1] != c || s[pos + 2] != c));
}
bool solve(std::string const &in, std::string &out)
{
unsigned int pos = 0;
int dir = 1;
out = in;
while (pos < in.size())
{
if (in[pos] != '?') // nothing to do: keep moving (forward or backward)
{
pos += dir;
}
else if (out[pos] == '?') // going forward, will try 'a' then 'b'
{
dir = 1;
if (isValid('a', out, pos))
{
out[pos] = 'a';
pos += dir;
}
else if (isValid('b', out, pos))
{
out[pos] = 'b';
pos += dir;
}
else
{
dir = -1;
pos += dir;
}
}
else if (out[pos] == 'a' && isValid('b', out, pos)) // going backward, already tried 'a'
{
out[pos] = 'b';
dir = 1;
pos += dir;
}
else // already tried everything: backtrack
{
dir = -1;
out[pos] = '?';
pos += dir;
}
}
return (pos == in.size());
}
int main(void)
{
std::vector<std::string> ins = {"a?bb", "??abb", "a?b?aa", "aa?bb"};
std::vector<std::string> outs = {"", "", "", "", ""};
for (unsigned int i = 0; i < ins.size(); ++i)
{
if (solve(ins[i], outs[i]))
std::cout << ins[i] << " --> " << outs[i] << std::endl;
else
std::cout << ins[i] << " --> NO SOLUTION" << std::endl;
}
return 0;
}
输出:
a?bb --> aabb
??abb --> ababb
a?b?aa --> aabbaa
aa?bb --> NO SOLUTION
首先x??y
有超过1个?总会过去的
a??b => abab
(两边不增加长度,*不影响*后面)a??a => abba
(无影响)a???????????????????b => ab?????????????????ab
所以你只需要考虑单?
aa? => aab
(没有其他可能性)a?a => aba
(无影响)
所以唯一的问题是a?b
aa?b => aabb
(如aa?
中所述)ba?b => baab
(无影响)^a?b
=>^aab
(仅起步,无影响)
您最多进行了 2 次回顾和 2 次回顾,所以这是一个 O(n)
解决方案,
如果它可以包含无效输入,只需 运行 另一个 O(n)
检查
我在另一个回答里加了一个
在JavaScript中实现
let solve = (str) => {
let opposite = {"a":"b","b":"a"};
let curr_idx = 0;
let output = [];
let lookahead = (x) => ((curr_idx+x<0)||(curr_idx+x>=str.length)?null:str[curr_idx + x]);
let lookbehind = (x) => (curr_idx-x<0?null:output[curr_idx - x])
let matches = (x,y) => (x == y || x == null || y == null);
let is_replacement = (x) => (x == '?');
while ( curr_idx < str.length )
{
let curr = lookbehind(1) || 'b';
let i = 0;
let next = lookahead(i);
while (is_replacement(next))
{
++i;
next = lookahead(i);
}
if (next == null)
{
// Found the end of the string.
// Append characters opposite to the previous for each ?
for (; i > 0; --i)
{
curr = opposite[curr];
output.push( curr );
}
break;
}
if (i > 1)
{
// Found multiple successive ?s
// Handle the first half of the ?s by
// Append alternating characters from the previous character.
let j = 0;
for (; j < i/ 2; ++j)
{
curr = opposite[curr];
output.push( curr );
}
// Then handle the second half of the ?s
// append alternating characters to the next character after the ?s.
for (; j < i; ++j)
{
output.push( (j%2) == (i%2) ? next : opposite[next] );
}
}
else if (i == 1)
{
// Found a single ?
let prev = lookbehind(2);
if (curr == prev && curr == opposite[next] && next == lookahead(2))
{
// No solution.
return null;
}
if (curr == prev || matches(curr, next))
{
output.push( opposite[curr] );
}
else
{
output.push( curr );
}
}
if ( next != null )
{
// Output the next non-? character.
output.push( next );
}
curr_idx += i + 1;
}
return output.join("");
};
let tests = [
"?",
"a?",
"a??",
"a???",
"b?",
"b??",
"b???",
"a?a",
"a?b",
"b?a",
"b?b",
"a??a",
"a??b",
"b??a",
"b??b",
"aa?",
"ba?",
"a?bb",
"?bb?",
"??abb",
"a?b?aa",
"ab???bb?",
"aa?bb",
];
for ( let test of tests )
{
console.log( `${test} -> ${solve(test)}` );
}
更新 - 所有可能的解决方案
可以将问题减少到 10 条规则,从左到右对字符串进行操作,最多 window 6 个字符(2 个后向和 4 个前向),这样规则可以为字符串生成所有可能的解决方案:
Rules:
0: ss?dd_ -> No solution
1: __??ss -> __?dss
2: _s?s__ -> _sds__
3: ss?___ -> ssd___
4: __?ss_ -> __dss_
5: DS?DS_ -> DSsDS_ | DSdDS_
6: DS??sD -> DSsdsD | DSddsD | DSdssD
7: Ds??dS -> DsdsdS | DssddS
8: ^??X_ -> ^s?X_ | ^d?X_
9: Ds??X_ -> DssdX_ | Dsd?X_
Notations:
s: Same character.
d: The opposite of the same character.
S: Either the same character or the end-of-the-string.
D: Either the opposite of the same character or the start- or end-of-the-string.
_: Any character or the start- or end-of-the-string.
?: A character to replace.
X: Either a character to replace or the end-of-the-string.
^: The start-of-the-string.
(注:规则一与规则四基本相同,只是先处理后面的替换字符。)
这导致 JavaScript 代码:
function* solve(
str,
i = 0,
depth = 0
)
{
let data = Array.from(str);
let chr = (n) => (n < 0 || n > data.length ? null : data[n]);
let lookbehind2 = chr(i - 2);
let lookbehind1 = chr(i - 1);
let current = chr(i);
let lookahead1 = chr(i + 1);
let lookahead2 = chr(i + 2);
let lookahead3 = chr(i + 3);
const DEBUG = (rule) => {
if (false)
{
console.log(`${"".padStart(depth)}Rule ${rule}@${i}: ${str} -> ${data.join("")}`)
}
};
let stepForward = (steps) => {
for (let n = 0; n < steps; ++n)
{
++i;
lookbehind2 = lookbehind1;
lookbehind1 = current;
current = lookahead1;
lookahead1 = lookahead2;
lookahead2 = lookahead3;
lookahead3 = chr(i + 3);
}
}
let isSolved = (ch) => (ch == 'a' || ch == 'b');
let isSame = (ch1, ch2) => (isSolved(ch1) && ch1 == ch2);
let opposite = (ch) => ({"a":"b","b":"a"}[ch]);
let isOpposite = (ch1, ch2) => (isSolved(ch1) && ch1 == opposite(ch2));
let isEndOfString = (ch) => (ch == null);
let isStartOfString = (ch) => (ch == null);
while( !isEndOfString(current) )
{
if (current != '?')
{
stepForward(1);
continue;
}
// Rules:
// 0: ss?dd_ -> No solution
// 1: __??ss -> __?dss
// 2: _s?s__ -> _sds__
// 3: ss?___ -> ssd___
// 4: __?ss_ -> __dss_
// 5: DS?DS_ -> DSsDS_ | DSdDS_
// 6: DS??sD -> DSsdsD | DSddsD | DSdssD
// 7: Ds??dS -> DsdsdS | DssddS
// 8: $??X_ -> $s?X_ | $d?X_
// 9: Ds??X_ -> DssdX_ | Dsd?X_
//
// Notations:
// s: Same character.
// d: The opposite of the same character.
// S: Either the same character or the end-of-the-string.
// D: Either the opposite of the same character or the start- or end-of-the-string.
// _: Any character or the start- or end-of-the-string.
// ?: A character to replace.
// X: Either a character to replace or the end-of-the-string.
// $: The end-of-the-string.
// -----------------------------------------------------------
// Rule 0: ss?dd_ -> No solution
if (
isSame(lookbehind2, lookbehind1)
&& isSame(lookahead1, lookahead2)
&& lookbehind1 != lookahead1
)
{
DEBUG(0);
return;
}
// Rule 1: __??ss -> __?dss
if (lookahead1 == '?' && isSame(lookahead2, lookahead3))
{
data[i + 1] = lookahead1 = opposite(lookahead2);
DEBUG(1);
}
// Rule 2: _s?s__ -> _sds__
if (isSame(lookbehind1, lookahead1))
{
data[i] = current = opposite(lookahead1);
DEBUG(2);
stepForward(2);
continue;
}
// Rule 3: ss?___ -> ssd___
if (isSame(lookbehind2, lookbehind1))
{
data[i] = current = opposite(lookbehind1);
DEBUG(3);
stepForward(1);
continue;
}
// Rule 4: __?ss_ -> __dss_
if (isSame(lookahead1, lookahead2))
{
data[i] = current = opposite(lookahead1);
DEBUG(4);
stepForward(3);
continue;
}
// Rule 5: DS?DS_ -> DSsDS_ | DSdDS_
if (lookahead1 != '?')
{
data[i] = current = "a";
DEBUG(5);
for (solution of solve(data.join(""), i + 2, depth + 1))
{
yield solution;
}
data[i] = current = "b";
DEBUG(5);
for (solution of solve(data.join(""), i + 2, depth + 1))
{
yield solution;
}
return;
}
// Rule 6: DS??sD -> DSsdsD | DSddsD | DSdssD
if (
isSame(lookbehind1, lookahead2)
|| (isStartOfString(lookbehind1) && isSolved(lookahead2))
)
{
data[i] = current = lookahead2;
data[i+1] = lookahead1 = opposite(lookahead2);
DEBUG(6);
for (solution of solve(data.join(""), i + 3, depth + 1))
{
yield solution;
}
data[i] = current = opposite(lookahead2);
data[i+1] = lookahead1 = opposite(lookahead2);
DEBUG(6);
for (solution of solve(data.join(""), i + 3, depth + 1))
{
yield solution;
}
data[i] = current = opposite(lookahead2);
data[i+1] = lookahead1 = lookahead2;
DEBUG(6);
for (solution of solve(data.join(""), i + 3, depth + 1))
{
yield solution;
}
return;
}
// Rule 7: Ds??dS -> DsdsdS | DssddS
if (isOpposite(lookahead2, lookbehind1))
{
data[i] = current = lookahead2;
data[i+1] = lookahead1 = opposite(lookahead2);
DEBUG(7);
for (solution of solve(data.join(""), i + 3, depth + 1))
{
yield solution;
}
data[i] = current = opposite(lookahead2);
data[i+1] = lookahead1 = lookahead2;
DEBUG(7);
for (solution of solve(data.join(""), i + 3, depth + 1))
{
yield solution;
}
return;
}
// Rule 8: $??X_ -> $s?X_ | $d?X_
// Rule 9: Ds??X_ -> DssdX_ | Dsd?X_
if (lookahead2 == "?" || isEndOfString(lookahead2))
{
if (isStartOfString(lookbehind1))
{
data[i] = current = "a";
DEBUG(8);
for (solution of solve(data.join(""), i + 1, depth + 1))
{
yield solution;
}
data[i] = current = "b";
DEBUG(8);
for (solution of solve(data.join(""), i + 1, depth + 1))
{
yield solution;
}
}
else
{
data[i] = current = opposite(lookbehind1);
DEBUG(9);
for (solution of solve(data.join(""), i + 1, depth + 1))
{
yield solution;
}
data[i] = current = lookbehind1;
data[i+1] = lookahead1 = opposite(lookbehind1);
DEBUG(9);
for (solution of solve(data.join(""), i + 2, depth + 1))
{
yield solution;
}
}
return;
}
throw `Invalid state ${data.join("")}`;
}
yield data.join("");
}
let tests = [
"?",
"??",
"???",
"a?",
"a??",
"a???",
"b?",
"b??",
"b???",
"a?a",
"a?b",
"b?a",
"b?b",
"a??a",
"a??b",
"b??a",
"b??b",
"aa?",
"ba?",
"a?bb",
"?bb?",
"?a",
"?aa",
"??aa",
"???aa",
"????aa",
"?ab",
"??ab",
"???ab",
"????ab",
"a?b?aa",
"ab???bb?",
"aa?bb",
"a?b?a?bb",
"?a?b?aa",
];
for ( let test of tests )
{
console.log( `${test} -> ${[...solve(test)]}` );
}
如果你想要一个单一的解决方案,那么你应该能够只为每个规则获取第一个匹配项,并在 O(N) 时间和 O(1) 内存求解器中实现它。
更新 2:C++ 解决方案
与 JavaScript 解决方案相同的算法,但使用了一堆部分解决方案(而不是递归调用生成器函数的 JavaScript 解决方案)。
#include <iostream>
#include <list>
class PartialSolution
{
public:
const std::string partial_solution;
const int position;
PartialSolution(
std::string const &solution,
const int pos
):
partial_solution(solution),
position(pos)
{}
};
class Solver
{
const bool DEBUGGING = false;
std::string str;
int position;
char lookbehind2;
char lookbehind1;
char current;
char lookahead1;
char lookahead2;
char lookahead3;
char get_character(int pos) const
{
if ( pos < 0 || pos >= str.length() )
{
return '[=13=]';
}
return str[pos];
}
void load_partial_solution(PartialSolution const &ps)
{
str = ps.partial_solution;
position = ps.position;
lookbehind2 = get_character(position - 2);
lookbehind1 = get_character(position - 1);
current = get_character(position + 0);
lookahead1 = get_character(position + 1);
lookahead2 = get_character(position + 2);
lookahead3 = get_character(position + 3);
if (DEBUGGING)
{
std::cout << "Load @" << position << ": " << str << "\n";
}
}
void step_forward(int n)
{
for (int i = 0; i < n; ++i)
{
++position;
lookbehind2 = lookbehind1;
lookbehind1 = current;
current = lookahead1;
lookahead1 = lookahead2;
lookahead2 = lookahead3;
lookahead3 = get_character(position + 3);
}
}
bool is_solved(char ch) const
{
return ch == 'a' || ch == 'b';
}
bool is_same(char ch1, char ch2) const
{
return is_solved(ch1) && ch1 == ch2;
}
char opposite(char ch) const
{
switch(ch)
{
case 'a': return 'b';
case 'b': return 'a';
default:
return '[=13=]';
}
}
bool is_opposite(char ch1, char ch2) const
{
return is_solved(ch1) && ch1 == opposite(ch2);
}
bool is_end_of_string(char ch) const
{
return ch == '[=13=]';
}
bool is_start_of_string(char ch) const
{
return ch == '[=13=]';
}
void DEBUG(int rule, bool pushback = false) const
{
if (DEBUGGING)
{
std::cout << (pushback?"Save: ":"") << "Rule " << rule << "@" << position << ": " << str << "\n";
}
}
public:
std::list<std::string> solve(std::string const&);
};
std::list<std::string> Solver::solve(std::string const& data)
{
std::list<PartialSolution> partial_solutions = { PartialSolution(data, 0) };
std::list<std::string> solutions = {};
while( !partial_solutions.empty() )
{
load_partial_solution( partial_solutions.back() );
partial_solutions.pop_back();
while( !is_end_of_string(current) )
{
if (current != '?')
{
step_forward(1);
continue;
}
// Rules:
// 0: ss?dd_ -> No solution
// 1: __??ss -> __?dss
// 2: _s?s__ -> _sds__
// 3: ss?___ -> ssd___
// 4: __?ss_ -> __dss_
// 5: DS?DS_ -> DSsDS_ | DSdDS_
// 6: DS??sD -> DSsdsD | DSddsD | DSdssD
// 7: Ds??dS -> DsdsdS | DssddS
// 8: $??X_ -> $s?X_ | $d?X_
// 9: Ds??X_ -> DssdX_ | Dsd?X_
//
// Notations:
// s: Same character.
// d: The opposite of the same character.
// S: Either the same character or the end-of-the-string.
// D: Either the opposite of the same character or the start- or end-of-the-string.
// _: Any character or the start- or end-of-the-string.
// ?: A character to replace.
// X: Either a character to replace or the end-of-the-string.
// $: The end-of-the-string.
// -----------------------------------------------------------
// Rule 0: ss?dd_ -> No solution
if (
is_same(lookbehind2, lookbehind1)
&& is_same(lookahead1, lookahead2)
&& lookbehind1 != lookahead1
)
{
DEBUG(0);
goto no_solution;
}
// Rule 1: __??ss -> __?dss
if (lookahead1 == '?' && is_same(lookahead2, lookahead3))
{
lookahead1 = opposite(lookahead2);
str[position+1] = lookahead1;
DEBUG(1);
}
// Rule 2: _s?s__ -> _sds__
if (is_same(lookbehind1, lookahead1))
{
str[position] = current = opposite(lookahead1);
DEBUG(2);
step_forward(2);
continue;
}
// Rule 3: ss?___ -> ssd___
if (is_same(lookbehind2, lookbehind1))
{
str[position] = current = opposite(lookbehind1);
DEBUG(3);
step_forward(1);
continue;
}
// Rule 4: __?ss_ -> __dss_
if (is_same(lookahead1, lookahead2))
{
str[position] = current = opposite(lookahead1);
DEBUG(4);
step_forward(3);
continue;
}
// Rule 5: DS?DS_ -> DSsDS_ | DSdDS_
if (lookahead1 != '?')
{
str[position] = current = 'b';
DEBUG(5, true);
partial_solutions.emplace_back(str, position + 2);
str[position] = current = 'a';
DEBUG(5);
step_forward(2);
continue;
}
// Rule 6: DS??sD -> DSsdsD | DSddsD | DSdssD
if (
is_same(lookbehind1, lookahead2)
|| (is_start_of_string(lookbehind1) && is_solved(lookahead2))
)
{
str[position] = current = opposite(lookahead2);
str[position+1] = lookahead1 = lookahead2;
DEBUG(6, true);
partial_solutions.emplace_back(str, position + 3);
str[position] = current = opposite(lookahead2);
str[position+1] = lookahead1 = opposite(lookahead2);
DEBUG(6, true);
partial_solutions.emplace_back(str, position + 3);
str[position] = current = lookahead2;
str[position+1] = lookahead1 = opposite(lookahead2);
DEBUG(6);
step_forward(3);
continue;
}
// Rule 7: Ds??dS -> DsdsdS | DssddS
if (is_opposite(lookahead2, lookbehind1))
{
str[position] = current = opposite(lookahead2);
str[position+1] = lookahead1 = lookahead2;
DEBUG(7, true);
partial_solutions.emplace_back(str, position + 3);
str[position] = current = lookahead2;
str[position+1] = lookahead1 = opposite(lookahead2);
DEBUG(7);
step_forward(3);
continue;
}
// Rule 8: $??X_ -> $s?X_ | $d?X_
// Rule 9: Ds??X_ -> DssdX_ | Dsd?X_
if (lookahead2 == '?' || is_end_of_string(lookahead2))
{
if (is_start_of_string(lookbehind1))
{
str[position] = current = 'b';
DEBUG(8, true);
partial_solutions.emplace_back(str, position + 1);
str[position] = current = 'a';
DEBUG(8);
step_forward(1);
continue;
}
else
{
str[position] = current = lookbehind1;
str[position+1] = lookahead1 = opposite(lookbehind1);
DEBUG(9, true);
partial_solutions.emplace_back(str, position + 2);
str[position] = current = opposite(lookbehind1);
str[position+1] = lookahead1 = '?';
DEBUG(9);
continue;
}
}
throw std::invalid_argument(str);
}
solutions.push_back(str);
no_solution:;
}
return solutions;
}
void run_test(
Solver &solver,
std::string const &test
)
{
std::cout << test << " -> ";
std::list<std::string> solutions = solver.solve(test);
int size = solutions.size();
for (const auto& solution : solutions)
{
std::cout << solution;
if (--size != 0)
{
std::cout << ", ";
}
}
std::cout << "\n";
}
int main()
{
std::list<std::string> tests = {
"?",
"??",
"???",
"a?",
"a??",
"a???",
"b?",
"b??",
"b???",
"a?a",
"a?b",
"b?a",
"b?b",
"a??a",
"a??b",
"b??a",
"b??b",
"aa?",
"ba?",
"a?bb",
"?bb?",
"?a",
"?aa",
"??aa",
"???aa",
"????aa",
"?ab",
"??ab",
"???ab",
"????ab",
"a?b?aa",
"ab???bb?",
"aa?bb",
"a?b?a?bb",
"?a?b?aa",
};
Solver solver = Solver();
for (const auto& test : tests)
{
run_test(solver, test);
}
}
下面的贪婪版本似乎有效。这样,除了一个输出字符串外,我们还可以有常量 space。有人可以帮忙找一个反例吗?我在实现简单的动态程序之前写下了这个想法(参见this revision)。尽量保持两个相同字符的顺序。
下面代码的最后一部分包括随机测试
Python代码:
def is_valid(s):
if not s:
return True
char = "x"
count = 0
for c in s:
if c != char:
char, count = c, 1
else:
count += 1
if count == 3:
return False
return True
# My greedy solution
def f(s):
if len(s) == 2:
return s.replace('?', 'a')
aa = ['a', 'a']
bb = ['b', 'b']
out = []
i = 0
a, b = 0, 0
while i < len(s):
if s[i] == 'a' or (s[i] == '?' and b == 2):
if s[i] == 'a' and a == 2:
if s[i-3:i-1] == "??":
out[i-3], out[i-2] = out[i-2], out[i-3]
a -= 1
else:
return ""
out.append('a')
a, b = a + 1, 0
i += 1
elif s[i] == 'b' or (s[i] == '?' and a == 2):
if s[i] == 'b' and b == 2:
if s[i-3:i-1] == "??":
out[i-3], out[i-2] = out[i-2], out[i-3]
b -= 1
else:
return ""
out.append('b')
a, b = 0, b + 1
i += 1
elif i == len(s) - 1:
out.append('a' if b else 'b')
i += 1
elif i == len(s) - 2:
if s[i+1] == '?':
out.extend(aa if b else bb)
else:
out.append('a' if b else 'b')
out.append(s[i+1])
i += 2
elif s[i+1] == '?':
if a:
out.append('a')
a, b = a + 1, 0
else:
out.append('b')
a, b = 0, b + 1
i += 1
elif s[i+1] == 'a':
if a or b < 2:
out.append('b')
a, b = 0, b + 1
else:
out.append('a')
a, b = a + 1, 0
i += 1
elif s[i+1] == 'b':
if b or a < 2:
out.append('a')
a, b = a + 1, 0
else:
out.append('b')
a, b = 0, b + 1
i += 1
return ''.join(out)
#
# Code by MTO
def solve(_str):
opposite = {"a": "b", "b": "a"}
curr_idx = 0
output = []
lookahead = lambda x: None if ((curr_idx + x < 0) or (curr_idx + x >= len(_str))) else _str[curr_idx + x]
lookbehind = lambda x: None if curr_idx-x< 0 else output[curr_idx - x]
matches = lambda x, y: x == y or x == None or y == None
is_replacement = lambda x: x == '?'
while curr_idx < len(_str):
curr = lookbehind(1) or 'b'
i = 0
next = lookahead(i)
while is_replacement(next):
i += 1
next = lookahead(i)
if next == None:
# Found the end of the string.
# Append characters opposite to the previous for each ?
for _ in range(i, 0, -1):
curr = opposite[curr]
output.append( curr )
break
if (i > 1):
# Found multiple successive ?s
# Handle the first half of the ?s by
# Append alternating characters from the previous character.
j = 0
while j < i / 2:
curr = opposite[curr]
output.append( curr )
j += 1
# Then handle the second half of the ?s
# append alternating characters to the next character after the ?s.
while j < i:
output.append( next if (j%2) == (i%2) else opposite[next] )
j += 1
elif i == 1:
# Found a single ?
prev = lookbehind(2)
if curr == prev and curr == opposite[next] and next == lookahead(2):
# No solution.
return None
if curr == prev or matches(curr, next):
output.append( opposite[curr] )
else:
output.append( curr )
if next != None:
# Output the next non-? character.
output.append( next )
curr_idx += i + 1
return ''.join(output)
strs = [
"a?bb", # "aabb"
"??abb", # "ababb" or "bbabb" or "baabb"
"a?b?aa", # "aabbaa"
"?bb?",
"aa?bb", # NO SOLUTION
"aa??aa", # "aabbaa"
"ab???bb?",
"????",
"??",
"?????",
"??????",
"?",
"a?",
"a??",
"a???",
"b?",
"b??",
"b???",
"a?a",
"a?b",
"b?a",
"b?b",
"a??a",
"a??b",
"b??a",
"b??b",
"aa?",
"ba?",
"a?bb",
"?bb?",
"??abb",
"a?b?aa",
"ab???bb?",
"aa?bb"
]
for s in strs:
_solve = solve(s)
_f = f(s)
print("%s\n%s, %s, %s, %s\n" % (s, is_valid(_f), is_valid(_solve), _solve, _f))
import random
def get_random(n):
char = 'x'
count = 0
out = []
not_c = lambda c: 'a' if c == 'b' else 'b'
for _ in range(n):
c = ['a', 'b'][int(random.random() * 2)]
if c != char:
out.append(c)
char, count = c, 1
elif count == 2:
out.append(not_c(c))
char, count = not_c(c), 1
else:
out.append(c)
count += 1
for i in range(n):
if int(random.random() * 2):
out[i] = '?'
return ''.join(out)
num_tests = 1000
n = 20
print("Starting random tests...")
for _ in range(num_tests):
s = get_random(n)
_solve = solve(s)
_f = f(s)
valid_solve = is_valid(_solve)
valid_f = is_valid(_f)
if not valid_f or not valid_solve:
print(s, valid_f, valid_solve, _f, _solve)
break
print("Done testing")
这个实现是
- 从左到右
- 单次通过 2 次后视和 1 次前视(尽管有初始检查)
O(n)
时间复杂度- 可以是
O(1)
space 复杂度,因为上下文最多为 4 个字符 - 是否不检查无效输入
先合并规则
a?? => ab?
a??b => abab
(a??b => ab?b => abab
)a??a => abba
a???????????????????b => ab??????????????????b
ba?b => baab
^a?b => ^aab
a? => ab
否则(也意味着a??
)aa? => aab
a?a => aba
aa?b => aabb
然后设置边界条件。
规则需要字符串 not 以 ?
s 开头(如果只是在另一遍中填写它们则不需要)
^?? => a?
(就像我们在前面加上b
)^?a => ba
在 a?b
的情况下,该规则需要 2 次回顾,所以我只是预先应用它以防止在主循环中进行检查
- 预填
^a?b => ^aab
代码 (WandBox link)
char inverse(char c){return c=='a'?'b':'a';}
std::string solve(std::string s){
/// boundary conditions
if(s.size()<3){
for(auto& c:s) if(c=='?') c='b';
return s;
}
if(s.starts_with("??")) s[0]='a'; // ?? => a? // not really necessary if use another pass to fill prefix '?'s
if(s.starts_with("?a") || s.starts_with("?b")) s[0]=inverse(s[1]); // ?a => ba // not really necessary as above
if(s.starts_with("a??") || s.starts_with("b??")) s[1]=inverse(s[0]); // not really necessary, just to prevent access s[-1]
if(s.starts_with("a?b") || s.starts_with("b?a")) s[1]=s[0]; // ^a?b => aab
/// primary loop
for(auto curr=s.data(); curr!=s.data()+s.size(); ++curr)
if(*curr=='?'){
if(curr[-1]!=curr[1] && curr[-2]==curr[1]) *curr=curr[-1]; // ba?b => baab
else *curr = inverse(curr[-1]); // a? => b (rule coaslesing)
}
return s;
}