繁体   English   中英

查找转换为回文式的最低成本

[英]Find minimum cost to convert to palindrome

我在这个问题上困扰了很长时间,无法找到任何有效的解决方案。 任何帮助,将不胜感激。

问题:

给定一个带有小写字符的字符串,我们需要找到将其转换为回文的最低成本。 我们可以插入新字符并删除现有字符。 每个字符都有与之相关的插入和删除成本。

成本为'a'= 1,'b'= 2,'c'= 3,.....,'z'= 26

例如,“ abc”->“ c”,费用为3

我只能想到一种方法,涉及遍历所有具有指数时间复杂性的后遗症。 有什么办法可以优化它?

您可以想象一个递归解决方案,其中解决使第一个和最后一个字符相同的问题,然后解决其余字符(不包括第一个和最后一个字符)的问题。

如果字符串的第一个和最后一个字符已经相同,则没有必要考虑在字符串的开头或结尾插入一个字符,或者删除第一个或最后一个字符。 那只会增加成本。

如果不同,则有几种选择来获得与最后一个字符相同的第一个字符:

  1. 在开始处插入一个字符:与当前字符串的最后一个字符相同的字符。 此字符的成本应添加到递归为原始字符串(不含最后一个字符)所付出的成本中。

  2. 在末尾插入一个字符:与当前字符串的第一个字符相同的字符。 此字符的成本应添加到递归为原始字符串而没有第一个字符的成本中。

  3. 在最后删除字符:这可能不会立即使第一个和最后一个字符相等,因为可能需要删除/插入更多的字符。 但是,这种选择将是递归调用的工作。 此字符的成本应添加到递归将为其余字符串提供的成本上。

  4. 首先删除字符(与选项3相同的理由):应将此字符的成本添加到递归将为其余字符串提供的成本中。

请注意,在选项1和3中进行的递归调用是相同的,并且添加的成本也完全相同。 比较选项2和4时,会有类似的观察结果。例如,当输入为“ abcb”时,我们可以看到在末尾添加“ a”或从开头删除“ a”都在同一位置产生回文成本。 因此,实际上我们只需要考虑这4个选项中的2个。

在对这两个选项进行递归调用之后,剩下的唯一事情就是选择两个中最便宜的一个。

仅剩下1个字符(或一个字符)时,递归停止:对于这种情况,由于该字符串是回文,所以成本为0。 (零)成本,甚至相应的回文率也可以返回给调用者。

通过记忆可以进行一些优化:跟踪每个访问范围的结果。

这是JavaScript的实现,它具有交互式输入,您可以在其中实时查看相应的计算成本和回文率:

 function toPalindrome(s, charCost) { let visited = []; function recur(i, j) { let key = i * s.length + j; if (visited[key]) return visited[key]; // use memoization let cost = 0, palindrome; if (i >= j) { // Base case palindrome = i > j ? "" : s[i]; } else if (s[i] === s[j]) { // If outermost two characters are equal: take them; no extra cost ({ cost, palindrome } = recur(i+1, j-1)); palindrome = s[i] + palindrome + s[i]; } else { // Otherwise consider deleting either first or last char ({ cost, palindrome } = recur(i, j-1)); cost += charCost[s[j]]; let { cost: cost2, palindrome: palindrome2 } = recur(i+1, j); cost2 += charCost[s[i]]; if (cost2 < cost) { // Take best of the two searched branches cost = cost2; palindrome = palindrome2; } } // Return two informations: cost and palindrome. return visited[key] = { cost, palindrome }; } return recur(0, s.length-1); } const charCost = [...Array(26).keys()].reduce((acc, i) => (acc[String.fromCharCode(i+97)] = i+1, acc), {}); // I/O handling (document.oninput = () => output.textContent = JSON.stringify(toPalindrome(input.value, charCost), null, 2) )(); 
 Input: <input id="input" value="antenna"><br> <pre id="output"></pre> 

返回所有回文

由于在一端删除字符的成本与在另一端添加字符的成本相同,因此可以以相同的最低成本创建多个回文集。

这是收集所有这些回文而不是一个的所有代码的版本。 显然,这会占用一些额外的执行时间和空间:

 function toPalindrome(s, charCost) { let visited = []; function recur(i, j) { let key = i * s.length + j; if (visited[key]) return visited[key]; // use memoization let cost = 0, palindromes; if (i >= j) { // Base case palindromes = [i > j ? "" : s[i]]; } else if (s[i] === s[j]) { // If outermost two characters are equal: take them; no extra cost ({ cost, palindromes } = recur(i+1, j-1)); palindromes = palindromes.map(pal => s[i] + pal + s[i]); } else { // Otherwise consider deleting either first or last char ({ cost, palindromes } = recur(i, j-1)); // add an alternative for every palindrome: using an insertion instead of deletion // at the opposite end of the string palindromes = [...palindromes, ...palindromes.map(pal => s[j] + pal + s[j])]; cost += charCost[s[j]]; let { cost: cost2, palindromes: palindromes2 } = recur(i+1, j); cost2 += charCost[s[i]]; if (cost2 <= cost) { // Take best of the two searched branches if (cost2 < cost) { palindromes = []; } palindromes = [...palindromes, ...palindromes2, ...palindromes2.map(pal => s[i] + pal + s[i])]; cost = cost2; } } // Return two informations: cost and palindrome. return visited[key] = { cost, palindromes }; } let result = recur(0, s.length-1); result.palindromes = [...new Set(result.palindromes)]; // make unique return result; } const charCost = [...Array(26).keys()].reduce((acc, i) => (acc[String.fromCharCode(i+97)] = i+1, acc), {}); // I/O handling (document.oninput = () => output.textContent = JSON.stringify(toPalindrome(input.value, charCost), null, 2) )(); 
 Input: <input id="input" value="antenna"><br> <pre id="output"></pre> 

enum Action { Initial, Unchanged, Insert, Delete }

int defaultEditCost(char ch) => char.ToLower(ch) - 'a' + 1;

int editDistancePalindrime(string str, Func<char, int> costFn)
{
    // Calculate the levenshtein distance table between `str` and its reverse.
    // str[i-1] is the normal string, and str[n-j] is the reverse.
    int n = str.Length;
    var table = new (Action action, int totalCost, int actionCost)[n + 1, n + 1];
    for (int i = 0; i <= n; i++)
    {
        for (int j = 0; j <= n; j++)
        {
            if (i == 0 && j == 0) table[i, j] = (Action.Initial, 0, 0);
            else if (i == 0)
            {
                var insertCost = costFn(str[n - j]);
                var insertTotalCost = table[i, j - 1].totalCost + insertCost;
                table[i, j] = (Action.Insert, insertTotalCost, insertCost);
            }
            else if (j == 0)
            {
                var deleteCost = costFn(str[i - 1]);
                var deleteTotalCost = table[i - 1, j].totalCost + deleteCost;
                table[i, j] = (Action.Delete, deleteTotalCost, deleteCost);
            }
            else if (str[i - 1] == str[n - j])
            {
                table[i, j] = (Action.Unchanged, table[i - 1, j - 1].totalCost, 0);
            }
            else
            {
                var insertCost = costFn(str[n - j]);
                var deleteCost = costFn(str[i - 1]);
                var insertTotalCost = table[i, j - 1].totalCost + insertCost;
                var deleteTotalCost = table[i - 1, j].totalCost + deleteCost;
                if (insertTotalCost <= deleteTotalCost)
                {
                    table[i, j] = (Action.Insert, insertTotalCost, insertCost);
                }
                else
                {
                    table[i, j] = (Action.Delete, deleteTotalCost, deleteCost);
                }
            }
        }
    }
    // The cost is the sum of actionCost for all inserts or all deletes.
    // (Both have the same value, because of symmetry.)
    int palindromeCost = 0;
    for (int i = n, j = n; i > 0 || j > 0;)
    {
        var (action, totalCost, actionCost) = table[i, j];
        switch (action)
        {
            case Action.Insert:
                palindromeCost += actionCost;
                j--;
                break;
            case Action.Delete:
                i--;
                break;
            case Action.Unchanged:
                i--;
                j--;
                break;
        }
    }
    return palindromeCost;
}
void Main()
{
    editDistancePalindrime("abc", defaultEditCost).Dump();
    // 'abc' -> 'c' or 'abcba' (cost 3)

    editDistancePalindrime("anchitjain", defaultEditCost).Dump();
    // 'anchitjain' -> 'nitin' or 'anchiajtjaihcna' (cost 23)
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM