anagram检查的最佳解决方案?

时间:2016-07-06 17:02:52

标签: java compare anagram

我正在经历一个排列/字谜问题,并希望输入最有效的检查方法。 现在,我正在Java土地上这样做,因此有一个包括排序在内的一切图书馆。 检查两个字符串是否是彼此的字谜的第一种方法是检查长度,以某种方式对它们进行排序,然后比较所述字符串的每个索引。代码如下:

private boolean validAnagram(String str, String pair) {
if(str.length() != pair.length()){
    return false;
}

char[] strArr = str.toCharArray();
char[] pairArr = pair.toCharArray();


Arrays.sort(strArr);
str = new String(strArr);

Arrays.sort(pairArr);
pair = new String(pairArr);

for(int i = 0; i<str.length(); i++){
    if(str.charAt(i) != pair.charAt(i)){
        return false;
    }
}
return true;
}

或者,我认为根据ascii值检查更容易,并避免检查每个可能的字符。代码如下:

private boolean validAnagram(String str, String pair) {
if(str.length() != pair.length()){
    return false;
}

char[] strArr = str.toCharArray();
char[] pairArr = pair.toCharArray();



int strValue = 0;
int pairValue = 0;

for(int i =0; i < strArr.length; i++){
    strValue+= (int) strArr[i];
    pairValue+= (int) pairArr[i];
}

if(strValue != pairValue){
    return false;
}
return true;
}

那么,这是一个更好的解决方案?我不太了解Arrays给我的那种,但是当我环顾旧的互联网时,这是更常见的答案。让我想知道我是否遗漏了什么。

11 个答案:

答案 0 :(得分:3)

这是一个非常简单的实现。

public boolean isAnagram(String strA, String strB) {
  // Cleaning the strings (remove white spaces and convert to lowercase)
  strA = strA.replaceAll("\\s+","").toLowerCase();
  strB = strB.replaceAll("\\s+","").toLowerCase();

  // Check every char of strA and removes first occurence of it in strB
  for (int i = 0; i < strA.length(); i++ ) {
    if (strB.equals("")) return false;  // strB is already empty : not an anagram
    strB = strB.replaceFirst(Pattern.quote("" + strA.charAt(i)), "");
  }

  // if strB is empty we have an anagram
  return strB.equals("");
}

最后:

System.out.println(isAnagram("William Shakespeare", "I am a weakish speller")); // true

答案 1 :(得分:1)

最佳解决方案取决于您的目标,代码大小,内存占用或最少的计算。

一个非常酷的解决方案,尽可能减少代码,不是Java 8中最快的O(nlog n)和非常低效的内存:

public class Anagram {
  public static void main(String[] argc) {
    String str1 = "gody";
    String str2 = "dogy";

    boolean isAnagram =
    str1.chars().mapToObj(c -> (char) c).sorted().collect(Collectors.toList())
    .equals(str2.chars().mapToObj(c -> (char) c).sorted().collect(Collectors.toList()));

    System.out.println(isAnagram);
  }
}

答案 2 :(得分:1)

我尝试了一些使用Sets的解决方案,并使每个运行1000万次以使用您的示例数组进行测试:

private static String[] input = {"tea", "ate", "eat", "apple", "java", "vaja", "cut", "utc"};

首先,我习惯称之为algotirhms的方法:

public static void main(String[] args) {
    long startTime = System.currentTimeMillis();
    for (int x = 0; x < 10000000; x++) {
        Set<String> confirmedAnagrams = new HashSet<>();
        for (int i = 0; i < (input.length / 2) + 1; i++) {
            if (!confirmedAnagrams.contains(input[i])) {
                for (int j = i + 1; j < input.length; j++) {
                        if (isAnagrams1(input[i], input[j])) {
                            confirmedAnagrams.add(input[i]);
                            confirmedAnagrams.add(input[j]);
                        }
                }
            }
        }
        output = confirmedAnagrams.toArray(new String[confirmedAnagrams.size()]);
    }
    long endTime = System.currentTimeMillis();
    System.out.println("Total time: " + (endTime - startTime));
    System.out.println("Average time: " + ((endTime - startTime) / 10000000D));
}

然后我使用了基于HashSet字符的算法。我将每个单词的每个字符添加到HashSet中,如果HashSet不是首字母的长度,则表示它们不是字谜。

我的算法及其运行时间:

算法1:

    private static boolean isAnagrams1(String x, String y) {
    if (x.length() != y.length()) {
        return false;
    } else if (x.equals(y)) {
        return true;
    }

    Set<Character> anagramSet = new HashSet<>();
    for (int i = 0; i < x.length(); i++) {
        anagramSet.add(x.charAt(i));
        anagramSet.add(y.charAt(i));
    }

    return anagramSet.size() != x.length();
}

这有运行时:

Total time: 6914
Average time: 6.914E-4

算法2

private static boolean isAnagrams2(String x, String y) {
    if (x.length() != y.length()) {
        return false;
    } else if (x.equals(y)) {
        return true;
    }

    Set<Character> anagramSet = new HashSet<>();
    char[] xAr = x.toCharArray();
    char[] yAr = y.toCharArray();
    for (int i = 0; i < xAr.length; i++) {
        anagramSet.add(xAr[i]);
        anagramSet.add(yAr[i]);
    }

    return anagramSet.size() != x.length();
}

运行时具有:

Total time: 8752
Average time: 8.752E-4

算法3

对于这个算法,我决定发送Set through,因此我只为每个周期创建一次,并在每次测试后清除它。

    private static boolean isAnagrams3(Set<Character> anagramSet, String x, String y) {
    if (x.length() != y.length()) {
        return false;
    } else if (x.equals(y)) {
        return true;
    }

    for (int i = 0; i < x.length(); i++) {
        anagramSet.add(x.charAt(i));
        anagramSet.add(y.charAt(i));
    }

    return anagramSet.size() != x.length();
}

运行时具有:

Total time: 8251
Average time: 8.251E-4

算法4

这个算法不是我的,它属于Pratik Upacharya也回答了这个问题,为了让我比较:

    private static boolean isAnagrams4(String stringOne, String stringTwo) {
    char[] first = stringOne.toLowerCase().toCharArray();
    char[] second = stringTwo.toLowerCase().toCharArray();
    // if length of strings is not same 
    if (first.length != second.length) {
        return false;
    }
    int[] counts = new int[26];
    for (int i = 0; i < first.length; i++) {
        counts[first[i] - 97]++;
        counts[second[i] - 97]--;
    }
    for (int i = 0; i < 26; i++) {
        if (counts[i] != 0) {
            return false;
        }
    }
    return true;
}

运行时具有:

Total time: 5707
Average time: 5.707E-4

当然,这些运行时在每次测试运行时都会有所不同,为了进行正确的测试,需要更大的示例集,并且可能需要更多迭代。

*编辑,因为我在初始方法中犯了一个错误,Pratik Upacharya's算法似乎确实是更快的算法

答案 3 :(得分:1)

我的解决方案:时间复杂度= O(n)

public static boolean isAnagram(String str1, String str2) {
    if (str1.length() != str2.length()) {
        return false;
    }

    for (int i = 0; i < str1.length(); i++) {
        char ch = str1.charAt(i);

        if (str2.indexOf(ch) == -1) 
            return false;
        else
            str2 = str2.replaceFirst(String.valueOf(ch), " ");
    }

    return true;
}

测试用例:

@Test
public void testIsPernutationTrue() {
    assertTrue(Anagram.isAnagram("abc", "cba"));
    assertTrue(Anagram.isAnagram("geeksforgeeks", "forgeeksgeeks"));
    assertTrue(Anagram.isAnagram("anagram", "margana"));
}

@Test
public void testIsPernutationFalse() {
    assertFalse(Anagram.isAnagram("abc", "caa"));
    assertFalse(Anagram.isAnagram("anagramm", "marganaa"));
}

答案 4 :(得分:1)

这是我能够编译的更简单易读的解决方案...

    static boolean isAnagram(String a, String b) {
    if (a.length() == b.length()){
        char[] arr1 = a.toLowerCase().toCharArray();
        char[] arr2 = b.toLowerCase().toCharArray();
        Arrays.sort(arr1);
        Arrays.sort(arr2);
        if (Arrays.equals(arr1, arr2)) return true;
        else return false;
    }else return false;
}

最好, 贾斯汀

答案 5 :(得分:0)

有几种方法可以检查两个字符串是否是字谜。 你的问题是,哪一个是更好的解决方案。 您的第一个解决方案有排序逻辑 排序具有最坏的案例复杂度(nlogn)。 你的第二个逻辑只使用一个具有复杂性的循环 上) 。

所以在这两个中,你的第二个解决方案只有O(n) 复杂性将是比第一个更好的解决方案。

一种可能的解决方案:

private boolean checkAnagram(String stringOne , String stringTwo){
        char[] first = stringOne.toLowerCase().toCharArray(); 
        char[] second = stringTwo.toLowerCase().toCharArray();
        // if length of strings is not same 
        if (first.length != second.length)
            return false;
        int[] counts = new int[26]; 
        for (int i = 0; i < first.length; i++){
            counts[first[i]-97]++;  
            counts[second[i]-97]--;   
        }
        for (int i = 0; i<26; i++)
            if (counts[i] != 0)
                return false;
        return true;
    }

private boolean checkAnagram(String stringOne , String stringTwo){ char[] first = stringOne.toLowerCase().toCharArray(); char[] second = stringTwo.toLowerCase().toCharArray(); // if length of strings is not same if (first.length != second.length) return false; int[] counts = new int[26]; for (int i = 0; i < first.length; i++){ counts[first[i]-97]++; counts[second[i]-97]--; } for (int i = 0; i<26; i++) if (counts[i] != 0) return false; return true; }

答案 6 :(得分:0)

//here best solution for an anagram
import java.util.*;

class Anagram{
public static void main(String arg[]){

Scanner sc =new Scanner(System.in);
String str1=sc.nextLine();
String str2=sc.nextLine();
int i,j;

boolean Flag=true;
i=str1.length();
j=str2.length();


if(i==j){
for(int m=0;m<i;m++){
    for(int n=0;n<i;n++){
        if(str1.charAt(m)==str2.charAt(n)){
           Flag=true;
           break;
          }
          else
          Flag=false;
    }
}
}
else{
Flag=false;
}

if(Flag)
System.out.println("String is Anagram");
else
System.out.println("String is not Anagram");
}
}

答案 7 :(得分:0)

招聘人员最近要我解决这个问题。 在研究这个问题时,我想出了一个解决两种类型的解决方案 字谜问题。

问题1: 确定文本正文中是否存在anagram。

问题2: 确定文本正文中是否存在正式的字谜。 在这种情况下,anagram必须与您的文本大小相同 比较它。在前一种情况下,两个文本的大小不一样 一个人只需要包含另一个。

我的方法如下:

设置阶段: 首先创建一个anagram类。这只会将文本转换为Map 其中包含有关字符的键,值包含数字 输入字符的出现次数。 我认为这至多需要O(n)时间复杂度。 因为这最多需要两张地图,最糟糕的情况是复杂性 将是O(2n)。至少我对渐近符号的天真理解 说。

处理阶段: 您所需要做的就是通过两个地图中较小的一个循环 在更大的地图中查找。如果它不存在或存在 但是由于出现次数不同,它不能使测试成为一个字谜。

这是确定我们是否有anagram的循环:

    boolean looking = true;
        for (Anagram ele : smaller.values()) {
            Anagram you = larger.get(ele);
                if (you == null || you.getCount() != ele.getCount()) {
                    looking = false;
                    break;
                }
        }
        return looking;

请注意,我创建了一个ADT来包含正在处理的字符串。他们 首先转换为地图。

以下是创建Anagram对象的代码片段:

    private void init(String teststring2) {
        StringBuilder sb = new StringBuilder(teststring2);
        for (int i = 0; i &lt sb.length(); i++) {
            Anagram a = new AnagramImpl(sb.charAt(i));
            Anagram tmp = map.putIfAbsent(a, a);
            if (tmp != null) {
                tmp.updateCount();
            }
        }
    }

答案 8 :(得分:0)

我想出了一个需要O(n)时间的解决方案,而且我什至不使用任何26个char数组... 检查一下:

StringBuffer a = new StringBuffer();
        a.append(sc.next().toLowerCase());

        StringBuffer b = new StringBuffer();
        b.append(sc.next().toLowerCase());
        if(a.length() !=b.length())
        {
            System.out.println("NO");
            continue;
        }
        int o =0;
        for(int i =0;i<a.length();i++)
        {
            if(a.indexOf(String.valueOf(b.charAt(i)))<0)
            {
               System.out.println("NO");
               o=1;break; 

            }
        }
        if(o==0)
         System.out.println("Yes");

答案 9 :(得分:0)

考虑使用 HashMap Arrays.sort

    private static Map<String, String> getAnagrams(String[] data) {

    Map<String, String> anagrams = new HashMap<>();
    Map<String, String> results = new HashMap<>();

    for (int i = 0; i < data.length; i++) {

        char[] chars = data[i].toLowerCase().toCharArray();
        Arrays.sort(chars);

        String sorted = String.copyValueOf(chars);

        String item = anagrams.get(sorted);
        if (item != null) {
            anagrams.put(sorted, item + ", " + i);
            results.put(sorted, anagrams.get(sorted));
        } else {
            anagrams.put(sorted, String.valueOf(i));
        }
    }

    return results;
}

我喜欢它,因为您只遍历数组一次。

答案 10 :(得分:0)

使用原始数据类型的解决方案。

boolean isAnagram(char input1[], char input2[]) {
    int bitFlip = 32;

    if(input2.length != input1.length){return false;}

    boolean found = false;
    for (int x = 0; x < input1.length; x++) {
        found = false;
        for (int y = 0; y < input2.length; y++) {
             if (!found && ((input1[x] | bitFlip)) ==
             ( (input2[y] | bitFlip))) {
                found = true;
                input2[y] = 0;
            }
        }
        if (!found) {
            break;
        }
    }
    return found ;
}

这种方法不依赖任何排序工具。它的作用是通过迭代查找值,找到后将其设置为零,以避免输入带有重复字符(如“ pool”和“ loop”)的重复字符,该字符具有2个字母“ o”。

它也忽略了不翻转位而不依赖toLowerCase()的情况,因为如果第6位(十进制为32)为1,则它为小写字母和大写字母,如果为零。

它是直接字节操作,因此它的性能会更好,就像图像操作中使用的一样。不利的一面可能是O(n ^ 2)。

这是在hackerrank中测试过的解决方案