【问题标题】:Anagram algorithm with minimum complexity具有最小复杂度的 Anagram 算法
【发布时间】:2011-07-25 02:41:33
【问题描述】:

我最近被要求设计一种算法来检查两个字符串是否是彼此的字谜。我的目标是最小化空间和时间复杂度,所以我想出了这个算法:

  1. 创建一个包含 26 个元素的数组,每个元素都初始化为零。
  2. 遍历第一个字符串并为每个字符递增对应于该字符的数组元素。
  3. 遍历第二个字符串,对于每个字符,递减对应于该字符的数组元素。
  4. 扫描阵列。如果所有元素都为 0,则这两个字符串是字谜。

但是,这个算法的时间复杂度是 O(n),我想不出一个复杂度更低的算法。有人知道吗?

【问题讨论】:

  • 我不是这方面的专家,但是对于这样的事情,O(n) 不是已经很有效了吗?我看到的唯一缺陷是您将很难处理“über”和“rübe”,因为您仅限于拉丁字符(但如果这是一个先决条件,那没关系)。

标签: c algorithm complexity-theory puzzle anagram


【解决方案1】:

您的算法是渐近最优的。不可能在 Ω(n) 时间内更好地解决这个问题。为了看到这一点,假设存在可以在 o(n) 时间内解决问题的算法 A(请注意,这里是 n 的 little-o)。那么对于任何 1 > ε > 0,有一些 n 使得对于任何大小至少为 n 的输入,算法必须在最多 εn 步中终止。设置 ε = 1/3 并考虑算法的任何输入,对于前面提到的这个 ε,长度至少为 n。由于该算法最多只能查看两个字符串中 1/3 的字符,因此该函数必须有两个不同的输入,一个是一对字谜,一个不是,这样算法就可以查看每个输入的字符的相同子集。然后,该函数必须在每种情况下产生相同的输出,因此至少在其中一个输入上是错误的。我们遇到了矛盾,所以这种算法一定不存在。

【讨论】:

    【解决方案2】:

    您可以通过提前退出来提高平均性能。在扫描第二个字符串时,如果在递减之前 count[char] 为 0,则您没有字谜,您可以停止扫描。

    此外,如果字符串短于 26 个字符,则在最后一步中,仅检查第一个字符串中的字符是否为零。

    这不会改变大 O,但它可以将您的平均运行时间更改为小于建议解决方案的 2N+26 o,具体取决于您的数据。

    【讨论】:

      【解决方案3】:

      为了确保字符串是字谜,您需要比较整个字符串 - 那怎么可能比 o(n) 更快?

      【讨论】:

      • 不,对两个字符串使用一个数组似乎已经需要最小的空间了。
      • 很有趣 - 我搜索了一下,发现 - stackoverflow。找到的最佳解决方案与您提出的相同。
      • @garima 您的空间复杂度为 O(1),并且在渐近意义上,您不会做得比这更好。从绝对空间量的角度来看,您可以通过使用 O(n log n) 排序对每个字符串进行排序然后比较结果来减少空间(以增加渐近运行时复杂度为代价)。 (请注意,您当前的方法本质上是对字符串进行计数。)
      • @jamesdlin:空间复杂度实际上是 O(log n),因为计数数组的类型必须足够大以处理 n 重复。
      【解决方案4】:

      我们来问一个问题: 给定两个字符串 s 和 t,编写一个函数来确定 t 是否是 s 的字谜。

      例如,s = "anagram",t = "nagaram",返回 true。 s = "老鼠", t = "汽车", 返回 false。

      方法一(使用HashMap):

          public class Method1 {
      
          public static void main(String[] args) {
              String a = "protijayi";
              String b = "jayiproti";
              System.out.println(isAnagram(a, b ));// output => true
      
          }
      
          private static boolean isAnagram(String a, String b) {
              Map<Character ,Integer> map = new HashMap<>();
              for( char c : a.toCharArray()) {
                  map.put(c,    map.getOrDefault(c, 0 ) + 1 );
              }
              for(char c : b.toCharArray()) {
                  int count = map.getOrDefault(c, 0);
                  if(count  == 0 ) {return false ; }
                  else {map.put(c, count - 1 ) ; }
              }
      
              return true;
          }
      
      }
      

      方法二:

          public class Method2 {
      public static void main(String[] args) {
          String a = "protijayi";
          String b = "jayiproti";
      
      
          System.out.println(isAnagram(a, b));// output=> true
      }
      
      private static boolean isAnagram(String a, String b) {
      
      
          int[] alphabet = new int[26];
          for(int i = 0 ; i < a.length() ;i++) {
               alphabet[a.charAt(i) - 'a']++ ;
          }
          for (int i = 0; i < b.length(); i++) {
               alphabet[b.charAt(i) - 'a']-- ;
          }
      
          for(  int w :  alphabet ) {
               if(w != 0 ) {return false;}
          }
          return true;
      
      }
      }
      

      方法三:

          public class Method3 {
      public static void main(String[] args) {
          String a = "protijayi";
          String b = "jayiproti";
      
      
          System.out.println(isAnagram(a, b ));// output => true
      }
      
      private static boolean isAnagram(String a, String b) {
          char[] ca = a.toCharArray() ;
          char[] cb = b.toCharArray();
          Arrays.sort(   ca     );
      
          Arrays.sort(   cb        );
          return Arrays.equals(ca , cb );
      }
      }
      

      方法四:

          public class Method4 {
      public static void main(String[] args) {
          String a = "protijayi";
          String b = "jayiproti";
          //String c = "gini";
      
          System.out.println(isAnagram(a, b ));// output => true
      }
      
      private static boolean isAnagram(String a, String b) {
          Map<Integer, Integer> map = new HashMap<>();
          a.codePoints().forEach(code -> map.put(code, map.getOrDefault(code, 0) + 1));
          b.codePoints().forEach(code -> map.put(code, map.getOrDefault(code, 0) - 1));
          //System.out.println(map.values());
      
          for(int count : map.values()) {
             if (count<0) return false;
      
          }
      
          return true;
      }
      }
      

      【讨论】:

      • 如果你能增加每一个的复杂性会很棒。
      【解决方案5】:
      int anagram (char a[], char b[]) {
      
        char chars[26];
        int ana = 0;
        int i =0;
      
        for (i=0; i<26;i++)
              chars[i] = 0;
      
      
        if (strlen(a) != strlen(b))
              return -1;
      
        i = 0;
        while ((a[i] != '\0') || (b[i] != '\0')) {
              chars[a[i] - 'a']++;
              chars[b[i] - 'a']--;
              i++;
        }
      
        for (i=0; i<26;i++)
              ana += chars[i];
      
         return ana;
      
      }
      
      
      void main() {
      
        char *a = "chimmy\0";
        char *b = "yimmch\0";
      
        printf ("Anagram result is %d.\n", anagram(a,b));
      
      
      }
      

      【讨论】:

      • 如果任何字符串包含a..z 之外的字符或小写字母在执行字符集中不连续(ASCII 可以,EBCDIC 错误),则行为未定义。
      • 测试while ((a[i] != '\0') || (b[i] != '\0')) 既多余又不正确。您已经检查过ab 具有相同的长度,如果a[i]'\0',则索引chars[a[i] - 'a'] 是不正确的,即使b[i] 不是。
      • 最后的循环并没有验证所有字母的计数是否为零……实际上数组元素的总和总是'0'。
      • char 对于 chars 数组来说是一个太小的类型:带有 256 a 的字符串似乎是带有 256 b 的字符串的字谜。
      猜你喜欢
      • 2022-01-22
      • 1970-01-01
      • 2019-05-30
      • 2013-04-12
      • 2018-06-14
      • 2022-01-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多