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

72 篇文章 0 订阅
438. 找到字符串中所有字母异位词

难度 中等

给定两个字符串 sp,找到 s 中所有 p异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。

异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。

示例 1:

输入: s = "cbaebabacd", p = "abc"
输出: [0,6]
解释:
起始索引等于 0 的子串是 "cba", 它是 "abc" 的异位词。
起始索引等于 6 的子串是 "bac", 它是 "abc" 的异位词。

示例 2:

输入: s = "abab", p = "ab"
输出: [0,1,2]
解释:
起始索引等于 0 的子串是 "ab", 它是 "ab" 的异位词。
起始索引等于 1 的子串是 "ba", 它是 "ab" 的异位词。
起始索引等于 2 的子串是 "ab", 它是 "ab" 的异位词。

提示:

  • 1 <= s.length, p.length <= 3 * 104
  • sp 仅包含小写字母

题解

​ 这道题就是寻找字符串P在S中的排列,刚开始想的是遍历,失败就回溯,简单来说就是暴力,但是时间复杂度堪忧。就是通过统计字母数组,比较两个统计字母数组相同。

class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        List<Integer> ans = new ArrayList<Integer>();//结果数组
        int[] count = new int[26];//记录P中所有字母出现的次数
        int plen = p.length();//P的长度
        for(int i = 0; i < plen; i++){//统计P字母出现的次数
            count[p.charAt(i) - 'a']++;
        }
        int slen = s.length();//s的长度
        for(int i = 0; i < slen; i++){//遍历s
            if(count[s.charAt(i) - 'a'] > 0){//当前字母存在P字符串中
                int[] temp = (int[])Arrays.copyOf(count, count.length);//复制p的统计数组
                int flag = 1;//失败标记
                int j = 0;//遍历长度
                for(; j < plen && j + i < slen; j++){
                    if(--temp[s.charAt(i + j) - 'a'] < 0){//如果s的字符串统计超过了p的字符串统计,枚举失败
                        if(count[s.charAt(i + j) - 'a'] == 0){//如果s的字符在p中不存在,不用回溯,直接跳到下个字符
                            i += j - 1;
                        }
                        flag = 0;
                        break;
                    }
                }
                if(j == plen && flag == 1){//枚举成功
                    ans.add(i);
                }
            }       
        }
        return ans;
    }
}

​ 想不出优化的办法,看了一下官方题解,不用回溯的方法确实秒。通过differ来看字符串之间差多少个字母。那这个differ是怎么其效果的,我也想了很久。想不通描述,就从代码入手。

​ 这里的辅助数组需要理解一下,count是一个字符记录数组。但是不是记录了字母出现多少次,而是记录了s和p在某个字符之间差多少(我定义为字母差)。aba和abc,这两个字符串相差1个字符,differ就相差1。详细的解析需要结合代码,全部写在代码上了,可以结合代码给的理解理解一下。

class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        int slen = s.length();
        int plen = p.length();
        if(slen < plen){
            return new ArrayList<Integer>();
        }

        List<Integer> ans = new ArrayList<Integer>();
        int[] count = new int[26];//辅助数组
        for(int i = 0; i < plen; i++){
            ++count[s.charAt(i) - 'a'];//加上s字符串的字母出现的次数
            --count[p.charAt(i) - 'a'];//减去p字符串的字母出现的次数
        }

        int differ = 0;//初始化为0
        for(int j = 0; j < 26; j++){//遍历26个字母
            if(count[j] != 0){//如果某个字母差别不为0
                ++differ;//字母差加一
            }
        }

        if(differ == 0){//如果没有字母差
            ans.add(0);
        }

        for(int i = 0; i < slen - plen; i++){
            //遍历s字符串,这里比较抽象,要理解在做什么
            //要理解这里在做什么,先理解(s.charAt(i) - 'a')和(s.charAt(i + plen) - 'a')
            //s.charAt(i) - 'a'是以i为开始的字母(就是要删除的首字母)
            //s.charAt(i + plen) - 'a'是以i开始,长度plen结束的字母(就是要新添加进来的尾字母)
            //上面我们已经做了从0开始,plen长度的字符串的匹配
            //然后我们移动时去除第一个字母,也就是这里的s.charAt(i);在尾部加上一个字母,也就是这里的s.charAt(i + plen),这么做为了保持长度
            
            
            //此时我们要移动字符串进行匹配,删除第一个字母,就是count[s.charAt(i) - 'a']--;
            //如果删除的这个字母不一样,字母差减一
            //	例如这种情况,s:cabd,p:abd,第一个c字母在字符串p中不存在,然后如果count[c] == 1(这个c字母差只有一个),我们又删除了这个字母c,那我们的字母差就减少了,所以differ--
            //如果删除的这个字母是一样的,字母差加一
            //	例如这种情况,s:abdc,p:abd,第一个a字母在字符串p中存在,然后count[a] == 0(这个a字母差没有),我们又删去了这个字母a,那我们的字母差就增加了,所以differ++
            if(count[s.charAt(i) - 'a'] == 1){
                differ--;
            } else if(count[s.charAt(i) - 'a'] == 0){
                differ++;
            }
            count[s.charAt(i) - 'a']--;

            
            //上面做了删除首字母操作,为了保证长度,我们需要在末尾加上一个字母,count[s.charAt(i + plen) - 'a']++;
            //如果添加的这个字母一样的,字母差减一
            //	例如这种情况,s:abdc,p:cbd,添加的C字母在字符串p中存在,然后count[c] == -1(这个C字母差只有一个),我们又添加了这个字母c,那我们的字母差就减少了,所以differ++
            //如果添加的这个字母是不一样的,字母差加一
            //	例如这种情况,s:abdc,p:abd,添加的c字母在字符串p中不存在,然后count[c]=0,我们又添加了这个字母c,那我们的字母差就增加了,所以differ++
            if(count[s.charAt(i + plen) - 'a'] == -1){
                differ--;
            }else if(count[s.charAt(i + plen) - 'a'] == 0){
                differ++;
            }
            count[s.charAt(i + plen) - 'a']++;

            //如果删去一个,添加一个,字母差为0 ,则是我们想要的答案。
            if(differ == 0){
                ans.add(i + 1);
            }
        }
        return ans;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值