Java【手撕滑动窗口】LeetCode 438. “字符串中所有异位词“, 图文详解思路分析 + 代码

news/2024/5/20 10:27:04 标签: java, leetcode, 滑动窗口, 双指针, 异位词

文章目录

  • 前言
  • 一、字符串中所有异位词
    • 1, 题目
    • 2, 思路分析
      • 2.1, 引入哈希表找出异位词
      • 2.2, 引入变量记录"有效字符的个数"
      • 2.3, left 右移维护窗口
      • 2.4, 总结核心步骤
    • 3, 代码


前言

各位读者好, 我是小陈, 这是我的个人主页, 希望我的专栏能够帮助到你:
📕 JavaSE基础: 基础语法, 类和对象, 封装继承多态, 接口, 综合小练习图书管理系统等
📗 Java数据结构: 顺序表, 链表, 堆, 二叉树, 二叉搜索树, 哈希表等
📘 JavaEE初阶: 多线程, 网络编程, TCP/IP协议, HTTP协议, Tomcat, Servlet, Linux, JVM等(正在持续更新)

一、字符串中所有异位词

1, 题目

OJ链接

以示例1为例, 给定字符串 s 为 : "cbaebabacd", 字符串 p 为"abc", "abc""cba", "acb", "bca" 等是异位词, 要求在 s 中找到 p 的所有异位词, 意思就是在 s 中找到连续的子区间, 这段区间是 p 的异位词

  • 关于异位词
    我们只需要 s 的连续子区间内找到包含 ‘a’, ‘b’, ‘c’ 的三个字符即可, 不一定非要是"abc", 这点很重要 ! !

一般来说, 如果我们研究的对象是 “连续的区间” 就可以考虑滑动窗口

滑动窗口其实就是"同向双指针", 滑动窗口的特点是, 前后两个指针不会回退, 并且窗口总是向前滑动, 窗口不是固定大小的, 可能边长也可能变短, 如果你在分析题目的时候发现了这些特征, 那就基本是滑动窗口的解法了


2, 思路分析

首先可以确定的是, 一定要有两个哈希表, 一个哈希表来记录字符串 p 中的字符出现了几次, 一个哈希表用来记录在字符串 s 的子区间中的字符出现了几次

基本思路 :

  • 1, 为了方便, 可以将字符串 s 和 p 分别转化为字符数组 : arrayS 和 arrayP
  • 2, 定义两个哈希表, hashP 和 hashWindow
  • 3, 定义 left 和 right 指针, 初始位置都从0开始, left 用于标记子序列的左边界, right 用于标记子序列的右边界

题目中说明了字符都是 ‘a’ ~ ‘z’ 的小写字符, 为了进一步提高效率, 可以直接使用长度为 26 的 int[] 数组来模拟哈希表, 用字符来映射出 0~25下标, 例如字符 ‘a’ 在哈希表中的下标就是 ‘a’ - ‘a’ = 0, 字符 ‘c’ 在哈希表中的下标就是 ‘c’ - ‘a’ = 2


2.1, 引入哈希表找出异位词

首先要把 arrayP (字符串 p 的字符数组) 中的字符在哈希表中映射, 出现几次就把哈希表中对应下标的值设为几

然后在 arrayS (字符串 s 的字符数组) 中找到的字符也在哈希表中映射, 同上

要注意, 使用者两个哈希表的目的是记录 “字符出现的次数”, 而不是"字符出现的个数"
例如要找 “bbc” 的异位词, ‘b’ 在哈希表中出现两次, ‘c’ 在哈希表中出现一次, 重点是字符出现的次数而不是个数

由于我们要找的子区间长度是和字符串 p 长度一致的, 所以要让 right 和 left 维护子区间的长度

在这里插入图片描述

此时 right 和 left 维护的子区间的长度和 arrayP 长度一致, 并且我们发现两个哈希表中的所记录的 “字符出现的次数” 是一致的, 那就可以认为我们找到了一个异位词

这里可以做优化 ! ! 如何判断两个哈希表中的所记录的 “字符出现的次数” 是一致的?

如果每次在 arrayS 中找到长度为 3 的子区间都遍历两个 hash 表, 是比较浪费时间的, 我们可以引入一个变量 count 用于记录"有效字符的个数"


2.2, 引入变量记录"有效字符的个数"

首先图解一下什么情况下, 字符为有效, 什么情况下不有效

注意观察不同示例下的 arrayS 和 arrayP
在这里插入图片描述
综上所述, right 每遍历到一个新的字符时, 就可以对比两个哈希表中的值来判断该字符是否有效

  • hashWindow[arrayS[right] - 'a'] <= hashP[arrayS[right] - 'a'] 时, 为有效字符, 此时可以让变量 count++
  • count == arrayP 时, 说明 right 和 left 维护的子区间的字符全都是有效字符, 即全都是 arrayP 中的字符, 此时可以把 left 加入到 list 中, 作为返回结果

2.3, left 右移维护窗口

上面说过, 假设我们要找的异位词的长度为 3 , 那么 right 和 left 维护的子区间(窗口)的长度就不能大于 3, 当窗口长度为 3 时, 判断 count 的值以判断是否为异位词

所以当窗口长度大于 3 时, 需要让 left 右移来实现"滑动窗口", 这一步有值得注意的细节在这里插入图片描述

本题的窗口在"滑动:过程中长度总是为 arrayP.length, 在"滑动窗口"中属于窗口长度不变的类型


2.4, 总结核心步骤

  • 1, right 指向新字符时, 在 hashWindow 中更新该字符出现的次数
  • 2, 判断 right 指向的字符是否为有效字符, 如果是, 令 count++
  • 3, 判断窗口大小是否大于 arrayP.length, 如果是, 令 left–
  • 4, 在 left-- 之前判断当前 left 指向的字符是否为有效字符, 如果是, 则 count- -
  • 5, 判断 count 是否和 arrayP.length 相等, 如果是, 令当前 left 存入 list 中

3, 代码

java">	public List<Integer> findAnagrams(String s, String p) {
        List<Integer> ret = new ArrayList<>();
        char[] arrayP = p.toCharArray();
        char[] arrayS = s.toCharArray();
        int[] hashP = new int[26];
        int[] hashWindow = new int[26];
        for(char ch : arrayP) {
            hashP[ch-'a']++;
        }
        int left = 0;
        int right = 0;
        int count = 0;// 有效字符个数
        while(right < arrayS.length) {
            hashWindow[arrayS[right] - 'a']++;
            if(hashWindow[arrayS[right] - 'a'] <= hashP[arrayS[right] - 'a']) {
                count++;
            }
            if(right - left + 1 > arrayP.length) {
                // left 右移之前要先判断是否为有效字符
                if(hashWindow[arrayS[left] - 'a'] <= hashP[arrayS[left] - 'a']) {
                    // 说明是有效字符
                    count--;
                }
                // hash表里面的该字符也要自减一次
                hashWindow[arrayS[left] - 'a']--;
                left++;
            }
            if(count == arrayP.length) {
                ret.add(left);
            }
            right++;
        }
        return ret;
    }

http://www.niftyadmin.cn/n/4997836.html

相关文章

centos7快速修改密码

centos7快速修改密码 小白教程&#xff0c;一看就会&#xff0c;一做就成。 1.命令 #第一种&#xff0c;我经常用这个&#xff0c;这个不行了&#xff0c;会用到第二个echo 用户名:密码 | sudo chpasswd #例如下面 echo root:yegoo123 | chpasswd#第二种echo 密码|passwd --st…

【高阶数据结构】红黑树 {概念及性质;红黑树节点的定义;红黑树插入操作详细解释;红黑树的验证}

红黑树 一、红黑树的概念 红黑树&#xff08;Red Black Tree&#xff09; 是一种自平衡二叉查找树&#xff0c;在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制&#xff0c;红黑树确保没有…

【数据结构】树和二叉树的概念及结构(一)

目录 一&#xff0c;树的概念及结构 1&#xff0c;树的定义 2&#xff0c;树结点的分类及关系 3&#xff0c;树的表示 二&#xff0c;二叉树的概念及结构 1&#xff0c;二叉树的定义 2&#xff0c;特殊的二叉树 3&#xff0c;二叉树的性质 4&#xff0c;二叉树的存储结构 1&…

基于阻塞队列的生产消费模型

目录 一、线程同步 1.生产消费模型&#xff08;或生产者消费者模型&#xff09; 2.认识同步 &#xff08;1&#xff09;生产消费模型中的同步 &#xff08;2&#xff09;生产者消费者模型的特点 二、条件变量 1.认识条件变量 2.条件变量的使用 3.代码改造 三、基于阻…

jpa框架部分重点

1.自定义vo类接收多个实体数据时,返回数据是list集合的 1.1 service 业务层 Override public List<Map<String,EnterpriseFiscalVo>> getDistrictCounty() { List<Map<String,EnterpriseFiscalVo>> list enterpriseFiscalRepository.getDistrict…

04-过滤器和拦截器有什么区别?【Java面试题总结】

过滤器和拦截器有什么区别&#xff1f; 运行顺序不同&#xff1a;过滤器是在 Servlet 容器接收到请求之后&#xff0c;但在 Servlet被调用之前运行的&#xff1b;而拦截器则是在Servlet 被调用之后&#xff0c;但在响应被发送到客户端之前运行的。 过滤器Filter 依赖于 Servle…

Mysql去重求count分页怎么办

最近工作开发了一个需求&#xff0c;为了减少表的创建&#xff0c;对单个表进行了一点冗余的设计&#xff0c;比如一个用户有5个权限&#xff0c;表里需要插入5条数据&#xff0c;只有权限不一样&#xff0c;其它的数据是一样的&#xff0c;对这个表分页查询的时候&#xff0c;…