(day26)leecode热题——找到字符串中所有字母异位词

news2024/9/21 0:38:17

描述

给定两个字符串 s 和 p,找到 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
  • s 和 p 仅包含小写字母

超时代码

class Solution:
    def findAnagrams(self, s: str, p: str) -> List[int]:
        li = [s[i:i+len(p)] for i in range(0, len(s)-len(p)+1, 1)]
        lis = []
        for i in range(len(li)):
            if sorted(li[i]) == sorted(p):
                lis.append(i)
            else:
                continue
        return lis

 leecode题解labuladong

 

from collections import Counter, defaultdict
class Solution:
    def findAnagrams(self, s: str, p: str) -> List[int]:
        need = Counter(p)  # 统计目标字符串t中每个字符的频率
        window = defaultdict(int)  # 记录窗口中字符的频率
        
        left, right = 0, 0  # 左右指针初始化
        valid = 0  # 记录当前窗口中满足条件的字符数
        res = []  # 存储结果的列表

        while right < len(s):  # 遍历字符串s
            c = s[right]  # 当前字符
            right += 1  # 移动右指针
            # 进行窗口内数据的一系列更新
            if c in need:
                window[c] += 1
                if window[c] == need[c]:
                    valid += 1
            
            # 判断左侧窗口是否需要收缩
            while right - left >= len(p):
                # 当窗口符合条件时,把起始索引加入res
                if valid == len(need):
                    res.append(left)
                d = s[left]  # 要移出窗口的字符
                left += 1  # 移动左指针
                # 进行窗口内数据的一系列更新
                if d in need:
                    if window[d] == need[d]:
                        valid -= 1
                    window[d] -= 1

        return res

 整体分析

1. 初始化阶段
  • 统计目标字符串 t 中每个字符的频率: 使用 Counter 创建字典 need,记录 t 中每个字符的频率。

  • 初始化窗口字典: 使用 defaultdict(int) 创建字典 window,用于记录当前窗口中字符的频率。

  • 设置左右指针: 初始化左右指针 leftright 均为0。

  • 初始化辅助变量: 变量 valid 用于记录当前窗口中满足条件的字符数。 列表 res 用于存储结果,即符合条件的起始索引。

2. 扩展右边界
  • 遍历字符串 s: 使用 while 循环遍历字符串 s,右指针 right 从左到右移动。

  • 更新窗口字符频率: 将 right 指针指向的字符加入窗口,更新 window 字典中该字符的频率。

  • 检查是否满足条件: 如果该字符在 need 字典中,并且其频率与 need 中的频率一致,则更新 valid 计数。

3. 收缩左边界
  • 判断窗口大小: 当窗口的大小达到字符串 t 的长度时,进入内层 while 循环。

  • 检查窗口是否符合条件: 如果当前窗口中的字符频率与 t 中字符频率一致,则将 left 指针的位置加入结果列表 res

  • 收缩窗口: 移动 left 指针,缩小窗口,并更新窗口中左边字符的频率。

  • 更新满足条件的字符数: 如果移除的字符在 need 字典中,并且其频率在窗口中不再满足 need 的要求,则更新 valid 计数。

4. 返回结果
  • 输出结果列表: 当所有字符遍历完成后,返回结果列表 res,其中存储了所有符合条件的起始索引。
关键点
  • 滑动窗口:通过左右指针维护一个大小为 t 长度的窗口。
  • 频率统计:使用 Counterdefaultdict 统计字符频率。
  • 条件判断:通过 valid 判断当前窗口是否符合条件。
  • 结果存储:符合条件时,将窗口的起始索引存储到结果列表中。

 

 滑动窗口

 维护一个窗口,不断滑动,然后更新答案。算法技巧的时间复杂度是 O(N),比字符串暴力算法要高效得多。细节:如何向窗口中添加新元素,如何缩小窗口,在窗口滑动的哪个阶段更新结果。

python框架

from collections import defaultdict

def sliding_window(s, t):
    # 创建need字典,记录目标字符串t中每个字符的频率
    need = defaultdict(int)
    for c in t:
        need[c] += 1

    window = defaultdict(int)  # 创建window字典,记录当前窗口中每个字符的频率
    left, right = 0, 0  # 初始化左、右指针
    valid = 0  # 记录当前窗口中满足条件的字符数

    while right < len(s):
        # c 是将移入窗口的字符
        c = s[right]
        # 右移窗口
        right += 1
        # 进行窗口内数据的一系列更新
        # 此处应填写窗口右移时的处理逻辑
        ...

        # debug 输出的位置
        print(f"window: [{left}, {right})")

        # 判断左侧窗口是否要收缩
        while window_needs_shrink():  # 此处应填写判断窗口是否需要收缩的条件
            # d 是将移出窗口的字符
            d = s[left]
            # 左移窗口
            left += 1
            # 进行窗口内数据的一系列更新
            # 此处应填写窗口左移时的处理逻辑
            ...

def window_needs_shrink():
    # 这是一个占位函数,用于判断窗口是否需要收缩
    # 你需要根据具体逻辑实现该函数
    return False


其中两处 ... 表示的更新窗口数据的地方,到时候你直接往里面填就行了。

而且,这两个 ... 处的操作分别是右移和左移窗口更新操作,等会你会发现它们操作是完全对称的。

 框架说明

上述Python框架代码主要涉及以下几个重要的知识点和数据结构:

 1. defaultdict
from collections import defaultdict

- **定义**:`defaultdict` 是 `collections` 模块中的一个类,它继承自内置的 `dict` 类。与普通字典不同的是,`defaultdict` 在访问不存在的键时,不会抛出 `KeyError`,而是会根据提供的默认工厂函数生成一个默认值。
- **用途**:在需要频繁处理不存在的键的情况下,`defaultdict` 可以简化代码,避免手动检查键是否存在。
 

need = defaultdict(int)
for c in t:
    need[c] += 1


在这里,`need` 是一个 `defaultdict`,默认值为 `int` 类型,即默认值为 0。这样,在统计字符频率时,如果某个字符在 `need` 中不存在,会自动添加并初始化为 0。

2. 滑动窗口

滑动窗口是一种常用于字符串或数组问题的算法技巧,用于在一维数据结构中维护一个动态范围(窗口)并根据特定条件进行调整。

- **用途**:适用于需要在一维数据结构(如字符串或数组)中查找满足特定条件的子数组或子字符串的问题。
- **核心思想**:通过两个指针(左指针 `left` 和右指针 `right`)维护一个窗口,动态调整窗口大小,移动窗口的位置以找到符合条件的子数组或子字符串。
- **示例**:

left, right = 0, 0
while right < len(s):
    c = s[right]
    right += 1
    ...
    while window_needs_shrink():
        d = s[left]
        left += 1
        ...
3. 字符频率统计

使用字典或 `defaultdict` 统计字符串中字符的频率,是处理字符串问题的常用技巧。

- **用途**:在涉及字符串比较、匹配等操作时,字符频率统计是一个基础步骤。
- **示例**:

need = defaultdict(int)
for c in t:
    need[c] += 1


在这里,`need` 字典用于统计字符串 `t` 中每个字符的频率。

4. 占位符函数
 
def window_needs_shrink():
    return False

- **用途**:在初步搭建框架时,占位符函数用于表示某个需要实现的具体逻辑。
- **实现**:在实际应用中,这个函数需要根据特定问题的需求进行实现,以判断窗口是否需要收缩。

 5. 字符串操作

在字符串中,通过索引访问字符是常见的操作。

- **示例**:

c = s[right]  # 获取当前右指针指向的字符
d = s[left]   # 获取当前左指针指向的字符


通过这种方式,可以获取窗口左右边界的字符,进行相应的逻辑处理。

总结

上述代码框架结合了 `defaultdict`、滑动窗口、字符频率统计等数据结构和算法技巧,为解决类似字符串匹配或子数组查找的问题提供了基础结构。通过填充具体的逻辑,可以实现特定功能的滑动窗口算法。

 速记口诀

 

整体思路 

滑动窗口算法的思路是这样:

1、我们在字符串 S 中使用双指针中的左右指针技巧,初始化 left = right = 0,把索引左闭右开区间 [left, right) 称为一个「窗口」。

2、我们先不断地增加 right 指针扩大窗口 [left, right),直到窗口中的字符串符合要求(包含了 T 中的所有字符)。

3、此时,我们停止增加 right,转而不断增加 left 指针缩小窗口 [left, right),直到窗口中的字符串不再符合要求(不包含 T 中的所有字符了)。同时,每次增加 left,我们都要更新一轮结果。

4、重复第 2 和第 3 步,直到 right 到达字符串 S 的尽头。

这个思路其实也不难,**第 2 步相当于在寻找一个「可行解」,然后第 3 步在优化这个「可行解」,最终找到最优解,**也就是最短的覆盖子串。左右指针轮流前进,窗口大小增增减减,窗口不断向右滑动,这就是「滑动窗口」这个名字的来历。

下面画图理解一下,needs 和 window 相当于计数器,分别记录 T 中字符出现次数和「窗口」中的相应字符的出现次数。

初始状态:

 

{:align=center}

增加 right,直到窗口 [left, right) 包含了 T 中所有字符:

 

{:align=center}

现在开始增加 left,缩小窗口 [left, right)。

 

{:align=center}

直到窗口中的字符串不再符合要求,left 不再继续移动。

 

{:align=center}

之后重复上述过程,先移动 right,再移动 left…… 直到 right 指针到达字符串 S 的末端,算法结束。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1953806.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【QGroundControl二次开发】六. QGC地面站中视频流配置gstreamer

本文解决qgc源码编译后无法接收视频推流&#xff0c;原因是缺少gstreamer。 下面为windows安装流程&#xff0c;qgc版本为4.4.1稳定版 1. 安装gstreamer 官网链接&#xff1a;https://gstreamer.freedesktop.org/download/#windows 两个都要下载。安装的时候&#xff0c;在cu…

网络安全自学从入门到精通的制胜攻略!!!

在信息时代&#xff0c;网络安全已成为至关重要的领域。越来越多的人希望通过自学掌握这门技术&#xff0c;开启充满挑战与机遇的职业道路。以下是一份精心为您打造的网络安全自学攻略&#xff0c;助您在自学之旅中乘风破浪。 一、明确目标与兴趣方向 网络安全涵盖众多领域&am…

Java的四种引用类型

Java的四种引用类型 1. 强引用&#xff08;Strong Reference&#xff09;2. 软引用&#xff08;Soft Reference&#xff09;3. 弱引用&#xff08;Weak Reference&#xff09;4. 虚引用&#xff08;Phantom Reference&#xff09; &#x1f496;The Begin&#x1f496;点点关注…

硬件工程师笔面试真题汇总

目录 1、电阻 1&#xff09;上拉电阻的作用 2&#xff09;PTC热敏电阻作为电源电路保险丝的工作原理 2、电容 1&#xff09;电容的特性 2) 电容的特性曲线 3) 1uf的电容通常来滤除什么频率的信号 3、电感 4、二极管 1&#xff09;二极管特性 2&#xff09;二极管伏安…

MySQL练手 --- 1633. 各赛事的用户注册率

题目链接&#xff1a;1633. 各赛事的用户注册率 思路&#xff1a; 两张表&#xff0c;一张为Users用户表&#xff0c;该表存储着所有用户的 user_id 和 user_name 另一张表为Register注册表&#xff0c;该表存储着赛事id&#xff08;contest_id&#xff09;&#xff0c;和已…

NumpyPandas:Pandas库(25%-50%)

目录 前言 一、列操作 1.修改变量列 2.筛选变量列 3.删除变量列 4.添加变量列 二、数据类型的转换 1.查看数据类型 2.将 ok的int类型转换成float类型 3.将ar的float类型转换成int类型 三、建立索引 1.建立DataFrame时建立索引 2.在读入数据时建立索引 3.指定某列或…

PHP8.3.9安装记录,Phpmyadmin访问提示缺少mysqli

ubuntu 22.0.4 腾讯云主机 下载好依赖 sudo apt update sudo apt install -y build-essential libxml2-dev libssl-dev libcurl4-openssl-dev pkg-config libbz2-dev libreadline-dev libicu-dev libsqlite3-dev libwebp-dev 下载php8.3.9安装包 nullhttps://www.php.net/d…

【Linux】生产者消费者模型 + 线程池的介绍和代码实现

前言 上节我们学习了线程的同步与互斥&#xff0c;学习了互斥锁和条件变量的使用。本章我们将学习编程的一个重要模型&#xff0c;生产者消费者模型&#xff0c;并且运用之前学的线程同步和互斥的相关接口来实现阻塞队列和环形队列&#xff0c;最后再来实现一个简易的线程池。 …

springboot电子产品销售系统-计算机毕业设计源码80294

摘 要 电子商务行业在全球范围内迅速发展&#xff0c;随之而来的是电子产品销售市场的快速增长和消费者对在线购物体验的需求提升&#xff0c;因此&#xff0c;电子产品销售系统应运而生。该系统旨在满足电子产品市场的需求&#xff0c;提供全面的购物功能和高效的管理操作。 …

严格模式 模块化开发

严格模式 当你在脚本或函数的顶部添加"use strict"语句时&#xff0c;你的代码将在严格模式下执行。这可以帮助你避免某些常见的编程陷阱&#xff0c;例如在不声明变量的情况下就使用它们&#xff0c;或者删除变量、函数或函数参数。在严格模式下&#xff0c;这样的…

进阶篇,内附代码:锂电池二阶模型-离线与在线参数辨识

锂电池二阶模型-在线参数辨识 背景二阶等效电路模型介绍二阶模型的离线参数辨识二阶模型的RLS表达式递推代码已知问题背景 锂电池一阶戴维南等效模型的参数辨识方法,已经在前面两期详细地讲解了一轮。 一阶模型-离线参数辨识一阶模型-在线参数辨识本期继续讲解一下如何进行二…

stm32——lcd液晶显示

一.液晶屏介绍 液晶显示屏是由液晶显示面板&#xff0c;电容触摸屏&#xff0c;pcb底板构成。在液晶显示屏里我们有带控制芯片的还有不带控制芯片的。对于低端的微控制器它不能直接控制液晶面板&#xff0c;所以需要给液晶控制面板而外增加一个液晶控制芯片。对于单片机stm32f4…

Photos框架 - 自定义媒体选择器(UI预览)

引言 在前面的博客中我们已经介绍了使用媒体资源数据的获取&#xff0c;以及自定义的媒体资源选择列表页。在一个功能完整的媒体选择器中&#xff0c;预览自然是必不可少的&#xff0c;本篇博客我们就来实现一个资源的预览功能&#xff0c;并且实现列表和预览的数据联动效果。…

内网横向——远程桌面利用

文章目录 一、远程桌面的确定和开启二、RDP Hijacking 网络拓扑&#xff1a; 攻击机kali IP&#xff1a;192.168.111.0 跳板机win7 IP&#xff1a;192.168.111.128&#xff0c;192.168.52.143 靶机win server 2008 IP&#xff1a;192.168.52.138 一、远程桌面的确定和开启 下面…

VMware、Docker - 让虚拟机走主机代理,解决镜像封禁问题

文章目录 虚拟机全局代理配置找到 VMnet8 的 IPv4 地址代理相关配置虚拟机代理配置 Docker 代理配置修改镜像修改 Docker 代理配置 虚拟机全局代理配置 找到 VMnet8 的 IPv4 地址 a&#xff09;打开此电脑&#xff0c;输入 “控制面板”&#xff0c;然后回车. b&#xff09;之…

元注解相关知识总结

Target 这个注解适用于决定注解的适用范围&#xff0c;例如适用于构造器&#xff0c;方法&#xff0c;字段等 Retention 这个注解的作用是确定注解的生命周期一般用得比较多的是RunTime这样就可以在运行环境中使用它&#xff0c;赋值的方式一般是使用value进行赋值 SOURCE 代…

【初阶数据结构篇】顺序表的实现(赋源码)

文章目录 本篇代码位置顺序表和链表1.线性表2.顺序表2.1 概念与结构2.2分类2.2.1 静态顺序表2.2.2 动态顺序表 2.3 动态顺序表的实现2.3.1动态顺序表的初始化和销毁及打印2.3.2动态顺序表的插入动态顺序表的尾插动态顺序表的头插动态顺序表的在指定位置插入数据 2.3.3动态顺序表…

spring常用注解有哪些

Spring框架使用了大量的注解来简化配置和开发&#xff0c;以下是一些常用的Spring注解&#xff1a; 1.Component&#xff1a;通用的构造型注解&#xff0c;用于标记一个类作为Spring管理的组件&#xff0c;通常用于自定义组件。 2.Autowired&#xff1a;用于自动装配Bean&#…

Android Add a Header to the GridLayout

RecyclerView 添加 Header, RecycleView 设置 LayoutManager 为 GridLayoutManger的 时候&#xff1b; Header 要占用3行 val manager GridLayoutManager(activity, 3)binding.sleepList.layoutManager manager// span size for each position, and assign it to manager.spa…

Golang零基础入门课_20240726 课程笔记

视频课程 最近发现越来越多的公司在用Golang了&#xff0c;所以精心整理了一套视频教程给大家&#xff0c;这个只是其中的第一部&#xff0c;后续还会有很多。 视频已经录制完成&#xff0c;完整目录截图如下&#xff1a; 课程目录 01 第一个Go程序.mp402 定义变量.mp403 …