给定任意字符串,如何在线性时间内添加最少的字符,使它成为回文字符串?
我只能想到一个O(N^2)的解决方案。
有人能帮我提供一个O(N)的解决方案吗?
1和3显然是线性的,2也是线性的,因为Knuth-Morris-Pratt是线性的。
Scala的解决方案:
def isPalindrome(s: String) = s.view.reverse == s.view
def makePalindrome(s: String) =
s + s.take((0 to s.length).find(i => isPalindrome(s.substring(i))).get).reverse
每个回文都可以看作是一组嵌套的字母对。
a n n a b o b
| | | | | * |
| -- | | |
--------- -----
anna: (0, 0) (1, 1)
bob: (0, 0) (1, 1)
(0, 0) (1, 2)
(0, 0) (2, 1)
def makePalindrome(str: String): String = {
/** Finds the pareto-minimum subset of a set of points (here pair of indices).
* Could be done in linear time, without sorting, but O(n log n) is not that bad ;) */
def paretoMin(points: Iterable[(Int, Int)]): List[(Int, Int)] = {
val sorted = points.toSeq.sortBy(identity)
(List.empty[(Int, Int)] /: sorted) { (result, e) =>
if (result.isEmpty || e._2 <= result.head._2)
e :: result
else
result
}
}
/** Find all pairs directly nested within a given pair.
* For performance reasons tries to not include suboptimal pairs (pairs nested in any of the pairs also in the result)
* although it wouldn't break anything as prune takes care of this. */
def pairs(left: Int, right: Int): Iterable[(Int, Int)] = {
val builder = List.newBuilder[(Int, Int)]
var rightMax = str.length
for (i <- left until (str.length - right)) {
rightMax = math.min(str.length - left, rightMax)
val subPairs =
for (j <- right until rightMax if str(i) == str(str.length - j - 1)) yield (i, j)
subPairs.headOption match {
case Some((a, b)) => rightMax = b; builder += ((a, b))
case None =>
}
}
builder.result()
}
/** Builds sequences of size n+1 from sequence of size n */
def extend(path: List[(Int, Int)]): Iterable[List[(Int, Int)]] =
for (p <- pairs(path.head._1 + 1, path.head._2 + 1)) yield p :: path
/** Whether full or degenerated. Full-pairs save us 2 characters, degenerated save us only 1. */
def isFullPair(pair: (Int, Int)) =
pair._1 + pair._2 < str.length - 1
/** Removes pareto-suboptimal sequences */
def prune(sequences: List[List[(Int, Int)]]): List[List[(Int, Int)]] = {
val allowedHeads = paretoMin(sequences.map(_.head)).toSet
val containsFullPair = allowedHeads.exists(isFullPair)
sequences.filter(s => allowedHeads.contains(s.head) && (isFullPair(s.head) || !containsFullPair))
}
/** Dynamic-Programming step */
@tailrec
def search(sequences: List[List[(Int, Int)]]): List[List[(Int, Int)]] = {
val nextStage = prune(sequences.flatMap(extend))
nextStage match {
case List() => sequences
case x => search(nextStage)
}
}
/** Converts a sequence of nested pairs to a palindrome */
def sequenceToString(sequence: List[(Int, Int)]): String = {
val lStr = str
val rStr = str.reverse
val half =
(for (List(start, end) <- sequence.reverse.sliding(2)) yield
lStr.substring(start._1 + 1, end._1) + rStr.substring(start._2 + 1, end._2) + lStr(end._1)).mkString
if (isFullPair(sequence.head))
half + half.reverse
else
half + half.reverse.substring(1)
}
sequenceToString(search(List(List((-1, -1)))).head)
}
注意:该代码未列出所有回文,但仅提供一个示例,保证其长度最小。通常情况下,有可能存在许多与最小长度相同的回文(在最坏情况下为O(2^n),因此您可能不想枚举它们全部)。
O(n * 2k)
时间内找到匹配的子字符串,其中n
是输入字符串的长度,k
是我们正在搜索的子字符串,但不能在O(n)
的时间内告诉你输入字符串中最长的回文子串是什么。x
,则添加的最小字符数为n-x
。例如,字符串aaba
的最长后缀子串是长度为3
的aba
,因此我们的答案是1
。判断一个字符串是否是回文字符串的算法需要O(n)
时间,无论是使用KMP还是更有效且简单的算法(O(n/2)
):
使用简单的算法,我们从整个字符串开始,检查它是否是回文字符串。如果是,我们就返回使用两个指针,一个在第一个字符处,另一个在最后一个字符处
比较指针处的字符,如果它们相等,则将每个指针向内移动,否则返回
false
当指针指向同一索引(奇数长度的字符串)或重叠(偶数长度的字符串)时,返回
true
0
;如果不是,我们就检查字符串string[1...end]
、string[2...end]
,直到达到单个字符并返回n-1
。这导致了O(n^2)
的运行时间。构建表格需要构建表格
搜索最长后缀回文串
O(n)
的时间,然后对每个子字符串string[0...end], string[1...end], ..., string[end-2...end]
进行“你是回文字符串吗”的每个检查都需要O(n)
的时间。在此情况下,k
是与简单算法所需的检查每个子字符串相同的因素,因为它从k=n
开始,然后经过k=n-1
、k=n-2
...就像简单算法一样。
KMP算法可以在 O(n)
时间内判断一个字符串是否为回文,但这并没有解决问题,因为你需要检查所有子串 string[0...end], string[1...end], ..., string[end - 2...end]
是否是回文,导致时间复杂度与简单的回文检查算法相同(甚至更差)。
O(n)时间复杂度的解法。 算法: 需要在给定的字符串中找到包含最后一个字符的最长回文子串。然后将所有不属于回文子串的字符按相反的顺序添加到字符串的末尾。
关键点: 在这个问题中,给定字符串中最长的回文子串必须包含最后一个字符。
例如: 输入:abacac 输出:abacacaba 在这里,输入中包含最后一个字母的最长回文子串是“cac”。因此,将“cac”之前的所有字母按相反的顺序添加到后面,使整个字符串成为回文。
使用C#编写,其中几个测试用例被注释掉了。
static public void makePalindrome()
{
//string word = "aababaa";
//string word = "abacbaa";
//string word = "abcbd";
//string word = "abacac";
//string word = "aBxyxBxBxyxB";
//string word = "Malayal";
string word = "abccadac";
int j = word.Length - 1;
int mark = j;
bool found = false;
for (int i = 0; i < j; i++)
{
char cI = word[i];
char cJ = word[j];
if (cI == cJ)
{
found = true;
j--;
if(mark > i)
mark = i;
}
else
{
if (found)
{
found = false;
i--;
}
j = word.Length - 1;
mark = j;
}
}
for (int i = mark-1; i >=0; i--)
word += word[i];
Console.Write(word);
}
}
str = 'xcbc' # Any string that you want.
arr1 = str.split('')
arr2 = arr1.reverse
count = 0
while(str != str.reverse)
count += 1
arr1.insert(count-1, arr2[count-1])
str = arr1.join('')
end
puts str
puts str.length - arr2.count
#include<iostream>
#include<string>
using std::cout;
using std::endl;
using std::cin;
int main() {
std::string word, left("");
cin >> word;
size_t start, end;
for (start = 0, end = word.length()-1; start < end; end--) {
if (word[start] != word[end]) {
left.append(word.begin()+end, 1 + word.begin()+end);
continue;
}
left.append(word.begin()+start, 1 + word.begin()+start), start++;
}
cout << left << ( start == end ? std::string(word.begin()+end, 1 + word.begin()+end) : "" )
<< std::string(left.rbegin(), left.rend()) << endl;
return 0;
}
我不知道它是否生成了最小数字,但它生成回文数。
解释:
在每次迭代中,我们检查每个字符是否相同,即 word[start]
== word[end]
?
word[start]
的副本附加到另一个称为 left
的字符串中,该字符串将在迭代完成时作为新回文字符串的左侧。然后我们将变量 (start
)++ 和 (end
)-- 向中心递增。word[end]
的副本附加到同一字符串 left
中。这就是算法的基础,直到循环完成。
当循环完成时,进行最后一次检查以确保如果我们得到了一个奇长度的回文字符串,则将中间字符附加到形成的新回文字符串的中间。
Using Recursion
#include <iostream>
using namespace std;
int length( char str[])
{ int l=0;
for( int i=0; str[i]!='\0'; i++, l++);
return l;
}
int palin(char str[],int len)
{ static int cnt;
int s=0;
int e=len-1;
while(s<e){
if(str[s]!=str[e]) {
cnt++;
return palin(str+1,len-1);}
else{
s++;
e--;
}
}
return cnt;
}
int main() {
char str[100];
cin.getline(str,100);
int len = length(str);
cout<<palin(str,len);
}
def shortPalin( S):
k=0
lis=len(S)
for i in range(len(S)/2):
if S[i]==S[lis-1-i]:
k=k+1
else :break
S=S[k:lis-k]
lis=len(S)
prev=0
w=len(S)
tot=0
for i in range(len(S)):
if i>=w:
break;
elif S[i]==S[lis-1-i]:
tot=tot+lcs(S[prev:i])
prev=i
w=lis-1-i
tot=tot+lcs(S[prev:i])
return tot
def lcs( S):
if (len(S)==1):
return 1
li=len(S)
X=[0 for x in xrange(len(S)+1)]
Y=[0 for l in xrange(len(S)+1)]
for i in range(len(S)-1,-1,-1):
for j in range(len(S)-1,-1,-1):
if S[i]==S[li-1-j]:
X[j]=1+Y[j+1]
else:
X[j]=max(Y[j],X[j+1])
Y=X
return li-X[0]
print shortPalin("tostotor")
我假设您无法替换或删除任何现有字符?
一个好的开始是反转其中一个字符串,并查找反转字符串和另一个字符串之间的最长公共子序列(LCS)。由于听起来这是一道作业或面试题,所以我将把其余部分留给您自己完成。
时间复杂度为O(n)的解决方案
public static void main(String[] args) {
String givenStr = "abtb";
String palindromeStr = covertToPalindrome(givenStr);
System.out.println(palindromeStr);
}
private static String covertToPalindrome(String str) {
char[] strArray = str.toCharArray();
int low = 0;
int high = strArray.length - 1;
int subStrIndex = -1;
while (low < high) {
if (strArray[low] == strArray[high]) {
high--;
} else {
high = strArray.length - 1;
subStrIndex = low;
}
low++;
}
return str + (new StringBuilder(str.substring(0, subStrIndex+1))).reverse().toString();
}