如何计算将字符串转换为回文所需的字符数?
Posted
技术标签:
【中文标题】如何计算将字符串转换为回文所需的字符数?【英文标题】:How can I compute the number of characters required to turn a string into a palindrome? 【发布时间】:2011-01-15 06:33:35 【问题描述】:我最近发现了一个竞赛问题,要求您计算必须在字符串中(任意位置)插入的最小字符数,以将其转换为回文。
例如,给定字符串:“abcbd”,我们可以通过仅插入两个字符将其变成回文:一个在“a”之后,另一个在“d”之后:“adbcbd一个”。
这似乎是一个类似问题的概括,它要求相同的东西,除了字符只能在末尾添加 - 这有一个使用哈希表在 O(N) 中非常简单的解决方案。
我一直在尝试修改Levenshtein distance algorithm 来解决这个问题,但没有成功。任何关于如何解决这个问题的帮助(它不一定要高效,我只对任何 DP 解决方案感兴趣)将不胜感激。
【问题讨论】:
@IVlad - 很好的问题。我删除了介绍位,并添加了指向 Levenshtein 上的 Wikipedia 文章的链接。希望没关系。欢迎使用 Stack Overflow! 我忘了问,这是作业吗? 不是作业。这是几年前在我国的计算机科学奥林匹克竞赛中提出的问题,我只是对解决方案感兴趣,因为我找不到官方解决方案,也找不到自己的 DP。 在这里查看我对一个非常相似的问题的回答:***.com/a/18758991/2771718 它使用 DP 方法。 【参考方案1】:注意:这只是一个好奇心。 Dav 提出了一种算法,该算法可以修改为 DP 算法,以便在 O(n^2) 时间和 O(n^2) 空间中轻松运行(并且可能在 O(n) 时更好地记账)。
当然,如果您决定更改允许的操作,这种“幼稚”算法实际上可能会派上用场。
这是一个“幼稚”的算法,通过巧妙的记账可能会更快。
给定一个字符串,我们猜测结果回文的中间部分,然后尝试计算使字符串围绕该中间部分成为回文所需的插入次数。
如果字符串的长度为 n,则有 2n+1 个可能的中间(每个字符,在两个字符之间,就在字符串之前和之后)。
假设我们考虑一个中间,它给了我们两个字符串 L 和 R(一个到左,一个到右)。
如果我们使用插入,我相信Longest Common Subsequence 算法(这是一种 DP 算法)现在可以用于创建一个包含 L 和 R 反转的“超级”字符串,请参阅Shortest common supersequence。
选择中间插入数量最少的。
我相信这是 O(n^3)。 (注意:我没有尝试证明它是真的)。
【讨论】:
例如,如果将回文以字符 k 为中心,如何使用 SCS 找出需要多少次插入?对于我的示例:“abcbd”。假设我们以第二个“b”为中心。 “abc”和“d”之间的 SCS 是“abcd”。这如何告诉您需要插入多少个字符?我认为它是 2*strlen(SCS) - strlen(L) - strlen(R),对吗?【参考方案2】:C# 添加到字符串末尾的递归解决方案:
有 2 个基本情况。当长度为 1 或 2 时。递归情况:如果极值相等,则 使回文成为没有极端的内部字符串,并返回带有极端的字符串。 如果极值不相等,则将第一个字符添加到末尾并使回文为 包含前一个最后一个字符的内部字符串。归还。
public static string ConvertToPalindrome(string str) // By only adding characters at the end
if (str.Length == 1) return str; // base case 1
if (str.Length == 2 && str[0] == str[1]) return str; // base case 2
else
if (str[0] == str[str.Length - 1]) // keep the extremes and call
return str[0] + ConvertToPalindrome(str.Substring(1, str.Length - 2)) + str[str.Length - 1];
else //Add the first character at the end and call
return str[0] + ConvertToPalindrome(str.Substring(1, str.Length - 1)) + str[0];
【讨论】:
【参考方案3】:我的 C# 解决方案在字符串中查找重复字符并使用它们来减少插入次数。在像 program 这样的词中,我使用 'r' 字符作为边界。在 'r's 内部,我将其设为回文(递归地)。在'r'之外,我镜像左右的字符。
一些输入有不止一个最短输出:输出可以是toutptuot或outuputuo。我的解决方案只选择其中一种可能性。
一些示例运行:
雷达 -> 雷达,0次插入 esystem -> metsystem,2 次插入 message -> megassagem,3 次插入 stackexchange -> stegnahckexekchangets,8 次插入首先我需要检查输入是否已经是回文:
public static bool IsPalindrome(string str)
for (int left = 0, right = str.Length - 1; left < right; left++, right--)
if (str[left] != str[right])
return false;
return true;
然后我需要在输入中找到任何重复的字符。可能不止一个。 message 一词有两个重复次数最多的字符('e' 和 's'):
private static bool TryFindMostRepeatedChar(string str, out List<char> chs)
chs = new List<char>();
int maxCount = 1;
var dict = new Dictionary<char, int>();
foreach (var item in str)
int temp;
if (dict.TryGetValue(item, out temp))
dict[item] = temp + 1;
maxCount = temp + 1;
else
dict.Add(item, 1);
foreach (var item in dict)
if (item.Value == maxCount)
chs.Add(item.Key);
return maxCount > 1;
我的算法在这里:
public static string MakePalindrome(string str)
List<char> repeatedList;
if (string.IsNullOrWhiteSpace(str) || IsPalindrome(str))
return str;
//If an input has repeated characters,
// use them to reduce the number of insertions
else if (TryFindMostRepeatedChar(str, out repeatedList))
string shortestResult = null;
foreach (var ch in repeatedList) //"program" -> 'r'
//find boundaries
int iLeft = str.IndexOf(ch); // "program" -> 1
int iRight = str.LastIndexOf(ch); // "program" -> 4
//make a palindrome of the inside chars
string inside = str.Substring(iLeft + 1, iRight - iLeft - 1); // "program" -> "og"
string insidePal = MakePalindrome(inside); // "og" -> "ogo"
string right = str.Substring(iRight + 1); // "program" -> "am"
string rightRev = Reverse(right); // "program" -> "ma"
string left = str.Substring(0, iLeft); // "program" -> "p"
string leftRev = Reverse(left); // "p" -> "p"
//Shave off extra chars in rightRev and leftRev
// When input = "message", this loop converts "meegassageem" to "megassagem",
// ("ee" to "e"), as long as the extra 'e' is an inserted char
while (left.Length > 0 && rightRev.Length > 0 &&
left[left.Length - 1] == rightRev[0])
rightRev = rightRev.Substring(1);
leftRev = leftRev.Substring(1);
//piece together the result
string result = left + rightRev + ch + insidePal + ch + right + leftRev;
//find the shortest result for inputs that have multiple repeated characters
if (shortestResult == null || result.Length < shortestResult.Length)
shortestResult = result;
return shortestResult;
else
//For inputs that have no repeated characters,
// just mirror the characters using the last character as the pivot.
for (int i = str.Length - 2; i >= 0; i--)
str += str[i];
return str;
请注意,您需要一个 Reverse 函数:
public static string Reverse(string str)
string result = "";
for (int i = str.Length - 1; i >= 0; i--)
result += str[i];
return result;
【讨论】:
以上是关于如何计算将字符串转换为回文所需的字符数?的主要内容,如果未能解决你的问题,请参考以下文章