跳至主要內容

字符串

绳子大约 24 分钟数据结构字符串Leetcode

基础

字符串是指一个或多个字符的序列。

字符和数字转换

Python:

a = "1"
aInt = ord(a) - ord("0") # ord returns the ascii code of a char
print(ord(a), ord("0"), aInt) # 49 48 1

b = 1
bStr = str(b) # bStr = "1"
print(b, bStr) # 1 "1"

Golang:

a := '1'
aInt := rune(a) - rune('0') // rune returns the unicode of a char
fmt.Println(rune(a), rune('0'), aInt) // 49 48 1

b := 1
bStr := rune(b) + rune('0')
fmt.Println(b, bStr) // 1 49

Leetcode 编程题

28. 实现 strStr() - 字符串搜索

28. 实现 strStr()open in new window

一、题目

实现 strStr() 函数。

给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回 -1。

二、解析

1)暴力搜索

class Solution:
    def strStr(self, haystack: str, needle: str) -> int:
        if not needle:
            return 0
        if not haystack:
            return -1
        
        for i in range(len(haystack) - len(needle) + 1):
            for j in range(len(needle)):
                if haystack[i + j] != needle[j]:
                    break
            else:
                return i
        
        return -1

2)KMP

参考 小白之KMP算法详解及python实现open in new window

对于一个长度为N的字符串S

  • 前缀为第0个字符S[0]到第j个字符S[j]组成的子串,其中0<=j<=N-1
  • 后缀为第j个字符S[j]到第N-1个字符S[N-1]组成的子串,其中0<=j<=N-1
  • 真前(后)缀表示不包括字符串本身的前(后)缀。

对于字符串abcab

  • 前缀包括a,ab,abc,abca,abcab,后缀包括abcab,bcab,cab,ab,b
  • 真前缀包括a,ab,abc,abca,真后缀包括bcab,cab,ab,b

对于一个长度为N的字符串S,它的 next 指针数组的长度与字符串长度相等,其中第i个元素表示【该字符串第0个字符S[0]到第i个字符S[i]组成的子串】的相同真前后缀的最大长度。

对于字符串abcab,next 指针数组为[0,0,0,1,2]

class Solution:
    def strStr(self, haystack: str, needle: str) -> int:
        def gen_next(string):
            N = len(string)
            next_pointer = [0] * N
            left = 0
            cur = 1
            while cur < N:
                if string[cur] == string[left]:
                    next_pointer[cur] = left + 1
                    left += 1
                    cur += 1
                elif left == 0:
                    next_pointer[cur] = 0
                    cur += 1
                else: ## left != 0
                    left = next_pointer[left - 1]
            return next_pointer

        nextp = gen_next(needle)
        m = len(haystack)
        n = len(needle)
        i, j = 0, 0
        while i < m and j < n:
            if haystack[i] == needle[j]:
                i += 1
                j += 1
            elif j == 0:
                i += 1
            else:
                j = nextp[j - 1]
        if j == n:
            return i - j
        return -1

8. 字符串转换整数 (atoi)

8. 字符串转换整数 (atoi)open in new window

一、题目

请你来实现一个 atoi 函数,使其能将字符串转换成整数。

二、解析

不要使用strip函数。

代码如下:

class Solution:
    def myAtoi(self, string: str) -> int:
        if not string or string[0].isalpha():
            return 0
        
        begin = 0
        res = 0
        sign = 1
        while begin < len(string) and string[begin] == " ":
            begin += 1
        
        if begin >= len(string):
            return 0
        
        if string[begin] == "+":
            begin += 1
        elif string[begin] == "-":
            sign = -1
            begin += 1
        
        while begin < len(string) and string[begin].isdigit():
            res = res * 10 + int(string[begin])
            begin += 1
        
        res = res * sign
        if res > 2 ** 31 - 1:
            return 2 ** 31 - 1
        elif res < -2 ** 31:
            return -2 ** 31
        return res

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

3. 无重复字符的最长子串open in new window

一、题目

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

示例 1:

输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

示例 2:

输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

示例 3:

输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
     请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。

提示:

  • 0 <= s.length <= 5 * 104
  • s由英文字母、数字、符号和空格组成

二、解析

参考 Leetcode-powcaiopen in new window

滑动窗口。使用左右两个指针,右指针每次往右走,如果满足条件(或不满足条件),左指针就一直收敛。

代码如下:

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        if not s: 
            return 0
        
        res = 0
        left = 0
        seen = set()
        for right in range(len(s)):
            while s[right] in seen:
                seen.remove(s[left])
                left += 1
            seen.add(s[right])
            res = max(res, right - left + 1)
        return res

1044. 最长重复子串

1044. 最长重复子串open in new window

一、题目

给你一个字符串 s ,考虑其所有 重复子串 :即 s 的(连续)子串,在 s 中出现 2 次或更多次。这些出现之间可能存在重叠。

返回 任意一个 可能具有最长长度的重复子串。如果 s 不含重复子串,那么答案为 "" 。

示例 1:

输入:s = "banana"
输出:"ana"

示例 2:

输入:s = "abcd"
输出:""

提示:

  • 2 <= s.length <= 3 * 104
  • s 由小写英文字母组成

二、解析

1)暴力法。

[Python] 今天这题太难了我真不会open in new window

代码如下:

class Solution:
    def longestDupSubstring(self, s: str) -> str:
        ans = ""
        for i in range(len(s)):
            length = len(ans) ## at least length
            while s[i:i+length+1] in s[i+1:]:
                ans = s[i:i+length + 1]
                length = len(ans)
        return ans

2)二分法 + Rabin-Karp 字符串编码

官方题解open in new window

二分法用来从判断合适的长度。举个例子,如果存在长度为 5 的子串为最长重复子串,那么我们就考虑长度大于 5 的子串是否有可能,而不需要考虑长度小于 5 的子串了。

Rabin-Karp 字符串编码用来对子串进行编码,用来判断是否有长度为 L 的重复子串。

代码如下:

class Solution:
    def longestDupSubstring(self, s: str) -> str:
        ## 生成两个进制
        a1, a2 = random.randint(26, 100), random.randint(26, 100)
        ## 生成两个模
        mod1, mod2 = random.randint(10**9+7, 2**31-1), random.randint(10**9+7, 2**31-1)
        n = len(s)
        ## 先对所有字符进行编码
        arr = [ord(c)-ord('a') for c in s]
        ## 二分查找的范围是[1, n-1]
        l, r = 0, n
        length, start = 0, -1
        while l < r:
            m = l + (r - l) // 2
            idx = self.check(arr, m, a1, a2, mod1, mod2)
            ## 有重复子串,移动左边界
            if idx != -1:
                l = m + 1
                length = m
                start = idx
            ## 无重复子串,移动右边界
            else:
                r = m
        return s[start:start+length] if start != -1 else ""

    def check(self, arr, m, a1, a2, mod1, mod2):
        n = len(arr)
        aL1, aL2 = pow(a1, m) % mod1, pow(a2, m) % mod2
        h1, h2 = 0, 0
        for i in range(m):
            h1 = (h1 * a1 + arr[i]) % mod1
            h2 = (h2 * a2 + arr[i]) % mod2
        ## 存储一个编码组合是否出现过
        seen = {(h1, h2)}
        for start in range(1, n - m + 1):
            h1 = (h1 * a1 - arr[start - 1] * aL1 + arr[start + m - 1]) % mod1
            h2 = (h2 * a2 - arr[start - 1] * aL2 + arr[start + m - 1]) % mod2
            ## 如果重复,则返回重复串的起点
            if (h1, h2) in seen:
                return start
            seen.add((h1, h2))
        ## 没有重复,则返回-1
        return -1

复杂度分析:

  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(n)

76. 最小覆盖子串

76. 最小覆盖子串open in new window

一、题目

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。

注意:

  • 对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
  • 如果 s 中存在这样的子串,我们保证它是唯一的答案。

示例 1:

输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"
解释:最小覆盖子串 "BANC" 包含来自字符串 t 的 'A'、'B' 和 'C'。

示例 2:

输入:s = "a", t = "a"
输出:"a"
解释:整个字符串 s 是最小覆盖子串。

示例 3:

输入: s = "a", t = "aa"
输出: ""
解释: t 中两个字符 'a' 均应包含在 s 的子串中,
因此没有符合条件的子字符串,返回空字符串。

二、解析

官方题解open in new window

滑动窗口。使用左右两个指针,右指针每次往右走,如果满足条件(或不满足条件),左指针就一直收敛。

在滑动窗口类型的问题中都会有两个指针,一个用于「延伸」现有窗口的 right 指针,和一个用于「收缩」窗口的 left 指针。在任意时刻,只有一个指针运动,而另一个保持静止。我们在 s 上滑动窗口,通过移动 right 指针不断扩张窗口。当窗口包含全部所需的字符后,如果能收缩,我们就收缩窗口直到得到最小窗口。

Python 代码如下:

class Solution:
    def minWindow(self, s: str, t: str) -> str:
        freq = {}
        for c in t:
            if c not in freq:
                freq[c] = 0
            freq[c] += 1

        sLen = len(s)
        maxLen = float("inf")
        res_left = res_right = -1
        counts = {}

        def check():
            for k in freq:
                if k not in counts or counts[k] < freq[k]:
                    return False
            return True

        left, right = 0, 0
        while right < sLen:
            right_char = s[right]
            if right_char in freq and freq[right_char] > 0:
                if right_char not in counts:
                    counts[right_char] = 0
                counts[right_char] += 1
            while check() and left <= right:
                if right - left + 1 < maxLen:
                    maxLen = right - left + 1
                    res_left, res_right = left, left + maxLen
                if s[left] in freq:
                    counts[s[left]] -= 1
                left += 1
            right += 1
        return s[res_left:res_right]

Golang 代码如下:

func minWindow(s string, t string) string {
    ori, cnt := map[byte]int{}, map[byte]int{}
    for i := 0; i < len(t); i++ {
        ori[t[i]]++
    }

    sLen := len(s)
    len := math.MaxInt32
    ansL, ansR := -1, -1

    check := func() bool {
        for k, v := range ori {
            if cnt[k] < v {
                return false
            }
        }
        return true
    }
    for l, r := 0, 0; r < sLen; r++ {
        if ori[s[r]] > 0 {
            cnt[s[r]]++
        }
        for check() && l <= r {
            if (r - l + 1 < len) {
                len = r - l + 1
                ansL, ansR = l, l + len
            }
            if _, ok := ori[s[l]]; ok {
                cnt[s[l]] -= 1
            }
            l++
        }
    }
    if ansL == -1 {
        return ""
    }
    return s[ansL:ansR]
}

316. 去除重复字母

316. 去除重复字母open in new window

一、题目

给你一个仅包含小写字母的字符串,请你去除字符串中重复的字母,使得每个字母只出现一次。需保证返回结果的字典序最小(要求不能打乱其他字符的相对位置)。

示例 1:

输入: "bcabc"
输出: "abc"

二、解析

参考 Leetcode官方题解open in new window

使用栈和哈希表。 栈里面保存中间结果,哈希表保存每个字母最后一次出现的位置。

代码如下:

class Solution:
    def removeDuplicateLetters(self, s) -> int:
        stack = []
        seen = set()
        last_occurrence = {c: i for i, c in enumerate(s)}
        for i, c in enumerate(s):
            if c not in seen:
                while stack and c < stack[-1] and i < last_occurrence[stack[-1]]:
                    seen.discard(stack.pop())
                seen.add(c)
                stack.append(c)
        return ''.join(stack)

1081. 不同字符的最小子序列

1081. 不同字符的最小子序列open in new window

和【316. 去除重复字母】相同。

12. 整数转罗马数字

12. 整数转罗马数字open in new window

一、题目

给定一个整数,将其转为罗马数字。输入确保在 1 到 3999 的范围内。

二、解析

代码如下:

class Solution:
    def intToRoman(self, num: int) -> str:
        roman_numbers = {1:'I', 4:'IV', 5:'V', 9:'IX',
                         10:'X', 40: 'XL', 50:'L', 90:'XC', 
                         100:'C', 400:'CD', 500:'D', 900:'CM', 
                         1000:'M'}
        res = []
        for d in sorted(roman_numbers.keys(), reverse=True):
            (r, num) = divmod(num, d)
            if r == 0:
                continue
            res.append(roman_numbers[d]*r)
        return ''.join(res)

13. 罗马数字转整数

13. 罗马数字转整数open in new window

一、题目

罗马数字包含以下七种字符:IVXLCDM。 给定一个罗马数字,将其转换成整数。输入确保在 1 到 3999 的范围内。

二、解析

代码如下:

class Solution:
    def romanToInt(self, s: str) -> int:
        roman_map = {'I': 1, 'V': 5, 'X': 10, 'L': 50, 'C': 100, 'D': 500, 'M': 1000}
        res = 0
        for i in range(len(s)):
            if i < len(s) - 1 and roman_map[s[i]] < roman_map[s[i + 1]]:
                res -= roman_map[s[i]]
            else:
                res += roman_map[s[i]]    
        return res

556. 下一个更大元素 III

556. 下一个更大元素 IIIopen in new window

一、题目

给定一个32位正整数 n,你需要找到最小的 32 位整数,其与 n 中存在的位数完全相同,并且其值大于n。如果不存在这样的32位整数,则返回-1。

二、解析

把数字转成字符数组,按字母表的顺序找到可以交换的位置。

代码如下:

class Solution:
    def nextGreaterElement(self, n: int) -> int:
        chars = list(str(n))
        length = len(chars)
        right = length - 2
        while right >= 0 and chars[right] >= chars[right + 1]:
            right -= 1
        if right < 0:
            return -1
        ## 在left右边,找到最后一个大于chars[left]的数字
        left = right
        right = right + 1
        while right < length and chars[right] > chars[left]:
            right += 1
        chars[left], chars[right - 1] = chars[right - 1], chars[left]
        ## left右边的数组进行翻转
        begin = left + 1
        end = length - 1
        while begin < end:
            chars[begin], chars[end] = chars[end], chars[begin]
            begin += 1
            end -= 1
        ## 计算
        res = 0
        for ch in chars:
            res = res * 10 + int(ch)
        return res if res < 2 ** 31 - 1 else -1

556-1. 上一个更小元素

注意:Leetcode 没有这道题。

一、题目

给定一个32位正整数 n,你需要找到最大的32位整数,其与 n 中存在的位数完全相同,并且其值小于n。如果不存在这样的32位整数,则返回-1。

二、解析

把数字转成字符数组,按字母表的顺序找到可以交换的位置。

class Solution:
    def lastSmallerElement(self, n: int) -> int:
        chars = list(str(n))
        length = len(chars)
        right = length - 2
        while right >= 0 and chars[right] <= chars[right + 1]:
            right -= 1
        if right < 0:
            return -1
        ## 在left右边,找到最后一个小于chars[left]的数字
        left = right
        right = right + 1
        while right < length and chars[right] < chars[left]:
            right += 1
        chars[left], chars[right - 1] = chars[right - 1], chars[left]
        ## 右边的数组进行翻转
        begin = left + 1
        end = length - 1
        while begin < end:
            chars[begin], chars[end] = chars[end], chars[begin]
            begin += 1
            end -= 1
        if chars[0] == "0":
            return -1
        res = 0
        for ch in chars:
            res = res * 10 + int(ch)
        return res

224. 基本计算器

224. 基本计算器open in new window

一、题目

给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。

注意:不允许使用任何将字符串作为数学表达式计算的内置函数,比如 eval()

示例 1:

输入:s = "1 + 1"
输出:2

示例 2:

输入:s = " 2-1 + 2 "
输出:3

示例 3:

输入:s = "(1+(4+5+2)-3)+(6+8)"
输出:23

二、解析

官方题解open in new window

括号展开 + 栈。

代码如下:

class Solution:
    def calculate(self, s: str) -> int:
        ops = [1]
        sign = 1

        ret = 0
        n = len(s)
        i = 0
        while i < n:
            if s[i] == ' ':
                i += 1
            elif s[i] == '+':
                sign = ops[-1]
                i += 1
            elif s[i] == '-':
                sign = -ops[-1]
                i += 1
            elif s[i] == '(':
                ops.append(sign)
                i += 1
            elif s[i] == ')':
                ops.pop()
                i += 1
            else:
                num = 0
                while i < n and s[i].isdigit():
                    num = num * 10 + ord(s[i]) - ord('0')
                    i += 1
                ret += num * sign
        return ret

227. 基本计算器 II

227. 基本计算器 IIopen in new window

一、题目

给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。

整数除法仅保留整数部分。

你可以假设给定的表达式总是有效的。所有中间结果将在 [-231, 231 - 1] 的范围内。

注意:不允许使用任何将字符串作为数学表达式计算的内置函数,比如 eval() 。

示例 1:

输入:s = "3+2*2"
输出:7

示例 2:

输入:s = " 3/2 "
输出:1

示例 3:

输入:s = " 3+5 / 2 "
输出:5

二、解析

代码如下:

class Solution:
    def calculate(self, s: str) -> int:
        res = 0
        sign = '+'
        digit = 0
        nums = []
        s = s.strip()
        for i in range(len(s)):
            if s[i] == ' ': 
                continue
            if s[i].isdigit(): 
                digit = digit * 10 + int(s[i])
            if not s[i].isdigit() or i == len(s) - 1:
                if sign == '+':
                    nums.append(digit)
                elif sign == '-':
                    nums.append(-digit)
                elif sign == '*':
                    tmp = nums[-1] * digit
                    nums[-1] = tmp
                elif sign == '/':
                    ## print(nums, digit)
                    tmp = abs(nums[-1]) // digit
                    if nums[-1] < 0:
                        tmp = -tmp
                    nums[-1] = tmp
                digit = 0
                sign = s[i]
                
        return sum(nums)

394. 字符串解码

394. 字符串解码open in new window

一、题目

给定一个经过编码的字符串,返回它解码后的字符串。

编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。

你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。

此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。

示例 1:

输入:s = "3[a]2[bc]"
输出:"aaabcbc"

示例 2:

输入:s = "3[a2[c]]"
输出:"accaccacc"

示例 3:

输入:s = "2[abc]3[cd]ef"
输出:"abcabccdcdcdef"

示例 4:

输入:s = "abc3[cd]xyz"
输出:"abccdcdcdxyz"

提示:

  • 0 <= s.length <= 30
  • s 由小写英文字母、数字和方括号 '[]' 组成
  • s 保证是一个 有效 的输入。
  • s 中所有整数的取值范围为 [1, 300]

二、解析

https://leetcode.cn/problems/decode-string/solution/decode-string-fu-zhu-zhan-fa-di-gui-fa-by-jyd/open in new window

1)使用栈。

构建辅助栈 stack, 遍历字符串 s 中每个字符 c;

  • 当 c 为数字时,将数字字符转化为数字 multi,用于后续倍数计算;
  • 当 c 为字母时,在 res 尾部添加 c;
  • 当 c 为 [ 时,将当前 multi 和 res 入栈,并分别置空置 0:
    • 记录此 [ 前的临时结果 res 至栈,用于发现对应 ] 后的拼接操作;
    • 记录此 [ 前的倍数 multi 至栈,用于发现对应 ] 后,获取 multi × [...] 字符串。
    • 进入到新 [ 后,res 和 multi 重新记录。
  • 当 c 为 ] 时,stack 出栈,拼接字符串 res = last_res + cur_multi * res,其中:
    • last_res是上个 [ 到当前 [ 的字符串,例如 "3[a2[c]]" 中的 a;
    • cur_multi是当前 [ 到 ] 内字符串的重复倍数,例如 "3[a2[c]]" 中的 2。

代码如下:

class Solution:
    def decodeString(self, s: str) -> str:
        stack, res, multi = [], "", 0
        for c in s:
            if c == '[':
                stack.append([multi, res])
                res, multi = "", 0
            elif c == ']':
                cur_multi, last_res = stack.pop()
                res = last_res + cur_multi * res
            elif '0' <= c <= '9':
                multi = multi * 10 + int(c)            
            else:
                res += c
        return res

2)递归。

代码如下:

class Solution:
    def decodeString(self, s: str) -> str:
        def dfs(s, i):
            res, multi = "", 0
            while i < len(s):
                if '0' <= s[i] <= '9':
                    multi = multi * 10 + int(s[i])
                elif s[i] == '[':
                    i, tmp = dfs(s, i + 1)
                    res += multi * tmp
                    multi = 0
                elif s[i] == ']':
                    return i, res
                else:
                    res += s[i]
                i += 1
            return res
        return dfs(s,0)

443. 压缩字符串

443. 压缩字符串open in new window

一、题目

给你一个字符数组 chars ,请使用下述算法压缩:

从一个空字符串 s 开始。对于 chars 中的每组 连续重复字符 :

  • 如果这一组长度为 1 ,则将字符追加到 s 中。
  • 否则,需要向 s 追加字符,后跟这一组的长度。

压缩后得到的字符串 s 不应该直接返回 ,需要转储到字符数组 chars 中。需要注意的是,如果组长度为 10 或 10 以上,则在 chars 数组中会被拆分为多个字符。

请在 修改完输入数组后 ,返回该数组的新长度。

你必须设计并实现一个只使用常量额外空间的算法来解决此问题。

示例1:

输入:chars = ["a","a","b","b","c","c","c"]
输出:返回 6 ,输入数组的前 6 个字符应该是:["a","2","b","2","c","3"]
解释:"aa" 被 "a2" 替代。"bb" 被 "b2" 替代。"ccc" 被 "c3" 替代。

示例2:

输入:chars = ["a"]
输出:返回 1 ,输入数组的前 1 个字符应该是:["a"]
解释:唯一的组是“a”,它保持未压缩,因为它是一个字符。

二、解析

参考 Leetcode官方题解open in new window

双指针。我们可以使用双指针分别标志我们在字符串中读和写的位置。每次当读指针 read 移动到某一段连续相同子串的最右侧,我们就在写指针 write 处依次写入该子串对应的字符和子串长度即可

代码如下:

class Solution(object):
    def compress(self, chars):
        anchor = write = 0
        for read, c in enumerate(chars):
            if read + 1 == len(chars) or chars[read + 1] != c:
                chars[write] = chars[anchor]
                write += 1
                if read > anchor:
                    for digit in str(read - anchor + 1):
                        chars[write] = digit
                        write += 1
                anchor = read + 1
        return write

6. Z 字形变换

6. Z 字形变换open in new window

一、题目

将一个给定字符串根据给定的行数,以从上往下、从左到右进行 Z 字形排列。

将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z 字形排列。

比如输入字符串为 "PAYPALISHIRING" 行数为 3 时,排列如下:

P   A   H   N
A P L S I I G
Y   I   R

之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:"PAHNAPLSIIGYIR"

二、解析

设置一个flag变量。

代码如下:

class Solution:
    def convert(self, s: str, numRows: int) -> str:
        if numRows == 1:
            return s
        
        res = [[] for _ in range(numRows)]
        row = 0
        flag = 1
        for c in s:
            res[row].append(c)
            if row == 0:
                flag = 1
            elif row == numRows - 1:
                flag = -1
            row += flag 
        return ''.join(''.join(r) for r in res)

696. 计数二进制子串

696. 计数二进制子串open in new window

一、题目

给定一个字符串s,计算具有相同数量0和1的非空(连续)子字符串的数量,并且这些子字符串中的所有0和所有1都是组合在一起的。 重复出现的子串要计算它们出现的次数。

示例 1:

输入:s = "00110011"
输出:6
解释:6 个子串满足具有相同数量的连续 1 和 0 :"0011"、"01"、"1100"、"10"、"0011" 和 "01" 。
注意,一些重复出现的子串(不同位置)要统计它们出现的次数。
另外,"00110011" 不是有效的子串,因为所有的 0(还有 1 )没有组合在一起。

二、解析

参考 Leetcode官方题解open in new window

1)中心扩展法

class Solution:
    def countBinarySubstrings(self, s: str) -> int:
        count = 0
        for i in range(len(s) - 1):
            if s[i] != s[i + 1]:
                count += 1
                for j in range(1, min(i + 1, len(s) - i - 1)):
                    if not (s[i - j] == s[i] and s[i + 1] == s[i + 1 + j]):
                        break
                    count += 1                    
        return count

2)线性扫描

class Solution(object):
    def countBinarySubstrings(self, s):
        ans, prev, cur = 0, 0, 1
        for i in range(1, len(s)):
            if s[i-1] != s[i]:
                ans += min(prev, cur)
                prev, cur = cur, 1
            else:
                cur += 1
        return ans + min(prev, cur)

1071. 字符串的最大公因子

1071. 字符串的最大公因子open in new window

一、题目

对于字符串ST,只有在S = T + ... + TT与自身连接 1 次或多次)时,我们才认定“T 能除尽 S”。 返回最长字符串X,要求满足X能除尽str1X能除尽str2

二、解析

代码如下:

class Solution:
    def gcdOfStrings(self, str1: str, str2: str) -> str:
        def gcd(a, b):
            if b == 0:
                return a
            return gcd(b, a % b)
        
        if str1 + str2 != str2 + str1:
            return ''
        return str1[:gcd(len(str1), len(str2))]

415. 字符串相加

415. 字符串相加open in new window

一、题目

给定两个字符串形式的非负整数 num1 和num2 ,计算它们的和。

二、解析

代码如下:

class Solution:
    def addStrings(self, num1: str, num2: str) -> str:
        len1 = len(num1)
        len2 = len(num2)
        i = len1 - 1
        j = len2 - 1
        res = []
        carry = 0
        while i >= 0 or j >= 0 or carry != 0:
            d1 = int(num1[i]) if i >= 0 else 0
            d2 = int(num2[j]) if j >= 0 else 0
            temp = d1 + d2 + carry
            carry, temp = divmod(temp, 10)
            res.append(str(temp))
            i -= 1
            j -= 1
        return ''.join(res[::-1])

43. 字符串相乘

43. 字符串相乘open in new window

一、题目

给定两个以字符串形式表示的非负整数 num1 和 num2,返回 num1 和 num2 的乘积,它们的乘积也表示为字符串形式。

二、解析

参考 官方题解open in new window

代码如下:

class Solution:
    def multiply(self, num1: str, num2: str) -> str:
        if num1 == "0" or num2 == "0":
            return "0"
        
        m, n = len(num1), len(num2)
        ansArr = [0] * (m + n)
        for i in range(m - 1, -1, -1):
            x = int(num1[i])
            for j in range(n - 1, -1, -1):
                ansArr[i + j + 1] += x * int(num2[j])
        
        for i in range(m + n - 1, 0, -1):
            ansArr[i - 1] += ansArr[i] // 10
            ansArr[i] %= 10
        
        index = 1 if ansArr[0] == 0 else 0
        return "".join(str(x) for x in ansArr[index:])

424. 替换后的最长重复字符

424. 替换后的最长重复字符open in new window

一、题目

给你一个仅由大写英文字母组成的字符串,你可以将任意位置上的字符替换成另外的字符,总共可最多替换 k 次。在执行上述操作后,找到包含重复字母的最长子串的长度。

示例 1:

输入:s = "ABAB", k = 2
输出:4
解释:用两个'A'替换为两个'B',反之亦然。

二、解析

官方题解open in new window

使用双指针。使用左右两个指针,右指针每次往右走,如果满足条件(或不满足条件),左指针只收敛一次。之前遇到过左指针一直收缩,参考【3. 无重复字符的最长子串】。

通过枚举字符串中的每一个位置作为右端点,然后找到其最远的左端点的位置,满足该区间内除了出现次数最多的那一类字符之外,剩余的字符(即非最长重复字符)数量不超过 k 个。

这样我们可以想到使用双指针维护这些区间,每次右指针右移,如果区间仍然满足条件,那么左指针不移动,否则左指针至多右移一格,保证区间长度不减小。

另外,每次区间右移,我们更新右移位置的字符出现的次数,然后尝试用它更新重复字符出现次数的历史最大值,最后我们使用该最大值计算出区间内非最长重复字符的数量,以此判断左指针是否需要右移即可。

Python 代码如下:

class Solution:
    def characterReplacement(self, s: str, k: int) -> int:
        nums = {c: 0 for c in s}
        n = len(s)
        maxn = 0 # 窗口中曾出现某字母的最大次数
        left = right = 0

        while right < n:
            nums[s[right]] += 1
            maxn = max(maxn, nums[s[right]])
            if right - left + 1 - maxn > k:
                nums[s[left]] -= 1
                left += 1
            right += 1
        return right - left

165. 比较版本号

165. 比较版本号open in new window

一、题目

给你两个版本号 version1version2 ,请你比较它们。

版本号由一个或多个修订号组成,各修订号由一个 '.' 连接。每个修订号由 多位数字 组成,可能包含 前导零 。每个版本号至少包含一个字符。修订号从左到右编号,下标从 0 开始,最左边的修订号下标为 0 ,下一个修订号下标为 1 ,以此类推。例如,2.5.330.1 都是有效的版本号。

示例 1:

输入:version1 = "1.01", version2 = "1.001"
输出:0
解释:忽略前导零,"01" 和 "001" 都表示相同的整数 "1"

二、解析

官方题解open in new window

使用两个指针。

Python 代码如下:

class Solution:
    def compareVersion(self, version1: str, version2: str) -> int:
        n, m = len(version1), len(version2)
        i, j = 0, 0
        while i < n or j < m:
            x = 0
            while i < n and version1[i] != '.':
                x = x * 10 + ord(version1[i]) - ord('0')
                i += 1
            i += 1  # 跳过点号
            y = 0
            while j < m and version2[j] != '.':
                y = y * 10 + ord(version2[j]) - ord('0')
                j += 1
            j += 1  # 跳过点号
            if x != y:
                return 1 if x > y else -1
        return 0

Golang 代码如下:

func compareVersion(version1, version2 string) int {
    n, m := len(version1), len(version2)
    i, j := 0, 0
    for i < n || j < m {
        x := 0
        for ; i < n && version1[i] != '.'; i++ {
            x = x*10 + int(version1[i]-'0')
        }
        i++ // 跳过点号
        y := 0
        for ; j < m && version2[j] != '.'; j++ {
            y = y*10 + int(version2[j]-'0')
        }
        j++ // 跳过点号
        if x > y {
            return 1
        }
        if x < y {
            return -1
        }
    }
    return 0
}

其他编程题

汉字读法的整数转为阿拉伯数字

一、题目

示例:

五千四百零三万一千二百
54031200

二、解析

不完全正确。

代码如下:

def chinese2int(string):
    """string表示的数字小于一亿"""
    digits_map = {"零": 0, "一": 1, "二": 2, "三": 3, "四": 4, "五": 5, 
                  "六": 6, "七": 7, "八": 8, "九": 9, "十": 10}
    unit_map = {"千": 1000, "百": 100, "十": 10, "零": 0}
    if len(string) == 1:
        return digits_map[string[0]]
    num = 0
    i = 0
    N = len(string)
    while i < N:
        if i < N and string[i] == "万":
            num *= 10000
            i += 1
        if i == N - 1:
            num += digits_map[string[i]]
            i += 1
            break
        end = i
        while end < N and string[end] not in unit_map:
            end += 1
        if i == end:
            if string[end] == "零":
                end += 1
                temp = digits_map[string[end]]
                end += 1
                if end < N and string[end] == "十":
                    temp *= 10
                    end += 1
                num += temp
            elif string[end] == "十":
                num += 10
                end += 1
                num += digits_map[string[end]]
                end += 1
        elif end < N:
            num += digits_map[string[i]] * unit_map[string[end]]
            end += 1
        i = end
    return num


if __name__ == "__main__":
    test_cases = [["零", 0],
                  ["一", 1],
                  ["十", 10],
                  ["十一", 11],
                  ["二十", 20],
                  ["一百", 100],
                  ["一百零一", 101],
                  ["一百一十一", 111],
                  ["一千", 1000],
                  ["一千零一", 1001],
                  ["五千零二十万一千二百零五", 50201205]]
    for string, ans in test_cases:
        res = chinese2int(string)
        print(string, ans, res)