LeetCode 438.找到字符串中所有字母异位词

145 阅读1分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

题目:给定两个字符串sp,找到sp的异位词子串,返回子串的起始索引。

解题思路

本题思路很好想,首先看两个字符串的长度,如果s长度小于p,那么直接可以返回空列表。如果s长度大于p,只需要将s从首位开始遍历,每次遍历判断从遍历起点开始到加上p长度的这一串字符串是否是字母异位词即可,如何判断字母异位词是本题的关键。

思路一

如何判断两个字符串是异位词,首先想到的是两个由相同字母组成的字符串有什么特征呢?

  • 字符串转数组后排序必定相同

根据这个特征,我们可以首先对p字符串进行排序,之后每次截取s中和p等长的字符串,对该字符串也进行排序,然后判断两个字符数组是否相等即可,可得代码如下:

public List<Integer> findAnagrams3(String s, String p) {
    int sLen = s.length();
    int pLen = p.length();
    if (sLen < pLen) return new ArrayList<>();
    ArrayList<Integer> res = new ArrayList<>();
    char[] pChar = p.toCharArray();
    Arrays.sort(pChar);
    for(int i=0;i<=sLen-pLen;i++){
        char[] cChar = s.substring(i, i+pLen).toCharArray();
        Arrays.sort(cChar);
        if(Arrays.equals(cChar, pChar)){
            res.add(i);
        }
    }
    return res;
}

时间复杂度为O(n2logn)O(n^2logn),有点高,最终耗时1800ms+

思路二

上面思路的方法慢主要是因为统计两个待比较的数组时比较耗时,那么我们可否换一种思路来获得两个待比较的数组。

因为字符串中字符的范围是确定的(a-z),因此我们可以初始化两个长度为26的数组,数组中统计每个元素出现的次数,之后根据比较这两个数组的亦同即可得到最终答案。

此处有一个要点在于我们要首先统计0索引处是否是字母异位词,之后统计1时就可以再次从0开始,每次更新起始索引和起始索引+pLen处的元素,可得代码如下:

public List<Integer> findAnagrams(String s, String p){
    int sLen = s.length();
    int pLen = p.length();
    if(sLen<pLen) return new ArrayList<>();
    ArrayList<Integer> res = new ArrayList<>();
    int[] pCount = new int[26];
    int[] sCount = new int[26];

    for(int i=0;i<pLen;i++){
        pCount[p.charAt(i)-'a']++;
        sCount[s.charAt(i)-'a']++;
    }

    if(Arrays.equals(pCount,sCount)) res.add(0);

    for(int i=0;i<sLen-pLen;i++){
        sCount[s.charAt(i)-'a']--;
        sCount[s.charAt(i+pLen)-'a']++;

        if(Arrays.equals(sCount, pCount)){
            res.add(i+1);
        }
    }
    return res;
}

时间复杂度明显下降,此时为O(n)O(n)

优化

实际上上述代码还可以优化,我们可以只使用一个数组来统计sp的字符,每次维护这一个数组即可。不过这个方法比较巧妙,需要自己模拟一遍,代码如下:

public List<Integer> findAnagrams2(String s, String p) {
    int sLen = s.length();
    int pLen = p.length();
    if (sLen < pLen) return new ArrayList<>();
    ArrayList<Integer> res = new ArrayList<>();
    int[] count = new int[26];
    int differ = 0;
    for(int i=0;i<pLen;i++){
        count[s.charAt(i)-'a']++;
        count[p.charAt(i)-'a']--;
    }

    for(int i=0;i<count.length;i++){
        if(count[i]!=0) differ++;
    }

    if(differ==0) res.add(0);

    for(int i=0;i<sLen-pLen;i++){
        if(count[s.charAt(i)-'a']==1){
            differ--;
        }else if(count[s.charAt(i)-'a']==0){
            differ++;
        }
        count[s.charAt(i)-'a']--;

        if(count[s.charAt(i+pLen)-'a']==0){
            differ++;
        }else if(count[s.charAt(i+pLen)-'a']==-1){
            differ--;
        }
        count[s.charAt(i+pLen)-'a']++;
        if(differ==0) res.add(i+1);
    }

    return res;
}