前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >算法:字符串

算法:字符串

作者头像
用户3578099
发布2022-03-15 08:04:32
2.7K0
发布2022-03-15 08:04:32
举报
文章被收录于专栏:AI科技时讯

字符串

字符串简介

字符串:简称为串,是由零个或多个字符组成的有限序列。一般记为

s=a_1a_2...a_n
  • 字符串名称:字符串定义中的s就是字符串的名称;
  • 字符串的值
a_1a_2...a_n

组成的字符串就是字符串的值,一般用双引号括起来;

  • 字符变量:字符串每一个位置上的元素都是一个字符变量。字符
a_i

可以是字母,数字或者其他字符。i是该字符在字符串中的位置;

  • 字符串的长度:字符串中字符的数目n成为字符串的长度;
  • 空串:零个字符构成的串也称为「空字符串」,它的长度为0,可以用''表示;
  • 子串:字符串中任意个连续的字符组成子序列称为该字符串的「子串」。并且有两种特殊子串,起始于位置为0,长度为k的子串称为「前缀」,而终止于n-1,长度为k的子串称为「后缀」
  • 主串:包含子串的字符串相应的称为「主串」

举个例子来说明一下:

代码语言:javascript
复制
str = "Hello World"

在示例代码中,str是一个字符串的变量名称,hello world则是该字符串的值,字符串的长度为11,该字符串的表示如下图所示:

在这里插入图片描述

根据字符串的特点,我们可以将字符串问题分为以下几种:

  • 字符串匹配问题
  • 子串相关问题
  • 前缀 / 后缀相关问题
  • 回文串相关问题
  • 子序列相关问题

字符串的比较

字符串的比较操作

两个数字之间很容易比较大小,例如 1 < 2。而字符串之间的比较相对来说复杂一点。字符串之间的大小取决于它们按顺序排列字符的前后顺序。比如字符串 str1 = "abc"str2 = "acc", 它们的第一个字母都是 a,而第二个字母,由于字母 b 比字母 c 要靠前,所以 b < c,于是我们可以 说 "abc" < "acd" ,也可以说 str1 < str2。字符串之间的比较是通过组成字符串的字符之间的「字符编码」来决定的。而字符编码指的是字符在对 应字符集中的序号。

而对于两个不相等的字符串,我们可以以下面的规则定义两个字符串的大小:

  • 从两个字符串的第 0 个位置开始,依次比较对应位置上的字符编码大小。
  • 如果 str1[i] 对应的字符编码等于 str2[i] 对应的字符编码,则比较下一位字符。
  • 如果 str1[i] 对应的字符编码小于 str2[i] 对应的字符编码,则说明 str1 < str2。比如:"abc" < "acc"
  • 如果 str1[i] 对应的字符编码大于 str2[i] 对应的字符编码,则说明 str1 > str2。比如:"bcd" > "bad"
  • 如果比较到某一个字符串末尾,另一个字符串仍有剩余:
    • 如果字符串 str1 的长度小于字符串 str2,即 len(str1) < len(str2)。则 str1 < str2。比如:"abc" < "abcde"
    • 如果字符串 str1 的长度大于字符串 str2,即 len(str1) > len(str2)。则 str1 > str2。比如:"abcde" > "abc"
  • 如果两个字符串每一个位置上的字符对应的字符编码都相等,且长度相同,则说明 str1 == str2, 比如:"abcd" == "abcd"

按照上面的规则,我们可以定义一个 strcmp 方法,并且规定:

  • 当 str1 < str2 时,strcmp 方法返回 -1;
  • 当 str1 == str2 时,strcmp 方法返回 0;
  • 当 str1 > str2 时,strcmp 方法返回 1;

python实现如下:

代码语言:javascript
复制
def strcmp(str1, str2):
    index1, index2 = 0, 0
  while index1 < len(str1) and index2 < len(str2):
    if ord(str1[index1]) == ord(str2[index2]):
      index1 += 1
      index2 += 1
    elif ord(str1[index1]) < ord(str2[index2]):
      return -1
       else:
      return 1

  if len(str1) < len(str2):
    return -1
     elif len(str1) > len(str2):
    return 1
     else:
    return 0
字符串的字符编码

以计算机中常用字符使用的 ASCII 编码为例。最早的时候,人们制定了一个包含 127 个字符的编码表 ASCII 到计算机系统中。ASCII 编码表中的字符包含了大小写的英文字母、数字和一些符号。每个字符 对应一个编码,比如大写字母 A 的编码是 65,小写字母 a 的编码是 97。后来又针对特殊字符,将 ASCII 扩展到了 256 位。ASCII 编码可以解决以英语为主的语言,可是无法满足中文编码。为了解决中文编码,我国制定了 GB2312、GBK、GB18030 等中文编码标准,将中文编译进去。但是世界上有上百种语言和文字,各 国有各国的标准,就会不可避免的产生冲突,于是就有了 Unicode 编码。Unicode 编码最常用的就 是 UTF-8 编码,UTF-8 编码把一个 Unicode 字符根据不同的数字大小编码成 1 ~ 6 个字节,常用的 英文字母被编码成 1 个字节,汉字通常是 3 个字节。

字符串的存储结构

字符串的存储结构跟线性表相同,分为 「顺序存储结构」「链式存储结构」

字符串匹配问题

字符串匹配 :又称 「模式匹配」。可以简单理解为,给定字符串 T p,在主串 T 中寻找子串 p。主 串 T 又被称为 「文本串」 ,子串 p 又被称为 「模式串」 。在字符串问题中,最重要的问题之一就是字符串匹配问题。而按照模式串的个数,可以将字符串匹 配问题分为:「单模式串匹配问题」和「多模式串匹配问题

单模式匹配问题

单模式匹配问题:给定一个文本串T = t_1t_2 ...t_n ,再给定一组特定模式串P = {P_1P_2…P_n}。要求从文本 串T找出特定模式串p的所有出现位置。有很多算法可以解决单模式匹配问题。而根据在文本中搜索模式串方式的不同,可以将单模式匹配 算法分为以下三种:

  • 基于前缀搜索方法:在搜索窗口内从前向后(沿着文本的正向)逐个读入文本字符,搜索窗口中文本和模式串的最长公共前缀。
    • 著名的KMP算法和更快的Shift-Or算法使用的就是这种方法。
  • 基于后缀搜索方法:在搜索窗口内从后向前(沿着文本的反向)逐个读入文本字符,搜索窗口中文 本和模式串的最长公共后缀。使用这种搜索算法可以跳过一些文本字符,从而具有亚线性的平均时 间复杂度。
    • 最著名的 BM 算法,以及 Horspool 算法Sunday 算法 都使用了这种方法。
  • 基于子串搜索方法:在搜索窗口内从后向前(沿着文本的反向)逐个读入文本字符,搜索满足「既 是窗口中文本的后缀,也是模式串的子串」的最长字符串。与后缀搜索方法一样,使用这种搜索方 法也具有亚线性的平均时间复杂度。这种方法的主要缺点在于需要识别模式串的所有子串,这是一 个非常复杂的问题。
    • Rabin-Karp 算法、BDM 算法、BNDM 算法 和 BOM 算法 使用的就是这种思想。其中, Rabin-Karp 算法使用了基于散列的子串搜索算法

多模式串匹配问题

多模式串匹配算法大多使用了一种基本的数据结构:「字典树(Trie)」。著名的 「AC 自动机算法」 就是在 KMP 算法 的基础上,与「字典树」结构相结合而诞生的。而「AC 自动机算法」也是多模式串 匹配算法中最有效的算法之一。所以学习多模式匹配算法,重点是要掌握 「字典树」 和 「AC 自动机算法」

单模式串朴素匹配算法

Brute Force算法:中文意思是暴力匹配算法,也可以叫做朴素匹配算法。BF算法思想:对于给定文本串T与模式串p ,从文本串的第一个字符开始与模式串p的第一个字符进 行比较,如果相等,则继续逐个比较后续字符,否则从文本串T的第二个字符起重新和模式串p进行 比较。依次类推,直到模式串p中每个字符依次与文本串T的一个连续子串相等,则模式匹配成功。否则模式匹配失败。

BF算法步骤

1 .对于给定的文本串T与模式串p ,求出文本串T的长度为n ,模式串p的长度为m

2 .同时遍历文本串T和模式串p ,先将T[0]p[0]进行比较

3 .如果相等,则继续比较T[1]p[1]。以此类推,一直到模式串p的末尾p[m - 1]为止

4 .如果不相等,则将文本串T移动到上次匹配开始位置的下一个字符位置,模式串p则回退到开始位置,再依次进行比较。

5 .当遍历完文本串T或者模式串p的时候停止搜索。

python实现

代码语言:javascript
复制
def bruthForce(T:str, p:str) -> str:
  n, m = len(T), len(p)

  i, j = 0, 0
  while i < n and j < m:
    if p[j] == T[i]:
      i += 1
      j += 1
     else:
      i = i-(j-1)  # 匹配失败,i移动到上次匹配开始位置的下一个位置
      j = 0
  if j == m:    # 匹配成功,返回匹配的开始位置
    return i-j
  return -1     # 匹配失败,返回-1

单模式串朴素匹配算法分析

  • BF算法非常简单,容易理解,但其效率很低。主要是因为在匹配过程中可能会出现回溯:当遇到一 对字符不同时,模式串p直接回到开始位置,文本串也回到匹配开始位置的下一个位置,再重新开 始比较
  • 回溯之后,文本串和模式串中一些部分的比较是没有必要的。由于这种操作策略,导致BF算法的** 效率很低**。最坏情况是每一趟比较都在模式串的最后遇到了字符不匹配的情况,每轮比较需要进行 m次字符对比,总共需要进行n-m+1轮比较,总的比较次数为m * (n-m+1) 。所以BF 算法的最坏时间复杂度为
O(m*n)

。在

  • 最理想的情况下(第一次匹配直接匹配成功),BF算法的最佳时间复杂度是O(m) .
  • 在一般情况下,根据等概率原则,平均搜索次数为(n+m)/2等,所以BF算法的平均时间复杂度为O(n+m)

单模式串 KMP 匹配算法

KMP算法全称叫做[ Knuth Morris Pratt算法」,是由它的三位发明者Donald Knuth、James H. Morris. Vaughan Pratt的名字来命名的。KMP算法是他们三人在1977年联合发表的。KMP算法思想:对于给定文本串T与模式串p ,当发现文本串T的某个字符与模式串p不匹配的时候,可以利用匹配失败后的信息,尽量减少模式串与文本串的匹配次数,避免文本串位置的回退,以达到快速匹配的目的。

KMP算法的改进 如果我们可以通过每一次的失配而得到一些 「信息」,并且这些 「信息」 可以帮助我们跳过那些不可能匹配成功的位置,那么我们就能大大减少模式串与文本串的匹配次数,从而达到快速匹配的目的。每一次失配所告诉我们的信息是:主串的某一个子串等于模式串的某一个前缀。 这个信息的意思是:

如果文本串T[i: i + m]与模式串p的失配是下标位置j上发生的,那么文本串T从下标位置i开始连续的j个字符,一定与模式串p的前j个字符一模一样,即

T[i:i+j] == p[0:j]

在这里插入图片描述

KMP算法的改进

KMP算法就是使用了这样的思路,对模式串p进行了预处理,计算出一个 「部分匹配表」,用一个数组next来记录。然后在每次失配发生时,不回退文本串的指针i,而是根据 「部分匹配表」 中模式串失配位置j的前一个位置的值,即next[j -1]值来决定模式串可以向右移动的位数。比如上述示例中模式串p是在j = 5的位置上发生失配的,则说明文本串的子串T[i: i + 5]和模式串p[0: 5]的字符是一致的,即"ABCAB" == "ABCAB"而根据 「部分匹配表」 中next[4] = = 2 ,所以不用回退i ,而是将j移动到下标为2的位置,让T[i + 5]直接对准p[2],然后继续进行比对。

如果文本串T[i:i+m]与模式串p的失配是在第j个下标位置发生的,那么:

  • 文本串T从下标位置i开始连续的j个字符,一定与模式串p的前j个字符一模一样,即:$T[i: i +j]==p[0: j]
  • 而如果模式串p的前j个字符中,前k位前缀和后k位后缀相同,即p[0:k] == p[j-k: j],并且要保证k要尽可能长。

可以推出:文本串子串的后k位后缓和模式串子串的前k位是相同的,即

T[i+m-k: i+m] == p[0:k]

,不需要再比较了,可以直接跳过。

那么我们就可以将文本串中的T[i + m]对准模式串中的p[k],继续进行对比。这里的k其实就是next[j-1]

next数组的构造我们可以通过递推的方式构造next数组。

  • 我们把模式串p拆分成left、right两部分。left表示前缀串开始所在的下标位置,right表示后 缀串开始所在的下标位置,起始时
left = 0 , right = 10
  • 比较一下前缀串和后缀串是否相等,通过比较p[left]p[right]来进行判断。
  • 如果
p[left] != p[right]

,说明当前的前后缀不相同。则让后缀开始位置k不动,前缀串开始位置 left 不断回退到

next [left - 1]

位置,直到

p[left] == p[right]

为止。

  • 如果
p[left] == p[right]

,说明当前的前后缀相同,则可以先让

left += 1

,此时left既是前缀 下一次进行上匕较的下标位置,又是当前最长前后缀的长度。

  • 记录下标right之前的模式串p中,最长相等前后缀的长度为left ,即
next [right] = left

python实现

代码语言:javascript
复制
# 生成next数组
# next[j]表示下标j之前的模式串p中,最长相等前后缀的长度

def generateNext(p: str):
    m = len(p)
    next = [0 for _ in range(m)]  # 初始化数组元素全部为0
    
    left = 0  # left表示前缀串开始所在的下标位置
    for right in range(1, m):  # right表示后缀串开始所在的下标位置
        while left > 0 and p[left] != p[right]:  # 匹配不成功,left进行回退,left==0时停止回退
            left = next[left-1]  # left进行回退操作  
        if p[left] == p[right]:  # 匹配成功,找到相同的前后缀,先让left+=1, 此时left为前缀长度
            left += 1
        next[right] = left  # 记录前缀长度,更新next[right], 结束本次循环,right+=1
     return next


# KMP 匹配算法,T为文本串,p为模式串
def kmp(T:str, p:str) -> int:
    n = len(T)
    m = len(p)
    
    next = generateNext(p)  # 生成next数组
    
    j = 0   # j为模式串中当前匹配的位置
    for i in range(n):  # i 为文本串中当前匹配的位置
       while j > 0 and T[i] != p[j]:  # 如果模式串前缀匹配不成功,将模式串进行回退,j==0时候停止回退
         j = next[j-1]
       if T[i] == p[j]:  # 当前模式串前缀匹配成功,令j+=1,继续匹配
         j += 1
       if j == m:   # 当前模式串完全匹配成功,返回匹配开始位置
         return i-j+1
     return -1  # 匹配失败,返回-1

KMP匹配算法分析

  • KMP算法在构造前缀表阶段的时间复杂度为
O(m)

,其中m是模式串p的长度

  • KMP算法在匹配阶段,是根据前缀表不断调整匹配的位置,文本串的下标i并没有进行回退,可以看出匹配阶段的时间复杂度是
O(n)

,其中n是文本串T的长度

  • 所以KMP整个算法的时间复杂度是
O(n + m)

,相对于朴素匹配算法

O(n*m)

的时间复杂度,KMP算法的效率有了很大的提升

字符串题目一般考虑使用滑动窗,双指针,kmp算法。

例题

107 验证回文串

题目大意:描述:给定一个字符串 s。要求:验证它是否是回文串,如果是回文串,则返回 True,否则返回 False。只考虑字母和数字字符, 可以忽略字母的大小写。

示例 :

代码语言:javascript
复制
输入: "A man, a plan, a canal: Panama"
输出: true
解释:"amanaplanacanalpanama" 是回文串
代码语言:javascript
复制
输入: "race a car"
输出: false
解释:"raceacar" 不是回文串

解题思路:使用对撞指针求解。具体步骤如下:

  • 使用两个指针left, right, left指向字符串开始位置,right指向字符串结束位置
  • 判断两个指针对应字符是否是字母或数字。通过left右移、right左移的方式过滤掉字母和数字以外的字符
  • 然后判断s[left]是否和s[right]相等(注意大小写)
    • 如果相等,则将left右移,right左移,继续进行下一次过滤和判断
    • 如果不相等,则说明不是回文串,直接返回False
  • 如果遇到
left==right

,跳出循环,则说明该字符串是回文串,返回True。

python实现

代码语言:javascript
复制
class Solution:
    def isPalindrome(self, s: str) -> bool:
        if not s or len(s) < 1:
            return False
        length = len(s)
        if length == 1:
            return True
        left = 0
        right = length-1
        while left < right:
            if not s[left].isalnum():
                left += 1
                continue
            if not s[right].isalnum():
                right -= 1
                continue
            
            if s[left].lower() == s[right].lower():
                left += 1
                right -= 1
            else:
                return False
        return True

C++实现

代码语言:javascript
复制
class Solution {
public:
    bool isPalindrome(string s) {
        string sgood;
        for(char ch:s)
        {
            if(isalnum(ch))
            {
                sgood += tolower(ch);
            }
        }
        
        int n = sgood.size();
        int left = 0;
        int right = n-1;
        while(left < right)
        {
            if (sgood[left] != sgood[right])
            {
                return false;
            }
            ++left;
            --right;
        }
        return true;
    }
};
127 翻转字符串里的单词

题目大意:描述:给定一个字符串 s。要求:逐个翻转字符串中所有的单词。注意:

  • 数组字符串 s 可以再前面、后面或者单词间包含多余的空格
  • 翻转后的单词应当只有一个空格分隔
  • 翻转后的字符串不应该包含额外的空格

示例 :

代码语言:javascript
复制
输入:s = "the sky is blue"
输出:"blue is sky the"
代码语言:javascript
复制
输入:s = "  hello world  "
输出:"world hello"
解释:输入字符串可以在前面或者后面包含多余的空格,但是翻转后的字符不能包括。
代码语言:javascript
复制
输入:s = "a good   example"
输出:"example good a"
解释:如果两个单词间有多余的空格,将翻转后单词间的空格减少到只含一个。
代码语言:javascript
复制
输入:s = "  Bob    Loves  Alice   "
输出:"Alice Loves Bob"
代码语言:javascript
复制
输入:s = "Alice does not even like bob"
输出:"bob like even not does Alice"

解题思路:第一种思路比较简单,就是直接调用一些函数的库函数,对字符串进行切片,翻转,然后拼合成字符串 第二种思路根据API的思路写出模拟代码,具体步骤如下:

  • 使用数组words存放单词,使用字符串变量cur存放当前单词
  • 遍历字符串,对于当前字符而
  • 如果遇到空格,则:
    • 如果当前单词不为空,则将当前单词存入数组words中,并将当前单词置为空串
  • 如果遇到字符,则:
    • 将其存入当前单词中,即
    cur += c
  • 如果遍历完,当前单词不为空,则将当前单词存入数组words中
  • 然后对数组words进行翻转操作,令words[i], words[len(words)- 1-i] 交换元素。
  • 最后将words中的单词连接起来,中间拼接上空格,将其作为答案返回。

fig

python实现

代码语言:javascript
复制
class Solution:
    def reverseWords(self, s: str) -> str:
        ss = s.split()
        return ' '.join(ss[::-1])

或者

代码语言:javascript
复制
class Solution:
   def reverseWords(self, s: str) -> str:
       words = []
        cur == ''
        for ch in s:
          if ch == ' ':
             if cur:
               words.append(cur)
                cur = ''
          else:
             cur += ch
        
        if cur:
           words.append(cur)
        
        return ' '.join(words[::-1])

自己实现

fig

代码语言:javascript
复制
class Solution:
    def trim_spaces(self, s: str) -> list:
        left, right = 0, len(s) - 1
        # 去掉字符串开头的空白字符
        while left <= right and s[left] == ' ':
            left += 1
        
        # 去掉字符串末尾的空白字符
        while left <= right and s[right] == ' ':
            right -= 1
        
        # 将字符串间多余的空白字符去除
        output = []
        while left <= right:
            if s[left] != ' ':
                output.append(s[left])
            elif output[-1] != ' ':
                output.append(s[left])
            left += 1
        
        return output
            
    def reverse(self, l: list, left: int, right: int) -> None:
        while left < right:
            l[left], l[right] = l[right], l[left]
            left, right = left + 1, right - 1
            
    def reverse_each_word(self, l: list) -> None:
        n = len(l)
        start = end = 0
        
        while start < n:
            # 循环至单词的末尾
            while end < n and l[end] != ' ':
                end += 1
            # 翻转单词
            self.reverse(l, start, end - 1)
            # 更新start,去找下一个单词
            start = end + 1
            end += 1
                
    def reverseWords(self, s: str) -> str:
        l = self.trim_spaces(s)
        
        # 翻转字符串
        self.reverse(l, 0, len(l) - 1)
        
        # 翻转每个单词
        self.reverse_each_word(l)
        
        return ''.join(l)

C++实现

代码语言:javascript
复制
class Solution {
public:
    string reverseWords(string s) {
        // 反转整个字符串
        reverse(s.begin(), s.end());

        int n = s.size();
        int idx = 0;
        for (int start = 0; start < n; ++start) {
            if (s[start] != ' ') {
                // 填一个空白字符然后将idx移动到下一个单词的开头位置
                if (idx != 0) s[idx++] = ' ';

                // 循环遍历至单词的末尾
                int end = start;
                while (end < n && s[end] != ' ') s[idx++] = s[end++];

                // 反转整个单词
                reverse(s.begin() + idx - (end - start), s.begin() + idx);

                // 更新start,去找下一个单词
                start = end;
            }
        }
        s.erase(s.begin() + idx, s.end());
        return s;
    }
};
329 验证回文字符串II

给定一个非空字符串 s最多删除一个字符。判断是否能成为回文字符串。

示例 :

代码语言:javascript
复制
输入: s = "aba"
输出: true
代码语言:javascript
复制
输入: s = "abca"
输出: true
解释: 你可以删除c字符。
代码语言:javascript
复制
输入: s = "abc"
输出: false

解题思路

首先考虑如果不允许删除字符,如何判断一个字符串是否是回文串。常见的做法是使用双指针。定义左右指针,初始时分别指向字符串的第一个字符和最后一个字符,每次判断左右指针指向的字符是否相同,如果不相同,则不是回文串;如果相同,则将左右指针都往中间移动一位,直到左右指针相遇,则字符串是回文串。

在允许最多删除一个字符的情况下,同样可以使用双指针,通过贪心实现。初始化两个指针 lowhigh 分别指向字符串的第一个字符和最后一个字符。每次判断两个指针指向的字符是否相同,如果相同,则更新指针,将 low 加 1high 减 1然后判断更新后的指针范围内的子串是否是回文字符串如果两个指针指向的字符不同,则两个字符中必须有一个被删除,此时就分成两种情况:即删除左指针对应的字符,留下子串

s[low+1:high]

,或者删除右指针对应的字符,留下子串

s[low:high−1]

当这两个子串中至少有一个是回文串时,就说明原始字符串删除一个字符之后就以成为回文串。

fig1

  • python实现
代码语言:javascript
复制
class Solution:
    def validPalindrome(self, s: str) -> bool:
        length = len(s)
        if not str or length < 1:
            return False

        if length == 1:
            return True
        
        left = 0
        right = length-1
        while left < right:
            if s[left] == s[right]:
                left += 1
                right -= 1
            else:
                return self.isPalindrome(s, left+1, right) or self.isPalindrome(s, left, right-1)  # 递归
        return True
    
    def isPalindrome(self, s:str, left, right) -> bool:
        while left < right:
            if s[left] == s[right]:
                left += 1
                right -= 1
            else:
                return False
        return True
  • C++实现
代码语言:javascript
复制
class Solution {
public:
    bool validPalindrome(string s) {
        int low = 0, high = s.length()-1;
        while(low<high)
        {
            char c1 = s[low];
            char c2 = s[high];
            if(c1==c2)
            {
                ++low;
                --high;
            }
            else
            {
                return validPalindrome(s, low, high-1) || validPalindrome(s, low+1, high);
            }
        }
        return true;
    }
    
    bool validPalindrome(string s, int low, int high)
    {
        for(int i=low, j=high; i<j; ++i, --j)
        {
            char c1 = s[i];
            char c2 = s[j];
            if(c1 != c2)
            {
                return false;
            }
        }
        return true;
    }
    
    
};
135 Excel表列名称

给你一个整数 columnNumber ,返回它在 Excel 表中相对应的列名称。

例如:

代码语言:javascript
复制
A -> 1
B -> 2
C -> 3
...
Z -> 26
AA -> 27
AB -> 28 
...

示例 :

代码语言:javascript
复制
输入:columnNumber = 1
输出:"A"
代码语言:javascript
复制
输入:columnNumber = 28
输出:"AB"
代码语言:javascript
复制
输入:columnNumber = 701
输出:"ZY"
代码语言:javascript
复制
输入:columnNumber = 2147483647
输出:"FXSHRXW"

解题思路:

要将数字转化为字母,就需要就将数据对26取模,再转化为对应字母的ascii码。

但是这里需要注意的是,1-26对26取余后的结果是1-25与0,此时转化为A-Z时则会出现循环错位,要么分类讨论,要么就寻找更好的方法。

而当对数据一律减1后取余,使得1-26减1后取余后的结果0-25再+'A',就可以与A-Z对应。

  • python实现
代码语言:javascript
复制
class Solution:
    def convertToTitle(self, columnNumber: int) -> str:
        ans = list()
        while columnNumber > 0:
            columnNumber -= 1
            ans.append(chr(columnNumber%26 + ord("A")))
            columnNumber //= 26
        return ''.join(ans[::-1])
  • C++实现
代码语言:javascript
复制
class Solution {
public:
    string convertToTitle(int columnNumber) {
        string res;
        while(columnNumber != 0)
        {
            columnNumber--;
            res += (columnNumber % 26) + 'A';
            columnNumber /= 26;
        }
        reverse(res.begin(), res.end());
        return res;
    }
};
224 字符串解码

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

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

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

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

示例 :

代码语言:javascript
复制
输入:s = "3[a]2[bc]"
输出:"aaabcbc"
代码语言:javascript
复制
输入:s = "3[a2[c]]"
输出:"accaccacc"
代码语言:javascript
复制
输入:s = "2[abc]3[cd]ef"
输出:"abcabccdcdcdef"
代码语言:javascript
复制
输入:s = "abc3[cd]xyz"
输出:"abccdcdcdxyz"

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

解题思路:

  • 由于存在括号, 所以使用栈来保存后进先出的原则;
  • 栈内需要保存的是括号前的数字k, 以及括号前已经组成的字符串res;
  • 遇到括号闭合时, 则将字符串按规则进行拼接;

python实现

代码语言:javascript
复制
 class Solution:
    def decodeString(self, s: str) -> str:
        stack = []
        for ch in s:
            if ch != ']':
                stack.append(ch)
            else:
                tmp = []
                num = ''
                # 先找要重复的子字符串
                while stack[-1] != '[' and not stack[-1].isdigit():
                    tmp.append(stack.pop())
                tmp = ''.join(tmp[::-1])  # 反序拼接
                
                # 移除'['
                stack.pop()

                # 再找重复的次数(数字)
                while stack and stack[-1].isdigit():
                    num += stack.pop()
                num = num[::-1]
                num = int(num) if num else num
                
                # 重复子字符串,并加入到stack中
                # print(tmp, num)
                if num:
                    stack.append(tmp*num)
            # print(ch, stack)
        return ''.join(stack)

C++实现

代码语言:javascript
复制
class Solution {
public:
    /*
     * 1、栈题,最好使用栈,那么必有入栈出栈操作
     * 2、基本元素:必定有判断、必定有循环
     * 3、字符题,判断必带字符判断
     * 4、必定有简单的判断思路,再加上巧妙的过程(这里将完整的字符串描述成一串字符串,部分的字符串描述成一块字符串)
     */
    std::string decodeString(std::string s) {
        std::stack<std::pair<int, std::string>> st; // 使用pair存储一块数字和一块字符串
        int count = 0;
        std::string onepiece = "";
        for(auto& c : s) // 重要思路:每一个pair都入栈一对 ( 数字 + 一块字符串 )
        {
            if(c >= '0' && c <= '9')
                count = count * 10 + (int)(c-'0'); //[1]遇到数字,数字计算公式
            else if(c == '[')
            {                
                st.emplace(std::pair<int, std::string>(count, onepiece)); //[2]遇到 [ , pair先入栈数字+空的一块字符串, 或者入栈数字+数字前的只有1次的一块字符串
                onepiece = ""; // 置空
                count = 0; // 置空
            }
            else if(c == ']') //[4]遇到 ] , pair的second进行字符串赋值
            {                
                std::string tmp = onepiece;
                std::string tmp1 = "";
                for(int i = 0; i < st.top().first; ++i) // 循环拼接有k次的一块字符串,st.top().first为数字
                    tmp1 += tmp;
                st.top().second = st.top().second + tmp1; //将一块字符串赋值给second,存储的second带有只有1次的一块字符串,所以拼接时要带上second来进行拼接,故有second+tmp1
                onepiece = st.top().second; // onepiece继续拼接,不断的扩展,由一块字符串不断拼接成一串字符串
                st.pop(); // 弹出,因为 ] 的位置
            }
            else
                onepiece += c; //[3]遇到字符就拼接组成一块字符串
        }
        return onepiece;
    }
};
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-03-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 AI科技时讯 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 字符串
  • 字符串简介
  • 字符串的比较
    • 字符串的比较操作
      • 字符串的字符编码
      • 字符串的存储结构
      • 字符串匹配问题
        • 单模式匹配问题
        • 多模式串匹配问题
        • 单模式串朴素匹配算法
        • 单模式串 KMP 匹配算法
        • 例题
          • 107 验证回文串
            • 127 翻转字符串里的单词
              • 329 验证回文字符串II
                • 135 Excel表列名称
                  • 224 字符串解码
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档