【问题标题】:How to detect the first occurrence of palindrome如何检测回文的首次出现
【发布时间】:2012-04-21 14:11:21
【问题描述】:

假设您正在从字符流中读取,当您读取第一次出现的回文时,该函数应该返回。

回文的长度应该是偶数

时间复杂度要求为O(N)。

例子:

  • 第一个字符:4
  • 第二个字符:1
  • 第三个字符:3
  • 第四个字符:3
  • 第 5 个字符:1
  • 第6个字符:4,返回

【问题讨论】:

  • 第一次出现是什么意思?
  • 必须超过一个字符吗? “读取第一个字符,返回”。它必须使用整个字符串吗?即“4,1,3,3”返回原因“3,3”是回文。
  • 当输入流为 413314 时,您可以阅读我的示例。它应该在读取第 6 个字符时停止,因为 413314 是回文,而 4、41、413、4133、41331 不是。跨度>
  • @jb。是的,它必须是整个字符串
  • 你保证回文的第一个字符是输入流的第一个字母吗?或者回文可以从任何位置开始?

标签: algorithm palindrome


【解决方案1】:

在大多数情况下应该在线性时间 O(n) 内运行的一种近似解决方案是使用滚动哈希。

您跟踪字符串的哈希值及其反向。每次读取一个新字符时,都可以更新 O(1) 中的两个哈希值。然后比较两个哈希值,如果它们相等,则比较字符串及其保留值。如果这也相等,则退出并得到回文。

例如,一个非常简单的滚动哈希函数是 hash(ck c(k-1).. c0) = (p^k*ck + p^(k - 1) * c(k - 1) + .. . + c0) mod m 其中 p 是奇素数。

所以开始:

p = 17 // or your lucky prime number
m = 10^9 // ...
hash = 0
hash_rev = 0
str = ''
str_rev = ''
p_pow = 1    

while True
    read c
    append c to str
    prepend c to str_rev
    hash = (hash * p + c) mod m
    hash_rev = (hash_rev + p_pow * c) mod m
    p_pow = p_pow * p
    if hash == hash_rev
         if str == str_rev
             found str to be the first palindrome

为了使其更快,您可以将哈希和 hash_rev 清除为 32 位整数并选择 m = 2^32。然后就可以忽略(mod m)操作了。

【讨论】:

  • 如果第一个回文不是以流的开头怎么办?想象一下,第一个回文从 N 个字符开始进入流 - 您是否不需要一个散列数组,每个散列从不同的点开始,并且您必须扫描您的散列以查找回文字符串的每个可能的起点?跨度>
  • 这个问题的作者说回文需要是整个字符串,不是吗?不过我可能错了。
【解决方案2】:

当你读到第一个字符时返回,这是一个单字母回文。

【讨论】:

  • 只需将我的问题更新为回文的长度应该是偶数。
  • 空字符串也是回文,0是偶数,用这种方法你甚至不需要读第一个字符。
  • @KanWu,如果字符串是 2231322 - 它会在 22 之后返回。这样可以吗?
  • @BillCheatham 应该在 cmets 中提问。是的,它应该在 22 岁之后返回。
  • @kilotaras,我试图澄清 Kan Wu 在这里给出的评论,并了解在新约束下是否仍然可以接受这个答案中给出的微不足道的解决方案。
【解决方案3】:

您需要对Manacher's Algorithm 稍作修改。它允许您在线性时间内找到字符串中的所有回文。关于算法的事情,它实际上从左到右进行,在需要时“使用”新字符。所需的修改是您需要读取新字符,仅当您尝试访问它时。
停止,如果你找到回文,那会一直追溯到流的开头。

【讨论】:

  • 我在 page 上阅读了 manacher 的算法,但我不确定 manacher 的算法如何适应这个问题,因为它必须在计算“P[i] 时更新“正确”指针",并且在阅读之前需要知道字符是什么。
【解决方案4】:

这个(未经测试的 C#)代码可以做到,但我认为它可能是 O(n^2)。任何人都可以确认/否认吗?

main()
{
    string str = "";
    char c1;
    char c2;

    while(true)
    {
        //has to be even, always get two chars at a time
        c1 = GetCharFromStream();
        c2 = GetCharFromStream();
        str += c1 + c2;
        if(isPalindrome(str))
        {
            Console.WriteLine(str);
            return;
        }
    }
}

bool isPalindrome(string str)
{
    if (str.Length % 2 != 0)
        throw new InvalidArgumentException("str");

    int first = 0;
    int last = str.Length - 1;

    while(first <= last)
    {
        if(str[first] != str[last])
            return false;

        first++;
        last--;
    }

    return true;
}

【讨论】:

  • 当然是 O(n^2)。对于读取的每个字符,您都会遍历字符串以确定它是否是回文。
  • @WeaselFox,是的,我就是这么想的。我的大妈还是很虚弱。
【解决方案5】:

使用Hashset怎么样?

假设输入流是1221

Hashset = empty;

if(hashset does not contain the inputelement)
{
   add to hashset. // 12 will be in the hashset. //413 for your example.
}

else
{
  delete from Hashset. // First deletes 2 and then 1. // First deletes 3, then 1 then 4.

  if(hashset is empty)
    return true; //Hashset is empty. return true.
}

【讨论】:

    【解决方案6】:

    这是我的看法。它从字符串的当前中间扫描每个新符号上的累积字符串,因此如果当前字符串不是回文,它将快速失败。

    class PalindromeDetector {
        private static class DetectorImpl {
            final ArrayList<Character> string = new ArrayList<Character>(32);
    
            boolean addSymbol(char symbol) {
                string.add(symbol);
                return check();
            }
    
            private boolean check() {
                if (string.size() < 2)
                    return false;
    
                int lowBound = string.size() / 2 - 1;
                int hiBound = lowBound + 1;
                while (lowBound >= 0 && string.get(lowBound) == string.get(hiBound)) {
                    lowBound --; hiBound ++;
                }
                return lowBound == -1;
            }
        }
    
        static boolean isPalindrome(String s) {
            DetectorImpl detector = new DetectorImpl();
            int index = 0;
            while (index < s.length())
                if (detector.addSymbol(s.charAt(index++)))
                    return true;
            return false;
        }
    }
    

    这是代码的单元测试:

    @Test
    public void test() {
        assertFalse(PalindromeDetector.isPalindrome("4"));
        assertTrue(PalindromeDetector.isPalindrome("44"));
    
        assertFalse(PalindromeDetector.isPalindrome("4343"));
        assertTrue(PalindromeDetector.isPalindrome("4334"));
    
        assertFalse(PalindromeDetector.isPalindrome("41331"));
        assertTrue(PalindromeDetector.isPalindrome("413314"));
    }
    

    【讨论】:

      【解决方案7】:

      好的,因为我的第一个答案具有 O(n^2) 时间复杂度,这是另一个,应要求使用 O(n)

      class PalindromeDetector {
          private static class DetectorImpl {
              int firstHalfSum, secondHalfSum;
              int size = 0, tensPower = 1;
      
              boolean add(int v) {
                  if (size % 2 == 1) {
                      firstHalfSum = firstHalfSum + (secondHalfSum / tensPower) * tensPower;
                      secondHalfSum = (secondHalfSum % tensPower) * 10 + v;
                      if (firstHalfSum == secondHalfSum)
                          return true;
                  } else {
                      secondHalfSum = secondHalfSum * 10 + v;
                  }
      
                  size ++;
                  if (size % 2 == 0)
                      tensPower *= 10;
      
                  return false;
              }
          }
      
          static boolean isPalindrome(String s) {
              if (s == null || s.length() == 0)
                  return false;
      
              int val = Integer.parseInt(s);
              int tensPower = s.length() == 1 ? 1 : (int) (Math.pow(10, s.length() - 1));
              DetectorImpl detector = new DetectorImpl();
              while (tensPower > 0) {
                  int digit = val / tensPower;
                  if (detector.add(digit))
                      return true;
      
                  val = val % tensPower;
                  tensPower /= 10;
              }
      
              return false;
          }
      }
      

      这是单元测试:

      @Test
      public void test() {
          assertFalse(PalindromeDetector.isPalindrome("4113"));
          assertTrue(PalindromeDetector.isPalindrome("3443"));
          assertTrue(PalindromeDetector.isPalindrome("473374"));
          assertTrue(PalindromeDetector.isPalindrome("413314"));
      }
      

      Answer 假设输入由十进制数字组成,但可以轻松扩展为字母数字输入(假设英文字母 + 10 位数字为我们提供基于 36 的数字系统)。

      【讨论】:

        【解决方案8】:

        这个解决方案(在 Haskell 中)依赖于对偶数长度回文在其中心包含重复字符的观察。当我们从输入流中读取字符时,我们会测试这样的对,当找到一个时,我们会播种一个新的候选答案。对于每个候选回文,我们还保留一个“剩余要匹配的字符”列表,并且在读取每个新字符时,我们将其与该列表进行匹配——如果没有匹配,候选者将被丢弃。当匹配列表为空时,已找到解决方案。请注意,虽然每个候选人都有自己的匹配列表,但这些都只是同一列表的后缀,因此在 Haskell 中,这些都将共享空间,并且即使有很多候选人,它们也不会占用超过 O(n) 的空间。

        在答案中心只有一个配对字符且因此没有“误报”候选者的最佳情况下,时间复杂度为 O(n) - 每个字符检查不超过两次。对于具有许多错误开始的输入字符串,即“abbbbbbbbbbbbba”,我不确定时间复杂度 - 它可能不再是 O(n) 但它比 O(n^2) 更好,因为没有候选人能活得更久比min(k, n-k) 其中k 是候选中心的位置。

        filter_map::(a -> Maybe a)->[a]->[a]
        filter_map op = foldr (maybeCons . op) []
          where maybeCons Nothing  ys = ys
                maybeCons (Just y) ys = y:ys
        
        -- Return the first prefix of the input argument that
        -- is an even-length palindrome.
        prefix_palindrome::(Eq a)=>[a]->[a]
        prefix_palindrome (x:xs) = search [x] [] xs
          where search seen ([]:_) _ = seen
                search (s:seen) candidates (x:xs) | x == s = search seen' (candidates' ++ [seen]) xs
                                                  | otherwise = search seen' candidates' xs
                  where seen' = (x:s:seen)
                        candidates' = filter_map extend candidates
                                      where extend (c:cs) | c == x = Just cs
                                                          | otherwise = Nothing
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-02-04
          • 1970-01-01
          • 1970-01-01
          • 2012-04-15
          相关资源
          最近更新 更多