LeetCode 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 仅包含小写字母

题解思路

方法一:哈希表

时空复杂度: O(m + (n−m) × (Σ + m)) 这里的两个字符串长度决定了循环的次数; 空间复杂度为O(26)即26个英文字母的大小

我们可以利用哈希表存储p字符串的所有字母出现的个数,然后循环遍历s字符串每个字母,然后截取区间内的字符出现的次数,和哈希表存储的个数进行比较,当相等的时候就可以得到起始索引,当然,这种方法应该算是比较暴力的方法了。

上代码:

public List<Integer> hashMethod(String s,String p) {
    if(s.length() < p.length()) {
        return new ArrayList<Integer>();
    }
    //得到p的计数数组
    int[] countp = new int[26];
    //遍历p得到计数
    for(int i = 0; i < p.length(); i ++) {
        countp[p.charAt(i) - 'a'] ++;
    }
    //创建结果List
    List<Integer> list = new ArrayList();
    //遍历s字符串
    for(int i = 0; i <= s.length() - p.length(); i ++) {
        //获取计数数组
        int[] counts = new int[26];
        for(int j = i; j < i + p.length(); j ++) {
            counts[s.charAt(j) - 'a'] ++;
        }
        //当counts和countp相等的时候就加入list中
        if(Arrays.equals(counts,countp)) {
            list.add(i);
        }
    }
    return list;
}

方法二:滑动窗口

时空复杂度: O(m+(n−m)×Σ) 这里的时间复杂度取决于s长度的大小,空间复杂度为 O(Σ) 只需要维护一个26字母所以这里 Σ=26

我们利用两个字符串的每个字符个个数作为滑动窗口的判断条件,定义两个指针,一个指向左边,一个指向右边,两个指针中间维护的窗口中的字符个数应该和p字符串的中字符种类和个数相同,当右指针往右移动的时候判断是否满足条件,当满足条件就记录起始点。

public List<Integer> slideMethod(String s,String p) {
    //首先判断s和t的大小
    if(s.length() < p.length()) {
        return new ArrayList<Integer>();
    }
    //创建结果集合
    List<Integer> res = new ArrayList();
    //创建p字符串的计数数组
    int[] countp = new int[26];
    for(int i = 0; i < p.length(); i ++) {
        countp[p.charAt(i) - 'a'] ++;
    }
    //创建左右指针,维护滑动窗口
    int left = 0;
    int[] counts = new int[26];
    for(int right = 0; right < s.length(); right ++) {
       	//将right指针指向的位置的字符加入count中
        counts[s.charAt(right) - 'a'] ++;
        //当区间长度大于p的长度就让left指针右移
        if(right - left + 1 > p.length()) {
            counts[s.charAt(left) - 'a'] --;
            left ++;
        }
        //判断当前区间是否符合条件
        if(Arrays.equals(countp,counts)) {
            res.add(left);
        }
    }
    return res;
}

方法三:滑动窗口(优化版)

时空复杂度: O(n+m+Σ) O(Σ)

根据上一个普通的滑动窗口方法,我们可以做出改进,取消使用两个count计数数组,而是改为使用diff变量记录两个数组中每个数字出现次数的差异。当diff = 0的时候代表滑动窗口内的元素是异位数。下面我们用代码来实现这个思想

public List<Integer> slideOpt(String s,String p) {
    //先判断两个字符串的大小
    if(s.length() < p.length()) return new ArrayList();
    
    //创建一个count数组用于记录每个元素出现的次数
    int[] count = new int[26];
    
    //创建diff变量
    int diff = 0;
    for(int i = 0; i < p.length(); i ++) {
        count[s.charAt(i) - 'a'] ++;
        count[p.charAt(i) - 'a'] --;
    }
    
    //判断出现元素的差异个数
    for(int i = 0; i < 26; i ++) {
        if(count[i] != 0) {
            diff ++;
        }
    }
    
    //创建结果集合
	List<Integer> res = new ArrayList();    
    
    if(diff == 0) {
		res.add(0);
    }
    
    //这里是滑动窗口移动的距离,从0一直移动到 s.len - p.len - 1
    for(int i = 0; i < s.length() - p.length(); i ++) {
        //向右滑动判断diff的改变
        if(count[s.charAt(i) - 'a'] == 1) {	//等于1代表没移动之前窗口中比p中多一个元素,移动后可以抹去一个差异
            diff --;
        } else if(count[s.charAt(i) - 'a'] == 0) { //等于0的话,移动后就多了一个差异
            diff ++;
        }
        
        //减去左边移去的元素
        count[s.charAt(i) - 'a'] --;
        
        //判断右端点diff后的改变
        if(count[s.charAt(i + p.length()) - 'a'] == -1) { //这里为-1代表没移动之前比p中少一个元素,移动后可以抹去差异
            diff --;
        } else if(count[s.charAt(i + p.length()) - 'a'] == 0) {
            diff ++;
        }
        
        //加入右边移动的元素
		count[s.charAt(i + p.length()) - 'a'] ++;
        
        //判断diff是不是为0
        if(diff == 0) {
            res.add(i + 1);	//注意:这里是加入移动后的位置即i+1
        }
    }
    return res;
}

总结

这题使用基础暴力方法,选择对了数据结构也是可以做出来的,就是时间复杂度可能比较高,但还是可以通过 leetcode 的数据测试,想要优化的话,我们就需要使用滑动窗口算法,这种方法是用于在一个字符串中查找子串,或者判断异位词等多种情况下大幅度减小时间复杂度的一种方法。需要我们多积累滑动窗口的应用场景!

  • 15
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

墨白Coding

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值