• [总结]字符串


    字符串类型的题目一般与哈希,栈,双指针,DP紧密相关。理解了这几个知识与结构,字符串类型的题目一般也可以迎刃而解。另外,针对字符串特有的KMP算法以及Manacher算法的变形题也是常见题,需要掌握。

    哈希

    [leetcode]318. Maximum Product of Word Lengths

    难点是怎么判断两个字符串是否不含有相同的字符。可以用一个int32位(小写字母只有26个),后26位用来表示对应的字符是否存在。最后两个int相与,如果结果为0,说明两个对应的字符串没有相同的字符。

    class Solution:
        def maxProduct(self, words: List[str]) -> int:
            res = 0
            d = collections.defaultdict(int)
            N = len(words)
            for i in range(N):
                w = words[i]
                for c in w:
                    d[w] |= 1 << (ord(c) - ord('a'))
                for j in range(i):
                    if not d[words[j]] & d[words[i]]:
                    # if not set(list(words[j])) & set(list(words[i])):       #Time Limit Exceeded
                        res = max(res, len(words[j]) * len(words[i]))
            return res
    
    [leetcode]336. Palindrome Pairs

    同样的,难点在于如何判断两个数是否能组成回文串。由于这里要求两个字符串的索引,可以使用hashmap存储字符串:索引形式,每次判断的时候只需要判断与该字符配对的回文串是否在hashmap中即可。

    class Solution:
        def palindromePairs(self, words: List[str]) -> List[List[int]]:
            dic = {w : i for i, w in enumerate(words)}
    
            def isPalindrome(word):
                return word == word[::-1]
    
            res = set()
            for idx, word in enumerate(words):
                if word and isPalindrome(word) and "" in dic:
                    nidx = dic[""]
                    res.add((idx, nidx))
                    res.add((nidx, idx))
    
                rword = word[::-1]
                if word and not isPalindrome(word) and rword in dic:
    
                    nidx = dic[rword]
                    res.add((idx, nidx))
                    res.add((nidx, idx))
    
                for x in range(1, len(word)):
                    left, right = word[:x], word[x:]
                    rleft, rright = left[::-1], right[::-1]
                    if isPalindrome(left) and rright in dic:
                        res.add((dic[rright], idx))
                    if isPalindrome(right) and rleft in dic:
                        res.add((idx, dic[rleft]))
            return list(res)
    
    

    使用额外空间降低时间复杂度是常见的套路了。一般在python中可以使用dict或者set结构,如果上题只需要求出对应字符串,用set就可以了。

    双指针

    [leetcode]3.Longest Substring Without Repeating Characters

    动态规划和双指针的思想。首先定义两个指针变量,用来控制输入串的子串的头和尾,设置一个dict存储读入的字符位置。头部指针不动,尾部指针向后移动,如果尾指针指向的字符没有出现在dict中,则加入,否则找到dict的值。由于遍历时顺序的遍历,头指针直接指到与尾指针重复的字符的后一个,即dict的值+1,这一步操作非常重要。重复3的步骤即可直到末尾,记得注意更新max长度,需要加入就更新,因为跳出循环不一定是重新重复步骤也可以是到字符串末尾。

    class Solution:
        def lengthOfLongestSubstring(self, s):
            start = maxLength = 0
            usedChar = {}
    
            for i in range(len(s)):
                if s[i] in usedChar and start <= usedChar[s[i]]:   #起始指针在重复字符之前才会更新
                    start = usedChar[s[i]] + 1
                maxLength = max(maxLength, i - start + 1)
                usedChar[s[i]] = i
    
            return maxLength
    
    [leetcode]76. Minimum Window Substring

    滑动窗口的思想。一个right指针遍历s,每次遇到t中的字符,在map中减少一个,同时用一个count做统计,当t中所有字符被遍历的时候,做一次统计,并且将left指针移动,直到count != t.length() ,相当于一个窗口在s字符串上配合map表动态滑动。

    class Solution(object):
        def minWindow(self, s, t):
            need, missing = collections.Counter(t), len(t)
            i = I = J = 0
            for j, c in enumerate(s, 1):
                missing -= need[c] > 0
                need[c] -= 1
                if not missing:
                    while need[s[i]] < 0:
                        need[s[i]] += 1
                        i += 1
                    if not J or j - i <= J - I:
                        I, J = i, j
            return s[I:J]
    
    [leetcode]424. Longest Repeating Character Replacement

    滑动窗口的思想。两个指针start和end,end向后移动,每一次都找出start到end这个窗口内出现次数最多的字符,那么这个窗口内就应该把其余的字符改成这个字符。需要更改的数目是end - start + 1 - maxCount。如果说数目大于k,那么需要start向后移动。

    class Solution:
        def characterReplacement(self, s: str, k: int) -> int:
            res = start = end = max_cur = 0
            count = collections.defaultdict(int)
            while end < len(s):
                # end_ind = ord(s[end])-ord('A')
                count[s[end]] += 1
                max_cur = max(max_cur,count[s[end]])
                if end-start+1-max_cur > k:
                    count[s[start]]-=1
                    start += 1
                res = max(res,end-start+1)
                end +=1
            return res
    
    [leetcode]438.Find All Anagrams in a String

    滑动窗口的思想。end对每个路过的字符-1,begin对每个字符+1,这样begin和end中间的字符信息就记录在字典中了,字典中的值表示当前子串还需要几个对应的字符(负数表示不需要)和p匹配。同时用count记录当前串是否完成匹配,count主要是记录字典的统计信息的,这样就不用去遍历字典检查信息了。

    import collections
    class Solution:
        def findAnagrams(self, s: str, p: str) -> List[int]:
            start, end = 0, 0
            missing,need = len(p),collections.Counter(p)
            res = []
            while end < len(s):
                if need[s[end]] > 0:
                    missing -= 1
                need[s[end]] -= 1
                end += 1
    
                #匹配成功
                if missing == 0:
                    res.append(start)
    
                #字串长度和p相等,begin向前移动
                if end - start == len(p):
                    # start向前移动
                    if need[s[start]] >= 0:
                        missing += 1
                    need[s[start]] += 1
                    start += 1
            return res
    
    
    [leetcode]567. Permutation in String

    几乎是上一题的简化题。我们需要考虑s2中是否包含s1中同样个数的字符,并且这些字符是连在一起。因此,我们可以使用一个滑动窗口,在s2上滑动。在这个滑动窗口中的字符及其个数是否刚好等于s1中的字符及其个数,此外滑动窗口保证了这些字符是连在一起的。

    import collections
    class Solution:
        def checkInclusion(self, s1: str, s2: str) -> bool:
            start, end = 0, 0
            missing,need = len(s1),collections.Counter(s1)
            while end < len(s2):
                if need[s2[end]] > 0:
                    missing -= 1
                need[s2[end]] -= 1
                end += 1
    
                #匹配成功
                if missing == 0:
                    return True
    
                #字串长度和p相等,begin向前移动
                if end - start == len(s1):
                    # start向前移动
                    if need[s2[start]] >= 0:
                        missing += 1
                    need[s2[start]] += 1
                    start += 1
            return False
    

    字符串加双指针组成滑动窗口是常规的解题手段,配合哈希更佳。

    [leetcode]402. Remove K Digits

    使用一个栈作为辅助,遍历数字字符串,当当前的字符比栈最后的字符小的时候,说明要把栈的最后的这个字符删除掉。为什么呢?你想,把栈最后的字符删除掉,然后用现在的字符进行替换,是不是数字比以前的那种情况更小了?所以同样的道理,做一个while循环,在每一个数字处理的时候,都要做一个循环,使得栈里面最后的数字比当前数字大的都弹出去。最后,如果K还没用完,那要删除哪里的字符呢?毋庸置疑肯定是最后的字符,因为前面的字符都是小字符。

    class Solution:
        def removeKdigits(self, num: str, k: int) -> str:
            if len(num) == k:
                return '0'
            stack = []
            for n in num:
                while stack and k and int(stack[-1]) > int(n):
                    stack.pop()
                    k -= 1
                stack.append(n)
            while k:
                stack.pop()
                k -= 1
            return str(int("".join(stack)))
    
    [leetcode]316. Remove Duplicate Letters

    需要借助一个栈来实现字符串构造的操作。具体操作如下:
    从输入字符串中逐个读取字符c,并把c的字符统计减一。
    如果当前字符c已经在栈里面出现,那么跳过。
    如果当前字符c在栈里面,那么:

    • 如果当前字符c小于栈顶,并且栈顶元素有剩余(后面还能再添加进来),则出栈栈顶,标记栈顶不在栈中。重复该操作直到栈顶元素不满足条件或者栈为空。
    • 入栈字符c,并且标记c已经在栈中。
    class Solution:
        def removeDuplicateLetters(self, s: str) -> str:
            count = collections.Counter(s)
            stack = []
            visited = collections.defaultdict(bool)
            for c in s:
                count[c] -= 1
                if visited[c]:
                    continue
                while stack and count[stack[-1]] and c<stack[-1] :
                    visited[stack[-1]] = False
                    stack.pop()
                visited[c] = True
                stack.append(c)
            return "".join(stack)
    

    这是一道hard题。解题的精髓在于设置visited哈希表,与常规的记录字符出现的个数不同,visited记录一个char是否出现过。

    分治

    [leetcode]395. Longest Substring with At Least K Repeating Characters

    分治的思想。如果字符串s的长度少于k,那么一定不存在满足题意的子字符串,返回0;如果一个字符在s中出现的次数少于k次,那么所有的包含这个字符的子字符串都不能满足题意。所以,应该去不包含这个字符的子字符串继续寻找。这就是分而治之的思路,返回不同子串的长度最大值。如果s中的每个字符出现的次数都大于k次,那么s就是我们要求的字符串。

    class Solution:
        def longestSubstring(self, s: str, k: int) -> int:
            if len(s) < k:
                return 0
            for c in set(s):
                if s.count(c) < k:
                    return max([self.longestSubstring(t, k) for t in s.split(c)])
            return len(s)
    
  • 相关阅读:
    10.并发包阻塞队列之ArrayBlockingQueue
    【PHP】最详细PHP从入门到精通(三)——PHP中的数组
    新随笔222
    新随笔
    iOS常用加密算法介绍和代码实践
    5.在MVC中使用泛型仓储模式和工作单元来进行增删查改
    Directx11学习笔记【二十】 使用DirectX Tool Kit加载mesh
    什么是Dagger2
    嗯嗯

  • 原文地址:https://www.cnblogs.com/hellojamest/p/11677929.html
Copyright © 2020-2023  润新知