【问题标题】:Optimal Algorithm to find Anagrams of a string in another string在另一个字符串中找到一个字符串的 Anagrams 的最佳算法
【发布时间】:2020-01-18 10:55:03
【问题描述】:

我在 Cracking The Coding Interview 一书中遇到了这个话题。挑战是在较大的字符串 b 中找到给定的较小字符串 s 的排列。我可以提出以下算法,其时间复杂度为 O(B x S),其中 S 和 B 分别是给定较小和较大字符串的长度:

import java.util.HashMap;
public class AnagramAlgorithm {
public static void main(String[] args) {
    String s = "cbabadcbbabbcbabaabccbabc";
    String b = "abbc";

    printAnagramsOfB(s, b);
}

public static void printAnagramsOfB(String text, String pattern) {
    if(isEmpty(text) || isEmpty(pattern)) {
        System.out.println("Invalid Strings");
        return;
    }
    int patternLength = pattern.length();
    for (int i = 0; i < text.length() - patternLength + 1; i++) {
        String substring = text.substring(i, i + patternLength);
        if (isAnagram(pattern, substring)) {
            System.out.println("Anagram Found : " + substring);
        }
    }
}

public static boolean isEmpty(CharSequence str) {
    return str == null || str.length() == 0;
}

public static boolean isAnagram(String pattern, String substring) {
    if (pattern.length() != substring.length()) {
        System.out.println("SubString length doesn't match the length of Given String");
        return false;
    }
    char[] subStringArr = substring.toCharArray();
    char[] patternArr = pattern.toCharArray();
    HashMap<Character, Integer> mapPattern = new HashMap<>();
    HashMap<Character, Integer> mapSubstring = new HashMap<>();
    for (int i = 0; i < subStringArr.length; i++) {
        if (mapSubstring.containsKey(subStringArr[i])) {
            int count = mapSubstring.get(subStringArr[i]);
            mapSubstring.put(subStringArr[i], count + 1);
        } else {
            mapSubstring.put(subStringArr[i], 1);
        }
        if (mapPattern.containsKey(patternArr[i])) {
            int count = mapPattern.get(patternArr[i]);
            mapPattern.put(patternArr[i], count + 1);
        } else {
            mapPattern.put(patternArr[i], 1);
        }
    }
    return mapPattern.equals(mapSubstring);
}
}

书中提到最优算法是 O(B)。我想不出这样的算法。根据我的想法,对于整体复杂度为 O(B),查找子字符串是否为字谜的算法应该是 O(1),即没有任何循环。这甚至可能吗?或者有没有其他方法可以实现最优算法?

【问题讨论】:

    标签: java algorithm data-structures


    【解决方案1】:

    此算法以线性时间运行。如果您正在准备面试,那么您可能可以自己找出这里发生的事情;)

    public class Solver {
    
        List<Integer> solve(String t, String s) {
    
            HashMap<Character, Integer> charCountInT = new HashMap<>();
            for (int i = 0; i < t.length(); i++) {
                Character c = t.charAt(i);
                if (charCountInT.containsKey(c)) {
                    charCountInT.put(c, charCountInT.get(c) + 1);
                }
                else {
                    charCountInT.put(c, 1);
                }
            }
    
            HashMap<Character, Integer> extraCharacters = new HashMap<>();
            for (Character c : charCountInT.keySet()) {
                extraCharacters.put(c, -charCountInT.get(c));
            }
            for (int i = 0; i < t.length(); i++) {
                Character c = s.charAt(i);
                if (extraCharacters.containsKey(c)) {
                    extraCharacters.put(c, extraCharacters.get(c) + 1);
                }
            }
    
            int expectedZeroesInExtraCharacters = charCountInT.size();
            int zeroesInExtraCharacters = 0;
            for (Integer count : extraCharacters.values()) {
                if (count == 0) ++zeroesInExtraCharacters;
            }
    
            List<Integer> answer = new ArrayList<>();
            if (zeroesInExtraCharacters == expectedZeroesInExtraCharacters) answer.add(0);
    
            for (int i = 1; i < s.length() - t.length(); i++) {
    
                Character nextChar = s.charAt(t.length() + i - 1);
                if (charCountInT.containsKey(nextChar)) {
                    extraCharacters.put(nextChar, extraCharacters.get(nextChar) + 1);
                    if (extraCharacters.get(nextChar) == 0) ++zeroesInExtraCharacters;
                    if (extraCharacters.get(nextChar) == 1) --zeroesInExtraCharacters;
                }
    
                Character removedChar = s.charAt(i - 1);
                if (charCountInT.containsKey(removedChar)) {
                    extraCharacters.put(removedChar, extraCharacters.get(removedChar) - 1);
                    if (extraCharacters.get(removedChar) == 0) ++zeroesInExtraCharacters;
                    if (extraCharacters.get(removedChar) == -1) --zeroesInExtraCharacters;
                }
    
                if (zeroesInExtraCharacters == expectedZeroesInExtraCharacters) answer.add(i);
            }
    
            return answer;
    
        }
    
        public static void main(String[] args) {
            String t = "abbc";
            String s = "cbabadcbbabbcbabaabccbabc";
            List<Integer> startIndices = new Solver().solve(t, s);
            System.out.println(startIndices);
            for (int startIndex : startIndices) {
                System.out.println(s.substring(startIndex, startIndex + t.length()));
            }
        }
    
    }
    
    

    【讨论】:

    • 为什么我们必须将较大的字符串分成两部分循环? 0 到 t.length() 然后 t.length() 到 s.length() - t.length()..
    • @NeerajaGandla 第一个为字符串t构建频率图,第二个为长度为t.length()s的每个子字符串构建频率图,与预先计算的频率图进行比较的t。使它成为 O(B) 而不是 O(BS) 的技巧是频率图可以在 O(1) 时间内从一个子字符串更新到下一个子字符串。
    • 算法线性所需的另一件事是比较两个频率图需要 O(1) 时间,假设字符串是由固定字母表组成的。
    • @kaya3 假设 HashMap get 和 put 操作在 O(1) 中运行,我的算法即使对于不固定的字母表也以线性时间运行,因为它不比较频率图。
    • @ardenit 哦,好点,我没有仔细阅读。不错!
    【解决方案2】:

    标准字谜查找方法使用排序版本进行比较。您不寻找“TARGET”,而是先将其按字母顺序排列:“AEGRTT”并搜索。

    从较大字符串的开头获取相关的字符数,“AEGRTT”为六个。排序并比较以查看是否匹配。然后删除第一个字母并将长列表中的下一个字母添加到适当的位置 - 列表已经排序,这将有所帮助。再次比较并重复。

    【讨论】:

    • 使用频率图比使用排序数组更好;更新长度为 S 的排序数组仍然需要 O(S) 时间,比较两个排序数组也需要 O(S) 时间,因此该算法最终将成为 O(BS) 整体。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-22
    • 1970-01-01
    相关资源
    最近更新 更多