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

描述

给定两个字符串 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 的末端,算法结束。

  • 10
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
可以创建一个工具类来实现英文字符串日期转换为字符串日期。具体代码如下: ```java import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.Locale; public class DateUtils { /** * 将英文日期字符串转换为文日期字符串 * @param englishDateStr 英文日期字符串,例如:May 26th, 2023 * @return 文日期字符串,例如:2023年05月26日 */ public static String englishToChineseDate(String englishDateStr) { SimpleDateFormat sdf = new SimpleDateFormat("MMMM d, yyyy", Locale.ENGLISH); Date date; try { date = sdf.parse(englishDateStr); } catch (ParseException e) { e.printStackTrace(); return null; } Calendar calendar = Calendar.getInstance(); calendar.setTime(date); String year = String.valueOf(calendar.get(Calendar.YEAR)); String month = String.format("%02d", calendar.get(Calendar.MONTH) + 1); String day = String.format("%02d", calendar.get(Calendar.DAY_OF_MONTH)); return year + "年" + month + "月" + day + "日"; } } ``` 在这个工具类,我们定义了一个`englishToChineseDate`方法,它接收一个英文日期字符串`englishDateStr`作为参数,并返回转换后的文日期字符串。 在方法,我们使用`SimpleDateFormat`类将英文日期字符串解析为`Date`类型的日期对象。接着,我们创建了一个`Calendar`对象,并将其设置为解析后的日期对象。通过`Calendar`对象,我们可以获取年、月、日等日期信息,并将其转换为字符串类型的文日期。 最后,我们将年、月、日拼接成文日期字符串,并返回。 需要注意的是,英文日期字符串的格式是"MMMM d, yyyy"。其,"MMMM"表示月份的英文全称,"d"表示日期(不带前导零),"yyyy"表示年份。如果英文日期字符串的格式不同,需要相应地修改`SimpleDateFormat`构造函数的参数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值