Leetcode——字母异位词分组 / 找到字符串中所有字母异位词(子串) / 字符串的排列(子串)

1. 字母异位词分组

给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。

示例:

输入: [“eat”, “tea”, “tan”, “ate”, “nat”, “bat”]
输出:
[
[“ate”,“eat”,“tea”],
[“nat”,“tan”],
[“bat”]
]

说明:

  • 所有输入均为 小写字母
  • 不考虑答案输出的顺序。

(1)Map

1.创建一个hashmap,该map用于存储:key:排序后值 value排序前原值
2.遍历取出每个单词,并进行排序,字母异位词排序后相同
3.判断该排序后词是否已经添加进map,未添加则存入排序后值,并新创建一数组
4.按照<排序后值,排序前原值数组>依次存入每个词

class Solution {
     public List<List<String>> groupAnagrams(String[] strs) {
        Map<String, ArrayList<String>> map = new HashMap<>();      //该map用于存储:key:排序后值 value排序前原值
        for (int i = 0; i < strs.length ; i++) {            //遍历取出每个值
            char[] chars = strs[i].toCharArray();
            Arrays.sort(chars);                             //排序
            String key = String.valueOf(chars);             
            if (!map.containsKey(key)) {                 //判断该词是否已经添加进map,未添加则存入排序后值,新创建一数组
                map.put(key, new ArrayList<>());
            }
            map.get(key).add(strs[i]);                     
        }
        return new ArrayList<>(map.values());
    }
}

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

在这里插入图片描述
注意:所有输入均为 小写字母

子串问题都考虑滑动窗口

(1)滑动窗口 + hashMap

class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        // 两个哈希表,记录窗口中的字符和需要凑齐的字符
        Map<Character, Integer> need = new HashMap<>();
        Map<Character, Integer> window = new HashMap<>();

        // 使用 left 和 right 变量初始化窗口的两端
        // valid 变量表示窗口中满足 need 条件的字符个数
        int left = 0, right = 0;
        int valid = 0;

        // 存储结果
        List<Integer> result = new ArrayList<>();

        // 遍历字符串 p ,初始化每个字母的次数
        for (char c : p.toCharArray()) {
            need.put(c, need.getOrDefault(c, 0) + 1);
        }

        // 滑动窗口主体
        while (right < s.length()) {
            // charRight 是移入窗口的字符
            char charRight = s.charAt(right);
            // 右移窗口
            right++;
            // 进行窗口内数据的更新,如果移入窗口字符存在于need中
            if (need.containsKey(charRight)) {
                window.put(charRight, window.getOrDefault(charRight, 0) + 1);
                //如果存在于窗口中的charRight词数量 与need中数量一致,valid + 1;
                if (window.get(charRight).equals(need.get(charRight))) {
                    valid++;
                }
            }

            // 判断左侧窗口是否要收缩
            while (right - left >= p.length()) {
                // 当窗口符合条件时,把起始索引加入 result
                if (valid == need.size()) {
                    result.add(left);
                }
                
                // charLeft 是将要移出窗口的字符,并左移窗口
                char charLeft = s.charAt(left);
                left++;
                // 进行窗口内数据的一系列更新,判断删除字符是否已存在于窗口 与 need中
                if (need.containsKey(charLeft)) {
                    //如果存在于窗口中的charRight词数量也与need中数量一致,证明当前字符的valid + 1了
                    //如果移出则不满足条件,valid 应该 -1
                    if (window.get(charLeft).equals(need.get(charLeft))) {
                        valid--;
                    }
                    window.put(charLeft, window.get(charLeft) - 1);
                }
            }
        }
        return result;
    }
}

(2)滑动窗口(数组优化)

  • 上面方法采用滑动窗口,运行效率低的原因是因为在判断是否窗口内的子串是否是字母异位词时,采用的方法是俩个fre数组进行比较或者重新计数进行比较这样每次都多了一个O(len§级的时间。如何避免这种费时的比较呢
  • 使用一个常量diff表示窗口内的子串和字母异位词的差距
 class Solution {
        public List<Integer> findAnagrams(String s, String p) {
            int[] fre = new int[26];


            //表示窗口内相差的字符的数量
            int dif = 0;
            
            // fre 统计频数
            for (char c : p.toCharArray()) {
                fre[c - 'a']++;
                dif++;
            }
            
            int left=0,right=0;
            int length = s.length();
            char[] array = s.toCharArray();
            
            List<Integer> result = new ArrayList<>();

            while (right < length) {
                char rightChar = array[right];

                //是p中的字符  
                if (fre[rightChar-'a'] > 0) {
                    fre[rightChar-'a']--;
                    //差距减少
                    dif--;
                    right++;

                    //差距减少为0时 说明窗口内为所求
                    if (dif == 0) {
                        result.add(left);
                    }
                } else {
                    // 第一种情况 rightChar 是p以外的字符串如"c" "abc" "ab" 此时 left 和 right 都应该指向 c后面的位置   
                    // left 和 right 都应该定位到 c  所以要恢复fre数组 同时恢复差距 
                    while (fre[array[right] -'a'] <= 0 && left < right) {
                        fre[array[left] - 'a']++;
                        left++;
                        dif++;
                    }

                    // 第二种情况 rightChar是p内的字符串但是是额外的一个char如第二个"b" 例 "abb" "ab" 此时right不变 left应该指向第一个b后面的位置
                    //此时fre[array[right]-'a']=0 让left移动到第一个b后面的位置 这样就融入了新的b(第二个b)
                    if (left == right ) {
                        //这个if用来检测right所处字符是否是p外的字符
                        //用来处理第二种情况 
                        if (fre[array[left] - 'a'] > 0) {
                            //说明是p里的字符 跳过
                            continue;
                        }else{
                        //用来处理第一种情况 移动到这个字符后面的位置
                            left++;
                            right++;
                        }

                    }
                }
            }
            return result;
        }
    }

3. 字符串的排列

在这里插入图片描述

(1)滑动窗口

  • 首先判断长度,s1的长度不能小于s2的长度
  • s1的排列有很多种,但是其中包含的字符都是一样的,我们在s2中取一个长度为s1的长度的滑动窗
  • 每次看滑动窗内各个字符的数量是否都相等
  • 可以用两个数组来存储字符的数量,就是将a-z出现的次数映射到数组的0-25
class Solution {
    public boolean checkInclusion(String s1, String s2) {
        //如果s1长度更大直接返回false
       if (s1.length() > s2.length())
          return false;      

        //两个字典数组用于存储字符的数量,将a-z出现的次数映射到数组的0-25
        int nums1[]=new int[26];
        int nums2[]=new int[26];

        //统计s1中各个单词的数量,放于num1中
        for (int i = 0; i < s1.length(); i++)
           nums1[s1.charAt(i)-'a']++;

        //特殊情况:以s1长度len1为滑动窗口长度,统计s2中各个单词的数量,放于num2中
        //s2中最开始len1个字符直接与s1相等
        int left=0;
        int right = left + s1.length()-1;
        for(int i = left; i <= right; i++){
             nums2[s2.charAt(i)-'a']++;
        }
        //判断两个字典数组是否相等
        if(isEqual(nums1,nums2))
           return true;

        while (right < s2.length()) {           
            nums2[s2.charAt(left)-'a']--;      //left对应的字符移出滑动窗
            left++;
            right++; 
            if (right == s2.length())              //避免right越界
               return false;         
            nums2[s2.charAt(right)-'a']++;     //right对应的字符加入滑动窗
            if (isEqual(nums1, nums2))
               return true;     
        }
        return false;
    }

    //判断两个字典数组是否相等
    public boolean isEqual(int arr1[], int arr2[]) {
        for(int i = 0; i < arr1.length; i++) {
            if(arr1[i]!=arr2[i])
               return false;
        }
        return true;
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Yawn__

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

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

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

打赏作者

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

抵扣说明:

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

余额充值