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

  1. 找到字符串中所有字母异位词
    给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引。

字符串只包含小写英文字母,并且字符串 s 和 p 的长度都不超过 20100。

说明:

字母异位词指字母相同,但排列不同的字符串。
不考虑答案输出的顺序。
示例 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” 的字母异位词。

解题思路
滑动窗口
思路:这个题目如果有滑动窗口的想法,很容易想到。一个长度等于p的窗口在s上滑动。难点在于怎么判断窗口字符串和p是否是异味子串。并且移动时怎么处理元素。想到用map或者set可以记录字符是否存在,但是这里可能会有重复字符。
灵感来自java对象的回收算法,对于一个对象被很多地方引用。每个引用计数+1,当消除一个引用时,垃圾回收期不会立即回收该对象。而是将对象的计数器的值-1,当值等于0时才回收。这里也计数,当窗口右移时,第一次添加的元素值为1,否则值+1.
窗口左边废弃元素,同理,大于1时-1,等于1时直接移除。

做法:用两个指针代表窗口的左右两端,间隔是p的长度。用map保存元素和出现的次数。向右滑动窗口,每滑动一次将新元素添加到map中,同时左边过去的元素从map中移除。
判断当前子串和p是否是异位字符串。当滑动到最右边时结束。

注意:不能用比较两个Integer对象的大小。因为他们不是int这种基础数据类型。他们是对象,这是判断对象是否是同一个。即使值一样,还是显示不相同。因为这是两个对象。在Integer中有个IntegerCache[],里面存储了-128~127之间的值,只有里面的值可以用比较大小。

时间复杂度:O(m*n),m是字符串s的长度,n是字符串n的长度。因为相当于向右遍历一遍s,每移动一个元素,都要循环判断窗口子串即长度p的。空间复杂度:O(n)

代码

class Solution {
    public static List<Integer> findAnagrams(String s, String p) {
        List<Integer> list = new ArrayList<>();
        //对于这种特殊情况,不要返回null,要返回空要求返回值。否则oj会判错。
        if (s.length() < p.length()) return list;

        //用两个map记录各个字符出现次数
        HashMap<Character, Integer> map1 = new HashMap<>();
        HashMap<Character, Integer> map2 = new HashMap<>();
        //初始化,将p和s字符串前p长度个字符分别添加到map中
        for (int i = 0; i < p.length(); i++) {
            //这里为了代码简洁抽取成一个方法。其实没必要,这样做会让oj中程序运行时间大大增加,这里不抽取运行时间56ms,抽取后运行时间2604ms。
            // 即不要随意抽取方法。即使在真正工作中用的很多
            myPut(map1, s.charAt(i));
            myPut(map2, p.charAt(i));
        }

        //定义窗口左右两端
        int start = 0;
        int end = p.length()-1;
        while (true) {
            //如果两个map相匹配,把子串开始索引添加到list中
            if (compare(map1, map2)) list.add(start);
            //窗口右移
            end++;
            //while循环的结束条件放在这里。这样写不太优雅。但我不想动脑筋了
            if (end >= s.length()) break;
            //将右移的新元素添加到map中
            myPut(map1, s.charAt(end));

            //将左边移过去的元素去除
            Integer i1 = map1.get(s.charAt(start));
            //元素计数大于1就-1,否则就直接移除
            if (i1 > 1) map1.put(s.charAt(start++), i1-1);
            else map1.remove(s.charAt(start++));
        }
        return list;
    }

    //将新元素添加到map
    private static void myPut(HashMap<Character, Integer> map, char charAt) {
        Integer put = map.put(charAt, 1);
        //如果为空说明添加成功,否则存在该元素,在原基础上数量+1
        if (put != null) map.put(charAt, put+1);
    }

    //判断两个map中的元素是否相同,且数量一致
    private static boolean compare(HashMap<Character, Integer> map1, HashMap<Character, Integer> map2) {
        //遍历map1中的key
        Set<Character> keySet1 = map1.keySet();
        for (Character key : keySet1) {
            //如果这个key在map2中找不到值,说明不存在,直接返回false
            if (map2.get(key) == null) return false;
            //这里一定要注意,不能用==比较两个Integer对象的大小。因为他们不是int这种基础数据类型。他们是对象,这是判断对象是否是同一个。
            // 即使值一样,还是显示不相同。因为这是两个对象。在Integer中有个IntegerCache[],里面存储了-128~127之间的值,只有里面的值可以用==比较大小。
            if (map1.get(key).compareTo(map2.get(key)) != 0) return false;
        }
        return true;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值