【leetcode】字符串刷题tricks

字符串反转

LeetCode541题 反转字符串II

模拟反转过程就可以

class Solution:
    def reverseStr(self, s: str, k: int) -> str:
        
        def reverse_substring(text):
            left, right = 0, len(text) - 1
            while left < right:
                text[left], text[right] = text[right], text[left]
                left += 1
                right -= 1
            return text
        
        res = list(s)

        for cur in range(0, len(s), 2 * k):
            res[cur: cur + k] = reverse_substring(res[cur: cur + k])
        
        return ''.join(res)

 LeetCode151题 反转字符串中的单词

先反转一遍字符串,再反转一遍字符串中的单词,就是要求的结果

class Solution:
    def reverseWords(self, s: str) -> str:
        res = ''
        s = s[:: -1]
        left = 0
        right = 0
        while right < len(s):
            if s[right] == ' ' and right > 0 and s[right - 1] != ' ':
                res += ' ' + s[left: right][:: -1]
                left = right + 1
            elif s[right] == ' ':
                left = right + 1
            elif right == len(s) - 1:
                res += ' ' + s[left: right + 1][:: -1]
            right += 1
        return res[1:]

双指针法

LeetCode5题 最长回文子串

回文串的的长度可能是奇数也可能是偶数,解决该问题的核心是从中心向两端扩散的双指针技巧。如果输入相同的左右指针,就相当于寻找长度为奇数的回文串,如果输入相邻的左右指针,则相当于寻找长度为偶数的回文串

class Solution:
    def longestPalindrome(self, s: str) -> str:
        res = ''
        for i in range(len(s)):
            s1 = self.longest_huiwen(s, i, i)
            s2 = self.longest_huiwen(s, i, i + 1)
            res = s1 if len(s1) > len(res) else res
            res = s2 if len(s2) > len(res) else res
        return res
 
    def longest_huiwen(self, s, l, r):
        # 中间向两边扩散
        while l >= 0 and r < len(s) and s[l] == s[r]:
            l -= 1
            r += 1
        return s[l + 1: r]

LeetCode844题 比较含退格的字符串

一个字符是否会被删掉,只取决于该字符后面的退格符,而与该字符前面的退格符无关。因此当我们逆序地遍历字符串,就可以立即确定当前字符是否会被删掉

class Solution:
    def backspaceCompare(self, s: str, t: str) -> bool:
        cur_s = len(s) - 1
        cur_t = len(t) - 1
        d_num_s = d_num_t = 0
        while cur_s >= 0 or cur_t >= 0:
            while cur_s >= 0 and (s[cur_s] == '#' or d_num_s > 0):
                if s[cur_s] == '#':
                    d_num_s += 1
                else:
                    d_num_s -= 1
                cur_s -= 1
            while cur_t >= 0 and (t[cur_t] == '#' or d_num_t > 0):
                if t[cur_t] == '#':
                    d_num_t += 1
                else:
                    d_num_t -= 1
                cur_t -= 1
 
            if cur_s >= 0 and cur_t >= 0 and s[cur_s] == t[cur_t]:
                cur_s -= 1
                cur_t -= 1
            elif cur_s < 0 and cur_t < 0:
                return True
            else:
                return False
        return True

滑动窗口

滑动窗口算法技巧主要用来解决子数组问题,比如让你寻找符合某个条件的最长/最短子数组或者子串。对于某些题目,并不需要穷举所有子串,就能找到题目想要的答案。滑动窗口就是这种场景下的一套算法模板,帮你对穷举过程进行剪枝优化,将求解子串复杂度由O(N^2)->O(N)

LeetCode3题 无重复字符的最长子串

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        res = 0
        left = 0
        right = 0
        visited = dict()
        while right < len(s):
            visited[s[right]] = visited.get(s[right], 0) + 1
            # 当新增元素数量>1时,需要持续向右移动left指针
            while visited[s[right]] > 1:
                visited[s[left]] -= 1
                left += 1
            # 统计加入s[right]时满足条件的子串长度
            res = max(res, right - left + 1)
            right += 1
        return res

 LeetCode76题 最小覆盖子串

class Solution:
    def minWindow(self, s: str, t: str) -> str:
        res = ''
        # valid记录被覆盖的字符数
        valid = left = right = 0
        # need统计t中字符数量,window记录窗口中字符数量
        need, window = dict(), dict()
        for i in t:
            need[i] = need.get(i, 0) + 1
        while right < len(s):
            window[s[right]] = window.get(s[right], 0) + 1
            # s[right]被覆盖后,valid+1
            if s[right] in need and window[s[right]] == need[s[right]]:
                valid += 1
            # 如果t被完全覆盖,先更新结果,再缩小窗口
            while valid == len(need):
                res = s[left: right + 1] if right - left + 1 < len(res) or res == '' else res
                # 缩小窗口过程中,若出现字符串不能被覆盖,valid-1
                if s[left] in need and window[s[left]] == need[s[left]]:
                    valid -= 1
                window[s[left]] -= 1
                left += 1
            right += 1
        return res

LeetCode438题 找到字符串中所有字母异位词

class Solution:
    def findAnagrams(self, s: str, p: str) -> List[int]:
        res = []
        valid = left = right = 0
        need, window = dict(), dict()
        for i in p:
            need[i] = need.get(i, 0) + 1
        while right < len(s):
            if s[right] not in need:
                while left < right:
                    if window[s[left]] == need[s[left]]:
                        valid -= 1
                    window[s[left]] -= 1
                    left += 1
                left += 1
            else:
                window[s[right]] = window.get(s[right], 0) + 1
                if window[s[right]] == need[s[right]]:
                    valid += 1
                while window[s[right]] > need[s[right]]:
                    if window[s[left]] == need[s[left]]:
                        valid -= 1
                    window[s[left]] -= 1
                    left += 1
                if valid == len(need):
                    res.append(left)
            right += 1
        return res

KMP算法

KMP算法可以使字符串匹配算法用线性复杂度实现。

一个字符串的前缀指包含首字母、不包含尾字母的所有子串,后缀指包含尾字母、不包含首字母的所有子串。KMP算法的关键是求模式串的每个子串的最长相等前后缀的长度,即next数组

利用next数组,文本串的指针不需要回退,就可以实现字符串的匹配。当文本串[i]不匹配模式串[j]时,j跳到next[j-1]的位置继续匹配,就可以进行字符串匹配。

求next数组步骤:

  • 初始化,前缀末尾位置j,后缀末尾位置i
  • 前后缀末尾不同情况,不断向前移动前缀末尾位置,j=next[j-1]
  • 前后缀末尾相同情况,向后移动前缀末尾位置,j++
  • 更新后缀末尾位置i对应的next[i]=j

 LeetCode28题 找出字符串中第一个匹配的下标

class Solution:
    def strStr(self, haystack: str, needle: str) -> int:
        next_list = self.getNext(needle)
        j = 0
        for i in range(len(haystack)):
            while j > 0 and haystack[i] != needle[j]:
                j = next_list[j - 1]
            if haystack[i] == needle[j]:
                j += 1
            if j == len(needle):
                return i - len(needle) + 1
        return -1
    
    def getNext(self, s):
        next_list = [0] * len(s)
        j = 0
        for i in range(1, len(s)):
            while j > 0 and s[j] != s[i]:
                j = next_list[j - 1]
            if s[j] == s[i]:
                j += 1
            next_list[i] = j
        return next_list

LeetCode459题 重复的子字符串

移动匹配法

令t=s[1:] + s[:-1],若t包含s,则说s由重复子字符串组成

证明:令t包含s的起点索引为i,则s=t[i:n+i]=s[i:n] + s[0:i],这说明如果t包含s,则s是一个可旋转的字符串,即将s的前i个字符保持顺序,移动到s的末尾,得到的新字符串与s相同。由此我们可以推导出对任意的未越界的索引j,s[j]=s[j+i]=s[i+2*i]=...=s[j+k*i],说明s是由重复的字符串组成的

class Solution:
    def repeatedSubstringPattern(self, s: str) -> bool:
        t = s[1:] + s[:-1]
        next_list = self.getNext(s)
        j = 0
        for i in range(len(t)):
            while j > 0 and s[j] != t[i]:
                j = next_list[j - 1]
            if s[j] == t[i]:
                j += 1
            if j == len(s):
                return True
        return False

    def getNext(self, s):
        next_list = [0] * len(s)
        j = 0
        for i in range(1, len(s)):
            while j > 0 and s[j] != s[i]:
                j = next_list[j - 1]
            if s[j] == s[i]:
                j += 1
            next_list[i] = j
        return next_list

KMP解法(利用最长相等前后缀的性质)

如果字符串s的最长相等前后缀剩下的部分的长度可以被len(s)整除,则说明s是由重复的字符串组成的

class Solution:
    def repeatedSubstringPattern(self, s: str) -> bool:
        t = s[1:] + s[:-1]
        next_list = self.getNext(s)
        j = 0
        if next_list[-1] != 0 and len(s) % (len(s) - next_list[-1]) == 0:
            return True
        return False

    def getNext(self, s):
        next_list = [0] * len(s)
        j = 0
        for i in range(1, len(s)):
            while j > 0 and s[j] != s[i]:
                j = next_list[j - 1]
            if s[j] == s[i]:
                j += 1
            next_list[i] = j
        return next_list

  • 24
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值