To Dark Mode
Featured Images
Photo by Nick Fewings on Unsplash

String

Zhenghao Wu

Wednesday, February 9, 2022 5 min read

Status: In Progress

Post Details

This post is part 1 of 11 in the algorithm series.

View all articles in this series
  1. Array
  2. Binary
  3. Dynamic Programming
  4. Graph
  5. Heap
  6. Interval
  7. Linked List
  8. Matrix
  9. Misc
  10. String (current)
  11. Tree
Table of Contents

3. 无重复字符的最长子串

https://leetcode-cn.com/problems/longest-substring-without-repeating-characters

给定一个字符串 s ,请你找出其中不含有重复字符的最长子串长度

这个得出的字符串中不能存在存在的字符,且返回只需要返回最长的长度,所以我们可以使用双指针,来指向不含重复字符的字串。其中:

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        appear = dict()
        l = 0
        longest = ""
        for c in range(len(s)):
            if s[c] in appear:
                if appear[s[c]] >= l:  # index not allow go backward
                    l = appear[s[c]] + 1
                appear[s[c]] = c  # update index
            else:
                appear[s[c]] = c
            if len(longest) < c+1-l:  # Update string length
                longest = s[l:c+1]
        return len(longest)

这种方法,主要的时间复杂度在右指针的遍历,因为右指针只遍历字符串一次,算法时间复杂度为 $\mathcal{O}(n)$, $n$ 为字符串长度。

5. 最长回文子串

https://leetcode-cn.com/problems/longest-palindromic-substring

回文串指从正序与倒序字串内容一致。

首先能想到一个最简单的方法是中心扩散:遍历每个字符,从每个字符的位置开始,向两侧扩散并判断字符是否一致,直到遇到不匹配。

其中,回文长度为单数和双数的情况需要分别处理。拿到回文区段后,判断长度是否是最长。

class Solution:
    def longestPalindrome(self, s: str) -> str:
        start, end = 0, 0
        for i in range(len(s)):
            l1, r1 = i, i
            while l1 >= 0 and r1 < len(s) and s[l1] == s[r1]:
                l1 -= 1
                r1 += 1
            l1, r1 = l1 + 1, r1 - 1
            l2, r2 = i, i+1
            while l2 >= 0 and r2 < len(s) and s[l2] == s[r2]:
                l2 -= 1
                r2 += 1
            l2, r2 = l2 + 1, r2 - 1
            if r1 - l1 > end - start:
                start, end = l1, r1
            if r2 - l2 > end - start:
                start, end = l2, r2
        return s[start:end + 1]

时间复杂度为 $\mathcal{O}(n^2)$。

除了暴力解法,还有许多其他的方法,但是时间复杂度上都没有明显的优势。有一个专门针对回文字串的算法 Manacher’s Algorithm马拉车算法),将时间复杂度降低到了 $\mathcal{O}(n)$,这里只讨论算法思想。

算法的做法是,维护数组 $P[]$ 用来记录字符串在 $i$ 位置为中心,存在的回文子串的个数,这个数其实也就是回文子串的“半径”。构建这个数组的方法可以根据中心扩散法的基础上修改。但是很明显,这样时间复杂度并没有下降。

Manacher 算法,充分利用了对称性,将寻找字符回文串个数的时间降到了线性。

设想一个字符串内容为 cbacabac, 我们记录这样一个结构。其中 T 代表的是原字符串。

   0  1  2  3  4  5  6  7  8
T  a  b  a  b  a  b  a  b  c
P  0  1  2  3  3  3  1  0  0

如位置 4 的字符 a,符合条件的子串就包括 babababa,和 bababab,则 $P[4]=3$。

如果现在要获得位置 6 字符的 $P$,经过观察会发现,因为已知位置 4 的 $P$,位置 6 字符包含在位置 4 的回文串中。根据镜面对称的特点,对应的,位置 2 在位置 4 字符的回文串中,所能拓展的范围 (a) 与位置 6 字符在相同回文串区间 (+) 内的扩展的范围 (b) 一致(例子中为 1-35-7)。

   0  1  2  3  4  5  6  7  8
T  a  b  a  b  a  b  a  b  c
P  0  1  2  3  3  3  1  0  0
      -------     -------
         a           b
      ~~~~~~~~~~~~~~~~~~~
               +

如果位置 2 的回文串在 1-3 区间就结束了,那么位置 6 的回文串在 5-7 区间也会结束。如果没有结束,则会给位置 6 的中心扩散提供信息:字符串范围的下限5-7 (至少两侧有一对回文字符)。

通过这种对称性,我们避免了不必要的重复搜索。上面的方法,在单双数长度的回文串的情况下,会有不同的结果,这时候会使用在字符间插入一个特殊字符(不存在原题中使用的字符集的字符,在英文字母的情况下,可以用 #)。这样所有的字符串都可以视为单数长度的字符串。还有一些细节,可见题解 详细通俗的思路分析,多解法-windliang

17. 电话号码的字母组合

https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number

题目根据给出的数字组合,对应九宫格输入法的数字英文对照。输出所有可能的字符组合。其中一种方式就是维护一个映射表,然后循环的将所有的组合输出。

class Solution:
    def letterCombinations(self, digits: str) -> List[str]:
        mapping = {
            '2': ['a', 'b', 'c'],
            '3': ['d', 'e', 'f'],
            '4': ['g', 'h', 'i'],
            '5': ['j', 'k', 'l'],
            '6': ['m', 'n', 'o'],
            '7': ['p', 'q', 'r', 's'],
            '8': ['t', 'u', 'v'],
            '9': ['w', 'x', 'y', 'z']
            }
        if digits == '':
            return []
        ans = ['']
        for num in digits:
            ans = [ec + ac for ec in ans for ac in mapping[num]]
        return ans

其中, ans = [ec + ac for ec in ans for ac in mapping[num]] 可以展开:

for num in digits:
    new_ans = list()
    for ec in ans:
        for ac in mapping[num]:
            new_ans.append(ec + ac)
    ans = new_ans

时间复杂度为 $\mathcal{O}(3^m\times 4^n)$,其中 $m$为映射中有三个字母的数字个数,$m$为映射中有四个字母的数字个数。

20. 有效的括号

https://leetcode-cn.com/problems/valid-parentheses

比较经典的利用栈的题目,遇到左括弧则推入栈中,遇到又括弧则抛出栈内元素进行比对。因为栈这一数据结构是先进先出(FIFO),则满足情况的阔号串满足:遍历完字符串后所有的堆栈为空。阔号的对则使用字典进行储存,方便进行压栈和抛出时比对。

其中不满足的情况:

  1. 抛出的阔号对不匹配:直接返回 False
  2. 左括号数量大于右括号数量:栈长度不为0,返回 False
  3. 右括号数量大于左括弧数量:栈已经不存在更多元素,继续抛出栈顶元素会出错。

针对第三个问题,使用到一个 trick 是,在初始化栈时,塞入一个不为阔号的元素:例如 $,则第三种情况发生时,一定无法匹配,触发第一个条件结束判断并返回结果。但是符合情况的字符串也发生了改变,如果栈的长度为1(只剩下$),则括号串满足条件。

class Solution:
    def isValid(self, s: str) -> bool:
        dic = {'{': '}',  '[': ']', '(': ')', '?': '?'}
        stack = ['?']
        for c in s:
            if c in dic:
                stack.append(c)
            elif dic[stack.pop()] != c:
                return False 
        return len(stack) == 1

遍历一次,时间复杂度为 $O(n)$。

Article Card

For "String"

AuthorZhenghao Wu
Publish & Update Date2022-02-09
TagsPlaceholder

Related Posts