【图解算法】- 异位词问题:双指针+哈希表

  一 - 前言

介绍:大家好啊,我是hitzaki辰。

社区:(完全免费、欢迎加入)日常打卡、学习交流、资源共享的知识星球。

自媒体:我会在b站/抖音更新视频讲解 或 一些纯技术外的分享,账号同名:hitzaki辰。

正文开始,抓紧上车!


二 - 异位词问题概述

1 - 异位词是什么

1)比如 abc 和 bca就是一个异构词

2)异构词的简单判断:

(1)长度相等

(2)每个字母的数量都相等

2 - 判断异位词

对于需要比较的字符串s和t,使用哈希表可以很方便的判断异位词,维护1个count和一个哈希表

1)关键代码1解读 - 迭代s串

对字符串s的所有字符都进行哈希,此时map对应每个字符的数量,count代表字符的种类。

Map<Character, Integer> map = new HashMap<>();
int count = 0;
for(int i=0; i<p.length(); i++){
    Integer temp = map.get(p.charAt(i));
    map.put(p.charAt(i), temp==null? 1: temp+1);
    if(temp==null) count++;
}

比如aabc对应的map和count迭代过程为:

2)关键代码2解读 - 迭代t串:(不同的题,我们迭代的方式不同。)

获取当前t.charAt(i),

(1)若map中没有对应key,说明不是异位词

(2)若有key,将对应value-1,若减为0,则将count-1。

(3)若有key,且对应value已经等于0,说明不是异位词。

最后判断count是否为0,若为0说明是异位词。

以aaba为例:

3 - 根据题干优化

如果题干说只有小写或大写字母这种情况,即固定了出现可能的集合,则我们可以使用一个数组代替哈希表

s串每次迭代只需要这样:++count[s.charAt(i) - 'a'];

三 - 例题1:找到字符串所有字母异位词

题目索引:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

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

1 - 使用哈希表题解:详细注解

class Solution {

  public List<Integer> findAnagrams(String s, String p) {

​    Map<Character, Integer> map = new HashMap<>();

​    int count = 0;

​    List<Integer> result = new ArrayList<>();

​    for(int i=0; i<p.length(); i++){

​      Integer temp = map.get(p.charAt(i));

​      map.put(p.charAt(i), temp==null? 1: temp+1);

​      if(temp==null) count++;

​    }

​    int p1=0, p2=0;

​    boolean flag;

​    while(p2<s.length()){

​      // 迭代p2

​      Integer temp2 = map.get(s.charAt(p2));

​      if(temp2==null){ // 包含p2的都不可能,因此结束

​        flag = false;

​      }else{

​        flag = true;

​        map.put(s.charAt(p2), temp2-1);

​        if(temp2==1)count--;

​      }

​      p2++;

​      

​      // 迭代p1, 根据p2情况进行迭代

​      // 1.若p2存在, 则当p2-p1==p.length()时才进入

​      // 若p2不存在, 则迭代至p1==p2

​      while(flag? p2-p1==p.length(): p1<p2-1){ // 只有p2-1时有null判断

​        if(count==0)result.add(p1);

​        // 将p1位置给去除

​        Integer temp1 = map.get(s.charAt(p1)); 

​        if(temp1==0)count++;

​        map.put(s.charAt(p1), temp1+1);

​        p1++;

​      }

​      if(!flag)p1++; // p1=p2-1时候一定为null, 跳过

​    } // 主迭代结束

​    return result;

  }

}

2 - 使用数组题解:注解

class Solution {

  public List<Integer> findAnagrams(String s, String p) {

​    int sLen = s.length(), 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'];

​      --count[p.charAt(i) - 'a'];

​    }



​    int differ = 0;

​    for (int j = 0; j < 26; ++j) {

​      if (count[j] != 0) {

​        ++differ;

​      }

​    }



​    if (differ == 0) {

​      ans.add(0);

​    }



​    for (int i = 0; i < sLen - pLen; ++i) {

​      if (count[s.charAt(i) - 'a'] == 1) {  // 窗口中字母 s[i] 的数量与字符串 p 中的数量从不同变得相同

​        --differ;

​      } else if (count[s.charAt(i) - 'a'] == 0) {  // 窗口中字母 s[i] 的数量与字符串 p 中的数量从相同变得不同

​        ++differ;

​      }

​      --count[s.charAt(i) - 'a'];

​      if (count[s.charAt(i + pLen) - 'a'] == -1) {  // 窗口中字母 s[i+pLen] 的数量与字符串 p 中的数量从不同变得相同

​        --differ;

​      } else if (count[s.charAt(i + pLen) - 'a'] == 0) {  // 窗口中字母 s[i+pLen] 的数量与字符串 p 中的数量从相同变得不同

​        ++differ;

​      }

​      ++count[s.charAt(i + pLen) - 'a'];

​      

​      if (differ == 0) {

​        ans.add(i + 1);

​      }

​    }



​    return ans;

  }

}

四 - 例题2:最小覆盖子串

题目索引:https://leetcode.cn/problems/minimum-window-substring/description/

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 ""

1 - 思路

例子:s = "ADOBECODEBANC", t = "ABC"

使用双指针

(1)左指针用来将map和count恢复

(2)右指针用来将map对应的value减一,并改变count

当count=0时,说明此时符合条件,则将此时的子串记录下来。

2 - 题解:详细注解

维护一个数量count和map, 先迭代t将map填充,然后让count = map.size

(1)p2遍历到的如果在map里就将map里对应元素-1, 如果刚好减到0就count-1

(2)如果count==0, 则开始遍历p1,

  每次让p1对应到map的元素+1, 每次迭代判断长度, 若小于min则更新

class Solution {
    public static String minWindow(String s, String t) {
        Map<Character, Integer> map = new HashMap<>();
        int min=Integer.MAX_VALUE, index=0,  count;
        Integer temp;
        for(int i=0; i<t.length(); i++){
            temp = map.get(t.charAt(i));
            map.put(t.charAt(i), temp==null? 1: temp+1);
        }
        count = map.size();
        int p1=0, p2=0;
        for(;p2<s.length();p2++){
            // 添加p2位置
            temp = map.get(s.charAt(p2));
            if(temp==null)continue;
            // 如果为1说明减完p2元素会归0, 则count变化
            map.put(s.charAt(p2), temp-1);
            if(temp==1) count--;
            // 迭代p1
            while(count==0){
                if(p2-p1+1<min) {
                    min = p2-p1+1;
                    index = p1;
                }
                // 去掉当前p1的元素
                temp = map.get(s.charAt(p1));
                p1++;
                if(temp==null)continue;
                map.put(s.charAt(p1-1), temp+1);
                if(temp==0) count++;
            }
        }
        if(min == Integer.MAX_VALUE)return "";
        return s.substring(index, index+min);
    }
}

五 - 结尾

感谢你看到这里,如果感觉内容不错的话请点赞支持一下!

如果小伙伴对我的讲解有疑问,欢迎评论区讨论。

  • 8
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

hitzaki辰

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

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

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

打赏作者

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

抵扣说明:

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

余额充值