面试经典:字符串

1,854 阅读7分钟

字符串

LeetCode中的字符串题目是指需要对字符串进行操作或处理的题目,包括字符串的匹配、替换、排序、翻转、去重、分割等等操作。字符串题目常见的难点在于如何处理字符串的边界条件,如空字符串、只包含空格的字符串、字符串长度为1等特殊情况,以及字符串中的特殊字符(如空格、数字、字母等)的处理。

常见的字符串题目有:

  1. 实现 strStr() 函数:在一个字符串中查找另一个字符串的位置

  2. 反转字符串:将一个字符串反转

  3. 反转字符串中的单词:将一个字符串中的单词顺序反转

  4. 有效的括号:判断一个字符串中的括号是否匹配

  5. 最长公共前缀:查找多个字符串中最长的公共前缀

  6. 翻转字符串里的单词 III:将一个字符串中每个单词反转

  7. 字符串转换整数 (atoi):将一个字符串转换为整数

  8. 无重复字符的最长子串:查找一个字符串中最长的不包含重复字符的子串

这些题目对于掌握字符串的基本操作、边界处理以及算法思想都有很好的练习作用。


151. 反转字符串中的单词 - 力扣(Leetcode)

给你一个字符串 s ,请你反转字符串中 单词 的顺序。

单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。

返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。

注意: 输入字符串 s中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。

 

示例 1:

输入: s = "the sky is blue"
输出: "blue is sky the"

示例 2:

输入: s = "  hello world  "
输出: "world hello"
解释: 反转后的字符串中不能存在前导空格和尾随空格。

示例 3:

输入: s = "a good   example"
输出: "example good a"
解释: 如果两个单词间有多余的空格,反转后的字符串需要将单词间的空格减少到仅有一个。

 

提示:

  • 1 <= s.length <= 104
  • s 包含英文大小写字母、数字和空格 ' '
  • s 中 至少存在一个 单词

 

进阶: 如果字符串在你使用的编程语言中是一种可变数据类型,请尝试使用 O(1) 额外空间复杂度的 原地 解法。

class Solution:
    def reverseWords(self, s: str) -> str:

        s = s.split(' ')
        ans = ''

        for i in range(len(s)-1,-1,-1):
            if s[i]:
                ans += s[i] + ' '

        return ans.strip()

函数的实现方法是先使用 split() 方法将字符串按照空格分割成一个个单词,然后从后往前遍历每个单词,并将非空单词加入答案字符串 ans 中,并在单词之间加上一个空格。最后返回答案字符串时,使用 strip() 方法将首尾多余的空格去掉。

这个函数的时间复杂度为 O(n)O(n),其中 nn 是字符串的长度。空间复杂度取决于单词的个数,因为需要将每个单词存储在一个新的字符串中,所以空间复杂度为 O(k)O(k),其中 kk 是单词的个数。

在 Python 中,字符串是不可变(immutable)的数据类型,这意味着一旦创建了一个字符串对象,就无法修改它的值。如果需要修改一个字符串,需要创建一个新的字符串对象,而不是修改原始的字符串对象。这个特性是由于 Python 中的字符串被视为一系列 Unicode 字符的序列,而这些字符在创建后是不能更改的。如果需要进行字符串的修改操作,可以使用一些字符串操作函数来完成,例如字符串切片、连接、替换等。

所以, 进阶问题我选择不进阶。

14. 最长公共前缀 - 力扣(Leetcode)

编写一个函数来查找字符串数组中的最长公共前缀。

如果不存在公共前缀,返回空字符串 ""

 

示例 1:

输入: strs = ["flower","flow","flight"]
输出: "fl"

示例 2:

输入: strs = ["dog","racecar","car"]
输出: ""
解释: 输入不存在公共前缀。

 

提示:

  • 1 <= strs.length <= 200
  • 0 <= strs[i].length <= 200
  • strs[i] 仅由小写英文字母组成
class Solution:
    def longestCommonPrefix(self, strs: List[str]) -> str:
        ans = ''

        for i in zip(*strs): 
            if len(set(i)) == 1:
                ans += i[0]
            else:
                break

        return ans

这个函数接受一个字符串数组 strs 作为输入,返回一个字符串,表示数组中所有字符串的最长公共前缀。

函数使用了 zip(*strs) 技巧,将字符串数组转换为元组数组,并按列进行迭代。对于每一列中的字符,如果它们都相同,则将其加入到最长公共前缀中,直到出现不同的字符或者所有字符串遍历完毕。最终返回最长公共前缀即可。

需要注意的是,如果输入的字符串数组为空或者其中任意一个字符串为空,则最长公共前缀应该是空字符串。


28. 找出字符串中第一个匹配项的下标 - 力扣(Leetcode)

给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回  -1 ****。

 

示例 1:

输入: haystack = "sadbutsad", needle = "sad"
输出: 0
解释: "sad" 在下标 0 和 6 处匹配。
第一个匹配项的下标是 0 ,所以返回 0 。

示例 2:

输入: haystack = "leetcode", needle = "leeto"
输出: -1
解释: "leeto" 没有在 "leetcode" 中出现,所以返回 -1

 

提示:

  • 1 <= haystack.length, needle.length <= 104
  • haystack 和 needle 仅由小写英文字符组成
class Solution:
    def strStr(self, haystack: str, needle: str) -> int:
        return haystack.index(needle) if needle in haystack else -1

代码实现了如下逻辑:

  1. 判断needle是否存在于haystack中,如果存在,返回needle在haystack中第一次出现的位置,即使用index()函数返回的结果;

  2. 如果needle不存在于haystack中,返回-1。

需要注意的是,当needle不存在于haystack中时,调用index()函数会抛出ValueError异常,因此在代码中使用了if语句进行了异常处理,将其转换为返回-1的结果。

这个算法的时间复杂度是 O(nm),其中 n 是 haystack 字符串的长度,m 是 needle 字符串的长度。这是因为在最坏情况下,需要比较 haystack 字符串中的每个位置和 needle 字符串中的每个位置,才能确定 needle 是否存在于 haystack 中。

空间复杂度是 O(1),因为算法中只使用了常数级别的额外空间。

6. N 字形变换 - 力扣(Leetcode)

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

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

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

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

请你实现这个将字符串进行指定行数变换的函数:

string convert(string s, int numRows);

 

示例 1:

输入: s = "PAYPALISHIRING", numRows = 3
输出: "PAHNAPLSIIGYIR"

示例 2:

输入: s = "PAYPALISHIRING", numRows = 4
输出: "PINALSIGYAHRPI"
解释:
P     I    N
A   L S  I G
Y A   H R
P     I

示例 3:

输入: s = "A", numRows = 1
输出: "A"

 

提示:

  • 1 <= s.length <= 1000
  • s 由英文字母(小写和大写)、',' 和 '.' 组成
  • 1 <= numRows <= 1000
class Solution:
    def convert(self, s: str, numRows: int) -> str:
        if numRows<2: return s
        res = ['' for _ in range(numRows)]
        i = 0
        flag = -1
        for c in s:
            res[i] += c
            if i==0 or i == numRows-1:
                flag = -flag
            i += flag
        return ''.join(res)

这段代码实现了将一个字符串按照锯齿形排列并输出的功能。锯齿形排列的行数由参数numRows给定,每个字符被填充在对应行上,最终从上到下、从左到右依次读出每个字符得到输出结果。

该方法的主要思路是模拟整个锯齿形状的过程,用一个数组res来保存每行的字符,初始时所有行都为空字符串。然后遍历字符串s中的每个字符,将其加入到res中对应的行。i表示当前正在处理的行号,flag表示i的变化方向,当i到达第一行或最后一行时,需要将flag取反,以改变i的增减方向。最后将res数组中的每行拼接起来即可。

这个方法的时间复杂度为O(n),其中n为字符串s的长度,因为只需要遍历一次s就可以得到最终结果。空间复杂度为O(numRows),需要一个数组来保存每行的字符。如果numRows非常大,空间开销会很大,因此需要根据实际情况进行优化。