【算法】剑指 Offer 专项突击版 Day6字符串部分

【算法】剑指 Offer 专项突击版 Day6字符串部分

题目地址https://leetcode-cn.com/study-plan/lcof/?progress=wgzvtig

目标:要点总结,分享思路



【困难】017. 含有所有字符的最短字符串

在这里插入图片描述
难度:思考较难,但换成词频统计+滑窗双指针角度就简单了
要点

  • 思路:暴力思考就是在s中的所有子串中寻找包含t的最短子串,而这里包含可以优化为字符串词频统计,如t=abac则t的词频就是a2b1c1,当子串si=aabaxxac时词频为a4b1c1,满足词频要求即包含,对于非t中字符无需统计词频
  • 因此,不断在s中移动右指针,使得当子串si的每个词的词频均≥t的词频时表示si已经包含了t
  • 此时,我们就要在子串si中寻找包含的最小子串了,即移动左指针,减小si的有效词频,并满足词频≥t的要求
  • 如果我没解释清楚,请直接参照下面这个示例即可:
    – s = “ADOBECODEBANC”,t = “ABC”
    – 初始时左右指针l,r=0,0代表子串si=“A”不满足词频要求
    – 移动右指针直到r=5代表子串si=“ADOBEC”满足词频要求了
    – 此时移动左指针缩小子串继续寻找,l=1代表子串si=“DOBEC”不满足词频要求接着移动右指针r,直到r=10代表子串si=“DOBECODEBA”满足词频要求
    – 移动左指针l=3代表子串si=“BECODEBA”满足词频,可以继续移动左指针l=5代表子串si=“CODEBA”满足词频,继续移动l=6代表子串si=“ODEBA”不满足词频
    – 移动右指针到r=12代表子串si=“ODEBANC”满足词频,移动左指针缩小
    – 最终得到BANC最短包含子串
  • 总结:笔者承认解释的还是不够清楚,但只要记住双指针,移动右指针代表扩大窗口直到满足词频包含要求,求出解,此时改为移动左指针代表缩小窗口,求出更优解

代码如下:

from collections import defaultdict
class Solution:
    def minWindow(self, s: str, t: str) -> str:
        ans = ""
        if len(s)<len(t):return ans
        wordDic = defaultdict(int)
        for ct in t:wordDic[ct]+=1
        l,r = 0,0
        valid_alpha = len(wordDic) 
        while r<len(s):
            c = s[r]
            if c in wordDic:
                wordDic[c]-=1
                if wordDic[c]==0:valid_alpha-=1
            # 当valid_alpha=0时代表此时s已经包含了t,记录答案并移动左指针缩小
            while valid_alpha==0 and l<=r:
                if len(ans)>(r-l+1) or not ans:
                    ans = s[l:r+1]
                cl = s[l]
                if cl in wordDic:
                    wordDic[cl]+=1
                    if wordDic[cl]>0:valid_alpha+=1
                l+=1
            r+=1
        return ans

【简单】018. 有效的回文

在这里插入图片描述
难度:简单,回文字符串的判断方法有很多种,这里提供思考和实现简单的方法:双端队列法

要点

  • 思路:将字符串s装换成双端队列queue,若队头队尾的字符相同则分别出队,若不同则不是回文字符串
  • 这里只要注意题目细节即可,仅考虑数字和字母,且不区分大小写,故需要一定的字符串清理
  • 当然,也有偷鸡方法,回文判断可以通过颠倒字符串判断即s==s[::-1]

代码如下:

class Solution:
    def clean(self,s:str):
        s = s.lower()
        cleaned_s = []
        for i in s:
            if i.isdigit() or i.isalpha():
                cleaned_s.append(i)
        return "".join(cleaned_s)
    def isPalindrome(self, s: str) -> bool:
        s = self.clean(s)
        # # 小技巧:字符串颠倒后仍相同则回文,这样一行代码就解决了
        # return s==s[::-1]
        # 双端队列
        queue = list(s)
        while len(queue)>1:
            if queue[0]!=queue[-1]:
                return False
            del queue[0]
            del queue[-1]
        return True

【简单】019. 最多删除一个字符得到回文

在这里插入图片描述
难度:不算简单,但好理解,与上一题思考一样。

要点

  • 思路:删除字符得回文,即当判断字符串不回文时,去头或去尾再多两次判断就好,此时自然想到递归
  • 其实这里还是可以用双端队列写的,但会超时。因而改为同理的双指针法实现,本质一样。
  • 注意改成双指针写法时,边界要考虑两边,很可能仅注意左边界忽略了右边界

代码如下:

class Solution:
    def __init__(self):
        self.flag = 1
    """
    def validPalindrome(self, s: str) -> bool:
        # 双端队列写法,超时
        queue = list(s)
        while len(queue)>1:
            if queue[0]!=queue[-1] and self.flag:
                self.flag = 0
                return self.validPalindrome(queue[1:]) or self.validPalindrome(queue[:-1])
            elif queue[0]==queue[-1]:
                del queue[0]
                del queue[-1]
            else:
                return False
        return True
        """
    def validPalindrome(self, s: str) -> bool:
        # 双指针写法
        left,right = 0,len(s)-1
        while left<right:
            if s[left]==s[right]:
                left+=1
                right-=1
            elif s[left]!=s[right] and self.flag:
                self.flag = 0
                # 这里要注意递归传入字符串s的左右指针
                # 很可能仅注意左边界忽略了右边界
                return self.validPalindrome(s[left+1:right+1]) or self.validPalindrome(s[left:right])
            else:
                return False
        return True

【中等】020. 回文子字符串的个数

在这里插入图片描述
难度:个人感受较难,自己做的时候能想到回文中心延伸和动态规划,但写不出来。

要点

  • 思路:

  • (法1)直观想法暴力遍历s的所有子串,并判断统计子串是否回文,遍历需要 O ( n 2 ) O(n^2) O(n2),判断回文需要 O ( n ) O(n) O(n),总体时间复杂度 O ( n 3 ) O(n^3) O(n3)
       优化:遍历子串必不可少,判断回文可以用字符串颠倒判断优化为 O ( 1 ) O(1) O(1),时间复杂度 O ( n 2 ) O(n^2) O(n2),空间复杂度 O ( n ) O(n) O(n)

  • (法2)回文中心拓展法,确定一个字符为回文中心,同时向左向右扩展
       比如奇数长度s=bab,当回文中心为b时无法向左扩展,当回文中心为a时可以向左右扩展一次得到bab,当回文中心为b无法向右扩展
       比如偶数长度s=baab,当回文中心为b时无法向左扩展,当回文中心为a时可以向左右扩展一次得到baa,当回文中心为a时可以向左右扩展一次得到aab,当回文中心为b时无法向右扩展
    但这样会错过aa子串,因而增加可以确定一组回文中心即,start=i,end=i+1这样aa子串也包含进去了

  • Manacher马拉车算法巧妙但需要理解,请自行学习:链接在此

代码如下:

class Solution:
    def countSubstrings(self, s: str) -> int:
        # 暴力优化法
        count = 0
        n = len(s)
        for i in range(n):
            s1 = ''
            s2 = ''
            # 回文串判断就是:字符串颠倒后不变
            # 因此优化回文判断O(n)->O(1)
            for j in range(i,n):
                s1=s1+s[j]
                s2=s[j]+s2
                if s1==s2:count+=1
        return count

    def countSubstrings(self, s: str) -> int:
        # 中心扩展法
        if len(s)<2:return len(s)
        def MoveCount(s,start,end):
            count = 0
            while start>=0 and end<len(s):
                if s[start]!=s[end]:
                    break
                count+=1
                start-=1
                end  +=1
            return count

        ans = 0
        for i in range(len(s)):
            # 
            ans += MoveCount(s,start=i,end=i)
            ans += MoveCount(s,start=i,end=i+1)
        return ans
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

阿芒Aris

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

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

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

打赏作者

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

抵扣说明:

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

余额充值