【问题标题】:How many palindromes can be formed by selections of characters from a string?从一个字符串中选择字符可以形成多少个回文?
【发布时间】:2023-04-03 19:10:01
【问题描述】:

我代表朋友发布此内容,因为我认为这很有趣:

取字符串“abb”。通过省略 任何数量的字母小于 我们以 7 结尾的字符串的长度 字符串。

a b b ab ab bb abb

这 4 个是回文。

字符串也一样

“hihellolookhavealookatthispalindromexxqwertyuiopasdfghjklzxcvbnmmnbvcxzlkjhgfdsapoiuytrewqxxsoundsfamiliardoesit”

(长度为 112 的字符串)2^112 - 1 可以组成字符串。

其中有多少是 回文??

下面是他的实现(在 C++ 中,C 也很好)。很长的话很慢;他想知道最快的算法是什么(我也很好奇:D)。

#include <iostream>
#include <cstring>

using namespace std;



void find_palindrome(const char* str, const char* max, long& count)
{
    for(const char* begin = str; begin < max; begin++) {
        count++;
        const char* end = strchr(begin + 1, *begin);
        while(end != NULL) {
            count++;
            find_palindrome(begin + 1, end, count);
            end = strchr(end + 1, *begin);
        }
    }
}


int main(int argc, char *argv[])
{
    const char* s = "hihellolookhavealookatthis";
    long count = 0;

    find_palindrome(s, strlen(s) + s, count);

    cout << count << endl;
}

【问题讨论】:

  • 每月举办一次编码竞赛的瑞典老杂志(想想 amiga vs c64)曾经有过这个任务。
  • 为什么是这个 C、C++ 问题??

标签: c++ algorithm performance


【解决方案1】:

首先,您朋友的解决方案似乎有一个错误,因为strchr 可以搜索到max。即使你解决了这个问题,解决方案也是指数级的。

要获得更快的解决方案,您可以使用dynamic programming 在 O(n^3) 时间内解决此问题。这将需要 O(n^2) 额外的内存。请注意,对于长字符串,即使是我在这里使用的 64 位整数也不足以容纳解决方案。

#define MAX_SIZE 1000
long long numFound[MAX_SIZE][MAX_SIZE]; //intermediate results, indexed by [startPosition][endPosition]

long long countPalindromes(const char *str) {
    int len = strlen(str);
    for (int startPos=0; startPos<=len; startPos++)
        for (int endPos=0; endPos<=len; endPos++)
            numFound[startPos][endPos] = 0;

    for (int spanSize=1; spanSize<=len; spanSize++) {
        for (int startPos=0; startPos<=len-spanSize; startPos++) {
            int endPos = startPos + spanSize;
            long long count = numFound[startPos+1][endPos];   //if str[startPos] is not in the palindrome, this will be the count
            char ch = str[startPos];

            //if str[startPos] is in the palindrome, choose a matching character for the palindrome end
            for (int searchPos=startPos; searchPos<endPos; searchPos++) {
                if (str[searchPos] == ch)
                    count += 1 + numFound[startPos+1][searchPos];
            }

            numFound[startPos][endPos] = count;
        }
    }
    return numFound[0][len];
}

解释:

数组numFound[startPos][endPos] 将保存子字符串中包含的回文数,索引为 startPos 到 endPos。

我们遍历所有索引对(startPos、endPos),从短跨度开始,到更长的跨度。对于每一对这样的对,有两个选项:

  1. str[startPos] 处的字符不在回文中。在这种情况下,有numFound[startPos+1][endPos] 可能的回文数 - 我们已经计算过这个数字。

  2. str[startPos] 处的字符在回文中(在其开头)。我们扫描字符串以找到匹配的字符放在回文的末尾。对于每个这样的字符,我们使用 numFound 中已经计算的结果来查找内部回文的可能性数。

编辑

  • 澄清:当我说“字符串中包含的回文数”时,这包括不连续的子字符串。例如,回文“aba”包含在“abca”中。

  • 利用 numFound[startPos][x] 的计算只需要知道所有 y 的 numFound[startPos+1][y] 的事实,可以将内存使用量减少到 O(n)。我不会在这里这样做,因为它会使代码有点复杂。

  • 预先生成包含每个字母的索引列表可以使内部循环更快,但总体上仍然是 O(n^3)。

【讨论】:

  • @Pavel Shved:你能澄清一下你的意思吗?一旦我提到的错误得到修复,我的答案与原始代码的结果相同。
  • 恐怕你在子索引的使用上有点错误。就我个人而言,我会说“省略任意数量的字母”意味着从“abc”可以得出“a b c ab ac bc abc”。实际的例子有点不稳定(使用“abb”),但你会注意到在派生列表中“ab”出现了两次,而如果字符串是连续的,你只会派生“a b b ab bb abb”。
  • @Matthieu M.:我的回答不仅仅着眼于连续的子字符串——它完全符合问题的要求。例如,使用字符串“aaa”将给出 7 而不是 6 的结果,因为它只计算连续子字符串。如果您不这么认为,请提供一个示例字符串,其中我的答案给出了错误的结果。
  • 抱歉,我被连续跨度的使用误导了。
【解决方案2】:

我有一种方法可以在 O(N^2) 时间和 O(1) 空间内完成,但我认为必须有其他更好的方法。

基本思想是长回文必然包含小回文,所以我们只搜索最小匹配,这意味着两种情况:“aa”,“aba”。如果我们找到任何一个,则展开以查看它是否是长回文的一部分。

    int count_palindromic_slices(const string &S) {
        int count = 0;

        for (int position=0; position<S.length(); position++) {
            int offset = 0;

            // Check the "aa" situation
            while((position-offset>=0) && (position+offset+1)<S.length() && (S.at(position-offset))==(S.at(position+offset+1))) {
                count ++;
                offset ++;
            }

            offset = 1;  // reset it for the odd length checking
            // Check the string for "aba" situation
            while((position-offset>=0) && position+offset<S.length() && (S.at(position-offset))==(S.at(position+offset))) {
                count ++;
                offset ++;
            }
        }
        return count;
    }

2012 年 6 月 14 日 经过一番调查,我相信这是最好的方法。 比接受的答案更快。

【讨论】:

  • 这个答案是错误的,所以它的相对速度是无关紧要的。 count_palindromic_spaces("abb") 当问题说它应该返回 4 时返回 1。(长度为 n 的字符串的答案总是至少为 n。)它为“aaa”返回 3 " 什么时候应该返回 7。代码有两个问题。首先,它要求回文串的最小长度应该是 1,而它应该是 1。其次,它只在应该检查所有 2^n-1 个字符选择时检查连续的字符串。
  • 嗨@RobKennedy,感谢您指出我的代码错误。对于第一个,我理解并且同意你的观点,在这篇文章中,每个角色都应该算数。它可以通过 count++ 在每个 for 部分中轻松修复。对于第二个,我不确定我是否得到它。你能解释更多吗?例如为什么“aaa”应该是7?我认为即使在原始帖子关注中也应该是 6(a,a,a,aa,aa,aaa)?
  • 三个字符的字符串中有七种不同的字符选择(因为 7 = 2^3-1)。当所有字母都相同时,很难说明,所以让我们看一下“abc”。以下是七个字符串:abc、ab、ac、bc、a、b、c。您的算法忽略的一个是 ac。当所有字母都相同时,这七个字符串显然都是回文,所以count_palindromic_slices("aaa")的结果应该是7。
【解决方案3】:

进行初始遍历并建立每个字符的所有出现的索引是否有任何里程数。

 h = { 0, 2, 27}
 i = { 1, 30 }
 etc.

现在从左边开始,h,只有可能的回文位于 3 和 17,char[0 + 1] == char [3 -1] 等是否得到回文。 char [0+1] == char [27 -1] 否,不需要进一步分析 char[0]。

继续 char[1],只需要 example char[30 -1] 和向内。

然后可能会变得聪明,当您确定从位置 x->y 运行的回文时,所有内部子集都是已知的回文,因此我们已经处理了一些项目,可以从以后的检查中排除这些情况。

【讨论】:

    【解决方案4】:

    我的解决方案使用O(n) 内存和O(n^2) 时间,其中n 是字符串长度:

    palindrome.c:

    #include <stdio.h>
    #include <string.h>
    
    typedef unsigned long long ull;
    
    ull countPalindromesHelper (const char* str, const size_t len, const size_t begin, const size_t end, const ull count) {
      if (begin <= 0 || end >= len) {
        return count;
      }
      const char pred = str [begin - 1];
      const char succ = str [end];
      if (pred == succ) {
        const ull newCount = count == 0 ? 1 : count * 2;
        return countPalindromesHelper (str, len, begin - 1, end + 1, newCount);
      }
      return count;
    }
    
    ull countPalindromes (const char* str) {
      ull count = 0;
      size_t len = strlen (str);
      size_t i;
      for (i = 0; i < len; ++i) {
        count += countPalindromesHelper (str, len, i, i, 0);  // even length palindromes
        count += countPalindromesHelper (str, len, i, i + 1, 1); // odd length palindromes
      }
      return count;
    }
    
    int main (int argc, char* argv[]) {
     if (argc < 2) {
      return 0;
     }
     const char* str = argv [1];
     ull count = countPalindromes (str);
     printf ("%llu\n", count);
     return 0;
    }
    

    用法:

    $ gcc palindrome.c -o palindrome
    $ ./palindrome myteststring
    

    编辑:我将问题误读为问题的连续子字符串版本。现在,鉴于人们想要找到非连续版本的回文数,我强烈怀疑,考虑到不同字符的数量及其各自的字符数,我强烈怀疑可以使用数学方程来解决它。

    【讨论】:

    • 正如你所说,这只找到连续的子字符串。我怀疑您是否可以找到一个数学方程式从这个到正确的解决方案,如您所说:例如,字符串“abcdabcd”和“abcdabdc”在您的解决方案中都给出 8 并且都具有相同的字符数。但是,两者的正确解决方案不同(分别为 24 和 27)。
    • 哦,我明白了。我将非连续的含义误认为在重新排序方面是完全免费的。 IOW,任何子排列都是游戏。
    【解决方案5】:

    嗯嗯,我想我会这样算:

    每个字符都是它自己的回文(减去重复字符)。
    每对相同的字符。
    每对相同的字符,所有回文夹在中间,可以由重复之间的字符串组成。
    递归应用。

    这似乎是你正在做的,虽然我不确定你不会重复计算重复字符的边缘情况。

    所以,基本上,我想不出更好的方法。

    编辑:
    想多了, 它可以通过缓存来改进,因为有时您会多次计算同一子字符串中的回文数。所以,我想这表明肯定有更好的方法。

    【讨论】:

    • 但是 strchr() 很昂贵。从较小的回文数向外工作,有必要只比较第一个和最后一个字符,在第一个不匹配处停止。
    • 我不知道:O(n) 并不是世界上最昂贵的操作。我承认可能有更好的解决方案,但我不确定它来自自上而下或自下而上的方法。
    【解决方案6】:

    这是一个用 Java 和 C++ 编写的字符串中的program for finding all the possible palindromes

    【讨论】:

    • 这些确实是寻找回文的程序,但它们不是寻找所有回文的程序this问题所问的。
    【解决方案7】:
    int main()
     {
        string palindrome;
    
        cout << "Enter a String to check if it is a Palindrome";
    
        cin >> palindrome;
    
        int length = palindrome.length();
    
        cout << "the length of the string is " << length << endl;
    
        int end = length - 1;
        int start = 0;
        int check=1;
    
        while (end >= start) {
            if (palindrome[start] != palindrome[end]) {
                cout << "The string is not a palindrome";
                check=0;
                break;
            }
            else
            {
                start++;
                end--;
    
            }
    
        }
        if(check)
        cout << "The string is a Palindrome" << endl;
    
    }
    

    【讨论】:

    • 1. 您不必遍历整个单词(使用while (end &gt;= start)),而只需遍历其中的一半,因为您已经比较了后半部分。 .. 2. 您的帖子根本没有回答问题...
    【解决方案8】:
    public String[] findPalindromes(String source) {
    
        Set<String> palindromes = new HashSet<String>();        
        int count = 0;
        for(int i=0; i<source.length()-1; i++) {            
            for(int j= i+1; j<source.length(); j++) {               
                String palindromeCandidate = new String(source.substring(i, j+1));
                if(isPalindrome(palindromeCandidate)) {
                    palindromes.add(palindromeCandidate);                   
                }
            }
        }
    
        return palindromes.toArray(new String[palindromes.size()]);     
    }
    
    private boolean isPalindrome(String source) {
    
        int i =0;
        int k = source.length()-1;      
        for(i=0; i<source.length()/2; i++) {            
            if(source.charAt(i) != source.charAt(k)) {
                return false;
            }
            k--;
        }       
        return true;
    }
    

    【讨论】:

      【解决方案9】:

      我不确定,但你可以试试 whit Fourier。这个问题让我想起了这个:O(nlogn) Algorithm - Find three evenly spaced ones within binary string

      只要我的 2cents

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-11-13
        • 1970-01-01
        • 2016-07-12
        • 1970-01-01
        • 1970-01-01
        • 2018-05-25
        相关资源
        最近更新 更多