Hot100-滑动窗口



438. 找到字符串中所有字母异位词 - 力扣(LeetCode)

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

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

示例 1:

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

第一次写的代码:

class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        //滑动窗口
        /*
        while(left<right)
        left:从charAt.s(0)开始,看在不在p中,不在则++
        right:从left+(p.length-1)开始,看在不在p中,不在则left++,
              在则right--,继续判断charAt.s(right--)在不在p中
        */
        int left = 0;
        int n = p.length();
        int right = left + n -1;
        if (p == null){
            return null;
        }
        List<Integer> result = new LinkedList();
        while (left < right){
            if (!p.contains(s.charAt(left))){
                left++;
            }
            else if(!p.contains(s.charAt(right))) {
                left++;
            }
            else{
                // result.add(left);
                // right--;
                for (int i=right; i>left; i--){
                if (p.contains(s.charAt(i))){
                    result.add(left);
                }
                }

            }
        }
        return result;
    }
}

存在以下问题:

  1. 字符检查方法错误: p.contains(s.charAt(left))p.contains(s.charAt(right)) 不能正确检查字符是否在字符串 p 中,因为 String.contains(CharSequence) 方法用于检查子字符串,而不是单个字符。

  2. 滑动窗口逻辑不正确: 你的代码尝试使用两个指针 leftright,但没有正确实现滑动窗口的概念。滑动窗口的正确实现需要移动窗口内的字符并更新频率计数。

  3. 没有考虑窗口大小: 滑动窗口的大小应该始终等于字符串 p 的长度,但代码没有保证这一点。

  4. 循环退出条件错误: while (left < right) 退出条件不正确,应当使用 while (left <= s.length() - p.length()) 来保证窗口不会超出字符串 s 的边界。

正确思路:

因为字符串 p 的异位词的长度一定与字符串 p的长度相同,所以我们可以在字符串 s 中构造一个长度为与字符串 p的长度相同的滑动窗口,并在滑动中维护窗口中每种字母的数量;

当窗口中每种字母的数量与字符串 p 中每种字母的数量相同时,则说明当前窗口为字符串 p 的异位词。

注意:

怎么记录每种字母的数量?

for (int i = 0; i < p.length(); i++) {
    pCount[p.charAt(i) - 'a']++;
}
  1. 遍历的过程:

    • i = 0 时,p.charAt(0)'a'

      pCount['a' - 'a']++; // pCount[0]++,=1

    • i = 1 时,p.charAt(1)'b'

      pCount['b' - 'a']++; // pCount[1]++,=1

    • i = 2 时,p.charAt(2)'c'

      pCount['c' - 'a']++; // pCount[2]++,=1

最后,pCount 数组变为:

pCount[0] = 1; // 'a' 的频率

pCount[1] = 1; // 'b' 的频率

pCount[2] = 1; // 'c' 的频率

正确代码:

class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        //滑动窗口
        /*
在字符串 s 中构造一个长度为与字符串 p的长度相同的滑动窗口,并在滑动中维护窗口中每种字母的数量;
当窗口中每种字母的数量与字符串 p 中每种字母的数量相同时,则说明当前窗口为字符串 p 的异位词。
        */
        int plen = p.length();
        int slen = s.length();
        if (slen < plen){
            return new ArrayList<Integer>();
        }
        List<Integer> result = new LinkedList();
        int[] pCount = new int[26];
        int[] WindowCount = new int[26];

        //记录p中每种字母的数量
        for (int i=0; i<plen; i++){
        pCount[p.charAt(i)-'a']++;
        }


        //初始化第一个窗口中每种字母的数量
        /*
第一个窗口是字符串 s 的前 p.length() 个字符。
我们需要统计这个窗口内每个字符的频率,以便与字符串 p 的字符频率进行比较。
        */
        for (int i=0; i<plen; i++){
        WindowCount[s.charAt(i)-'a']++;
        }

        if (Arrays.equals(pCount,WindowCount)){
            result.add(0);
        }

        for (int left=0; left<slen-plen; left++){

        // 更新窗口,添加右边的新字符,移除左边的旧字符
        WindowCount[s.charAt(left+plen) - 'a']++;
        WindowCount[s.charAt(left) - 'a']--;
        // 如果当前窗口是异位词,添加起始索引
        if (Arrays.equals(pCount,WindowCount)){
            result.add(left+1);
        }
    }
        return result;}
}

注意:

   1.若S的长度小于P的长度,不能返回null,应该直接返回一个空数组,因为这个方法要求返回值是List<Integer>

        if (slen < plen){
            return new ArrayList<Integer>();
        }

2.必须从第二个窗口开始,left<slen-plen(不能取等),要不然left+plen会超出索引

        //除了第一个窗口,从第二个窗口(left+1)开始遍历
        /*
          必须从第二个窗口开始,left<slen-plen(不能取等),要不然left+plen会超出索引
        */
        for (int left=0; left<slen-plen; left++){
                    // 更新窗口,添加右边的新字符,移除左边的旧字符
        WindowCount[s.charAt(left+plen) - 'a']++;
        WindowCount[s.charAt(left) - 'a']--;
        // 如果当前窗口是异位词,添加起始索引
        if (Arrays.equals(pCount,WindowCount)){
            result.add(left+1);
        }

3.判断pCount和WindowCount相等,必须用Arrays.equals(pCount,WindowCount),不能直接用pCount==WindowCount(equals,不是equal!!!)

== 用于比较两个对象的引用是否相同。对于数组来说,== 比较的是两个数组对象的内存地址,而不是它们的内容。因此,pCount == WindowCount 判断的是两个数组是否是同一个数组对象,而不是它们的内容是否相同。

567. 字符串的排列 - 力扣(LeetCode)

给你两个字符串 s1 和 s2 ,写一个函数来判断 s2 是否包含 s1 的排列。如果是,返回 true ;否则,返回 false 。

换句话说,s1 的排列之一是 s2 的 子串 。

示例 1:

输入:s1 = "ab" s2 = "eidbaooo"
输出:true
解释:s2 包含 s1 的排列之一 ("ba").

示例 2:

输入:s1= "ab" s2 = "eidboaoo"
输出:false
class Solution {
    public boolean checkInclusion(String s1, String s2) {
        /*
        滑动窗口:在s2中滑动,窗口大小为s1.length()
        统计s1中字母出现的次数
        判断与窗口中字母出现的次数是否相等
        */
        int windowLength = s1.length();
        if (s2.length() < windowLength){
            return false;
        }
        //统计s1中字母出现的次数
        int[] s1Count = new int[26];
        for(int i = 0; i < windowLength; i++){
            s1Count[s1.charAt(i) - 'a']++;
        }

        //初始化第一个窗口内容,这样for循环中才好匹配
        int[] s2Count = new int[26];
        for(int i = 0; i < windowLength; i++){
            s2Count[s2.charAt(i) - 'a']++;
        }       

        //在s2中滑动窗口,匹配窗口中的内容与s1是否相等
        for(int left = 0; left < s2.length() - windowLength; left++) {
            if (Arrays.equals(s1Count,s2Count)){
                return true;
            }
            //更新窗口内容(继续滑动)
            //把左边的数删除(对应的字母出现的次数减一)
            s2Count[s2.charAt(left) - 'a']--;
            //add右边的数(对应的字母出现的次数加一)
            s2Count[s2.charAt(left+windowLength)-'a']++;
        }
        //最后一个窗口在for循环中没扫描上,因此在循环外再匹配一次
        for(int left = s2.length() - windowLength; left<=s2.length() - windowLength; left++){
            if (Arrays.equals(s1Count,s2Count)){
                return true;
            }
        }
        return false;

    }
}

判断最后一个窗口的时候可以优化。此时已经循环完了,窗口就在s2的最后了,不需要这个for循环

        //最后一个窗口在for循环中没扫描上,因此在循环外再匹配一次
            if (Arrays.equals(s1Count,s2Count)){
                return true;
            }

优化后的正确代码:

class Solution {
    public boolean checkInclusion(String s1, String s2) {
        /*
        滑动窗口:在s2中滑动,窗口大小为s1.length()
        统计s1中字母出现的次数
        判断与窗口中字母出现的次数是否相等
        */
        int windowLength = s1.length();
        if (s2.length() < windowLength){
            return false;
        }
        //统计s1中字母出现的次数
        int[] s1Count = new int[26];
        for(int i = 0; i < windowLength; i++){
            s1Count[s1.charAt(i) - 'a']++;
        }

        //初始化第一个窗口内容,这样for循环中才好匹配
        int[] s2Count = new int[26];
        for(int i = 0; i < windowLength; i++){
            s2Count[s2.charAt(i) - 'a']++;
        }       

        //在s2中滑动窗口,匹配窗口中的内容与s1是否相等
        for(int left = 0; left < s2.length() - windowLength; left++) {
            if (Arrays.equals(s1Count,s2Count)){
                return true;
            }
            //更新窗口内容(继续滑动)
            //把左边的数删除(对应的字母出现的次数减一)
            s2Count[s2.charAt(left) - 'a']--;
            //add右边的数(对应的字母出现的次数加一)
            s2Count[s2.charAt(left+windowLength)-'a']++;
        }
        //最后一个窗口在for循环中没扫描上,因此在循环外再匹配一次
            if (Arrays.equals(s1Count,s2Count)){
                return true;
            }
        return false;

    }
}
   

注意:

Arrays.equals

一定要有s!!!

  • 16
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值