代码随想录算法训练营第八天(字符串part01) | 344.反转字符串,541. 反转字符串II,卡码网:54.替换数字,151.翻转字符串里的单词,卡码网:55.右旋转字符串

📝344.反转字符串(easy)

建议: 本题是字符串基础题目,就是考察 reverse 函数的实现,同时也明确一下 平时刷题什么时候用库函数,什么时候不用库函数。

题目链接/文章讲解/视频讲解:代码随想录


👩‍💻思路:(双指针)

  • 初始化左右指针:左指针 left 初始化为 0,右指针 right 初始化为 len(s) - 1,即字符串的最后一个字符的位置。
  • 双指针交换:使用 while left < right 作为循环条件,当左右指针相遇或交错时停止循环。在每次循环中,交换 s[left]s[right] 的值,然后移动指针:左指针右移(left += 1),右指针左移(right -= 1)。
  • 终止条件:当 leftright 相等或 left 超过 right 时,停止循环。

❌易错点/难点总结:

1.索引越界:
确保 right 初始化为 len(s) - 1,防止初始值超出字符串范围。

2.循环条件错误:
循环条件应该是 while left < right,而不是 <=,因为当左右指针相遇时已经无需交换。

3.指针移动:
在交换后,正确地移动左右指针:left += 1 和 right -=1。

4.交换操作:
确保使用 s[left], s[right] = s[right], s[left] 语法进行交换,以避免临时变量的错误使用。

5.原地修改:
题目要求原地修改字符串,因此不要返回新列表,只需直接修改输入列表 s。

📸解题代码:

Time complexity : O(N) to swap N/2 element.
Space complexity : O(1), it's a constant space solution.
class Solution(object):
    def reverseString(self, s):
        """
        :type s: List[str]
        :rtype: None Do not return anything, modify s in-place instead.
        """
        left = 0
        right = len(s) -1 
        while left < right: #在两个pointers没重合之前,一直swap
            s[left],s[right] = s[right],s[left]
            left += 1
            right -= 1
            #直到left = right,stop swaping


📝541. 反转字符串II (easy)

建议:本题又进阶了,自己先去独立做一做,然后在看题解,对代码技巧会有很深的体会。

题目链接/文章讲解/视频讲解:代码随想录


👩‍💻思路:

1.直接使用reversed函数进行swap: 

这个方法利用了 Python 的切片和内置函数 reversed 的高效特性,实现了对列表子区块的部分反转操作。首先,请明确一件事情:我们每一次swap的起点都在 0,2k,4k,8k....,可以把他们想象成一个一个小的block,在遍历列表的过程中,需要注意的一件事是,如果没有足够的字符,我们可能不会反转block中的每一个字符。

  • 将字符串转换为列表:由于字符串是不可变的,为了在原地修改字符串,需要先将其转换为列表。
  • 遍历字符串:以步长 2*k 遍历字符串,这样每次都会跳过一个长度为 2*k 的区块。
  • 反转前 k 个字符:对当前区块的前 k 个字符进行反转操作。
  • 处理剩余字符:用reserved 函数,如果当前区块的长度不足 k 个字符,则反转所有剩余字符;如果介于 k2*k 之间,则仅反转前 k 个字符,剩余字符保持不变。
  • 将列表转换回字符串:遍历结束后,将列表转换回字符串。
  • 以下是对遍历过程的详细解读:

初始状态:

输入字符串 s = "abcdefg" 转换为列表 a = ['a', 'b', 'c', 'd', 'e', 'f', 'g'] |k = 2

For 循环: range(0, len(a), 2*k) 生成的序列是 0, 4, 8。

第一次迭代 (i = 0):

a[0:2] = reversed(a[0:2]),即 ['a', 'b'] = ['b', 'a']。

列表变为 ['b', 'a', 'c', 'd', 'e', 'f', 'g']。

第二次迭代 (i = 4):

a[4:6] = reversed(a[4:6]),即 ['e', 'f'] = ['f', 'e']。

列表变为 ['b', 'a', 'c', 'd', 'f', 'e', 'g']。

第三次迭代 (i = 8):

i = 8 已经超出列表长度,循环终止


2.不使用reversed函数:

这个方法和第一个方法的思路一摸一样,唯一的不同就是我们要自行在内部编写一套用来swap的function来替代reversed函数,很妙的是,我们发现这个用来swap的方法就是【344.反转字符串】的解题方法。

❌易错点/难点总结:

1.字符串和列表的转换:

由于字符串是不可变的,为了在原地修改字符串,需要先将其转换为列表。

2.在最后一步需要将列表转化为字符串:

很容易遗忘这一步,注意题目要求需要我们最终返还的是什么

3.内嵌函数的variables不要添加self进去:

在调用function:swap_mylist的时候,在类方法内部定义的函数(比如嵌套函数或辅助函数)不需要使用 self。这些函数只是类内部的工具函数,不属于类的实例方法。此外,在调用时,也不用写self.swap_mylist,而是直接写swap_mylist即可。注意这两点,否则会报错。

📸解题代码:

方法一:直接用reversed函数解决

Time Complexity: O(N)
Space Complexity: O(N),the size of mylist.
class Solution(object):
    def reverseStr(self, s, k):
        """
        :type s: str
        :type k: int
        :rtype: str
        """
        #将str字符串转换为list列表以便原地修改因为字符串无法修改
        mylist = list(s)
        #从起点开始遍历整个列表,step=2k
        for i in range(0,len(mylist),2*k):
            #反转i到k范围内的字符(PS:不包括第k个字符本身
            mylist[i:i+k] = reversed(mylist[i:i+k])
        #将列表转换为字符串
        return "".join(mylist)

方法二:不使用revered函数

Time Complexity: O(N)
Space Complexity: O(N),the size of mylist.
class Solution(object):
    def reverseStr(self, s, k):
        """
        :type s: str
        :type k: int
        :rtype: str
        """
        # 定义用于实现‘swap’的内嵌函数
        def swap_mylist(s):
            left = 0
            right = len(s) - 1
            while left < right:
                s[left], s[right] = s[right], s[left]
                left += 1
                right -= 1
            return s
        # 将 str 字符串转换为 list 列表以便原地修改因为字符串无法修改
        mylist = list(s)
        # 从起点开始遍历整个列表,step=2k
        for i in range(0, len(mylist), 2 * k):
            # 反转 i 到 k 范围内的字符(PS:不包括第 k 个字符本身)
            mylist[i:i + k] = swap_mylist(mylist[i:i + k])  # 调用方法时不需要 self
        # 将列表转换为字符串并返回
        return "".join(mylist)

📝卡码网:54.替换数字

建议:对于线性数据结构,填充或者删除,后序处理会高效的多。好好体会一下。

题目链接/文章讲解:代码随想录


👩‍💻思路:

1.直接替换(暴力法)

这是一个直截了当的方法,我们不在原本的字符串上进行修改(因此不需要将字符串提前转化为列表以便修改),而是判断完字符串之后逐一放在新的array中。首先创建一个新的array用于存放单独的str,然后遍历整个字符串,如果是数字,就改成 ‘number’ append到新的array中,反之,直接append原本的str到array中,不做修改。最后,别忘记把我们的array修改为字符串。


2.双指针法

  • 统计数字字符:首先遍历原始字符串 s,统计其中数字字符的个数 numCount
  • 计算扩展后的字符串长度:原始字符串长度为 oldSize。每个数字字符需要额外增加 5 个字符(假设把5替换成number,我们需要额外的五个字符。5=6(number占了6个)-1(5占了一个字符但是即将被替换掉),因此扩展后的字符串长度为 newSize = oldSize + numCount * 5。
  • 扩展字符串数组:将字符串转换为字符列表 s,因为 Python 字符串是不可变的。扩展列表 s 以便容纳替换后的字符(多出的空间用空字符 '' 填充)。
  • 初始化双指针:leftP 指针从原始字符串的末尾开始,即 leftP = oldSize - 1。rightP 指针从扩展后的字符串末尾开始,即 rightP = newSize - 1。
  • 从后向前遍历并替换字符:使用 while 循环遍历字符串,直到 leftP 小于 0。如果当前字符是数字字符,将其替换为 "number",并调整 rightP 指针(right指针需要减6,这是因为如果一个字符中有多个数字,我们会提前腾出很多位置,替换完第一个number,前面还有剩余的空的位置,所以right要一次性前进六个字符位置)。如果当前字符不是数字字符,直接将其复制到新的位置(即rightP指针所在的位置),并调整 rightP 指针(right指针需要减1)。每次循环结束时,将 leftP 指针左移一位。
  • 返回结果:将扩展后的字符列表 s 转换回字符串,并返回结果

❌易错点/难点总结:

1.指针初始化错误:
左指针 (leftP) 和右指针 (rightP) 的初始位置需要非常准确。leftP 应该初始化为 oldSize - 1,而 rightP 应该初始化为 newSize - 1。如果这一步出错,会导致字符替换的错位。

2.字符范围判断错误:
判断字符是否为数字字符时,条件应该是 if '0' <= char <= '9'。如果写成 if char in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'] 会导致效率降低。

3.索引范围越界:
在进行字符替换时,如 s[rightP-5:rightP+1] = 'number',要确保 rightP-5 不会小于 0,否则会引起索引错误。需要特别注意边界条件,避免指针移动导致的越界。

4.指针移动步长错误:
替换数字字符为 "number" 后,rightP 指针需要移动 6 个位置,而不是 5 个,因为要确保所有字符都被正确替换和填充。同样,处理非数字字符时,rightP 指针只需移动 1 个位置。

5.字符追加错误:
在将字符串扩展为字符列表时,需确保新的字符列表长度足够容纳替换后的所有字符,且扩展字符必须用空字符 '' 填充。

📸解题代码:

1.直接替换(暴力法)

Time Complexity: O(N)
Space Complexity: O(N),the size of new array(result)
class Solution(object):
    def swap_number(self, s):
        #创建一个空的list用于存放遍历并修改后的字符
        result = []
        #遍历整个str
        for i in range(len(s)): 
            #查找字符是否为数字
            if s[i] in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']:
                #如果是的话直接修改为‘number’
                result.append('number')
            #另外一种情况:不是数字,那么肯定是字母了
            else:
                #不做任何改变,直接append原来的字母
                result.append(s[i])
        #最后要记得把新的list转化为字符串        
        return "".join(result)

solution = Solution()
test_string = "abc123"
result = solution.swap_number(test_string)
print(result)
#output:abcnumbernumbernumber

2.双指针法

Time Complexity: O(N)
Space Complexity: O(1)
class Solution(object):
    def replaceNumber(self, s):
        numCount, oldSize = 0, len(s)
        #用numcount来查找一下s中有几个数字
        for char in s:
            if '0' <= char <= '9':
                numCount += 1
        #计算新的size
        newSize = oldSize + numCount * 5
        #为即将插入的number腾出更多的位置
        s = list(s) + [''] * numCount * 5
        #左指针在原本str的末尾,右指针在新的str的末尾
        leftP, rightP = oldSize - 1, newSize - 1
        while leftP >= 0:
            if '0' <= s[leftP] <= '9':
                #开始swap
                s[rightP-5:rightP+1] = 'number'
                rightP -= 6
            else:
                s[rightP] = s[leftP]
                rightP -= 1
            leftP -= 1
        return ''.join(s)
# 测试用例
sol = Solution()
print(sol.replaceNumber("a5b"))  # 输出 "anumberb"

📝151.翻转字符串里的单词 (medium)

建议:这道题目基本把 刚刚做过的字符串操作 都覆盖了,不过就算知道解题思路,本题代码并不容易写,要多练一练。

题目链接/文章讲解/视频讲解:代码随想录


👩‍💻思路:

1.首先把字符串前方和后方的空格全部消除掉(注意:这个时候不保证全部空白位置被删去,因为我们还没有来得及看被两个单词夹在中间的,例如"hello    world")。之后,我们单独删除一下两个单词中间的多余空位,但是要留下最后一个空位作为单词之间的隔断。

2.第二步,用左右双指针无差别翻转所有str(“hello world”会变为“dlrow olleh")

3.最后一步,单独reverse每个单独的单词(“dlrow olleh"会变为“world hello”)

请参考以"   hello   world " 为例子的步骤解读:

❌易错点/难点总结:

1.边界条件处理:
在移除首尾空格和多余空格时,容易出现索引越界或遗漏空格的情况。在 reverse_each_word 函数中处理 end 索引的边界情况容易出错,尤其是 end - 1 处的处理需要特别注意。

2.多空格处理:
在 trim_spaces 函数中,处理多个连续空格时,容易出现遗漏或者多添加空格的情况。确保最后一个添加到 output 列表中的字符不是空格。

3.双指针法的理解和使用:
理解双指针法的具体实现方式和为什么从后往前处理比从前往后处理更高效。特别是,在插入元素时避免多次移动元素。

📸解题代码:

空间复杂度均为 O(n),其中 n 是输入字符串 s 的长度或列表 l 中的元素数量。这是因为我们使用额外的空间来存储修剪后的字符串或反转列表。时间复杂度是O(n),其中 n 是输入字符串 s 的长度。

class Solution(object):
    #part 1:删掉空白位置但是保留特殊位置上的“ ”
    def trim_spaces(self, s: str) -> list:
        left = 0
        right = len(s) - 1
    
    # remove leading spaces
        while left <= right and s[left] == " ":
            left += 1
    
    # remove trailing spaces
        while left <= right and s[right] == " ":
            right -= 1

    # reduce multiple spaces to single one (between words)
        result = []
        while left <= right:
            if s[left] != " ":
                result.append(s[left])
            elif result and result[-1] != " ":
                result.append(s[left])
            left += 1
        return result
        
    #part 2:整体翻转(每一个字母)
    def reverse_whole(self, l: list, left: int, right: int) -> None:
        #这里不用<=是因为left和right相等时是指的同一个字母,没必要反转
        while left < right:
            l[left],l[right] = l[right],l[left]
            left += 1
            right -= 1
    #part 3:翻转每一个单独的单词
    def reverse_each_word(self, l: list) -> None:
        start = 0
        end = 0
        n = len(l)
        while start < n:
            while end < n and l[end] != " ":
                end += 1
                #开始reverse第一个单词了
            self.reverse_whole(l,start,end-1)
            start = end + 1
            end += 1

    def reverseWords(self, s: str) -> str:
        # converst string to char array
        # and trim spaces at the same time
        l = self.trim_spaces(s)

        # reverse the whole string
        self.reverse_whole(l, 0, len(l) - 1)

        # reverse each word
        self.reverse_each_word(l)

        return "".join(l)

📝卡码网:55.右旋转字符串

建议:题解中的解法如果没接触过的话,应该会想不到

题目链接/文章讲解:

代码随想录


👩‍💻思路:

1.创建一个新的list用于存放。

2.用双指针锁定要move的右侧字母的范围。

3.直接把右侧字母挪到前面去。

4.把list修改为字符串。

📸解题代码:

这个解决方案的时间复杂度为 O(n),其中 n 是输入字符串 s 的长度。这是因为我们要从输入字符串创建一个列表,执行切分操作,然后将列表连接回字符串,所有这些操作的时间复杂度都是线性的。空间复杂度也是 O(n),因为我们要创建一个新的列表来存储反转的字符串,而这个列表可能和输入字符串本身一样大。

class Solution(object):
    def reversed_right(self, s, k):
        mylist = list(s)
        start = len(s) - k
        end = len(s) - 1
        right = start - 1
        mylist[:k] = s[start:end+1]
        mylist[k:] = s[:start]
        return ''.join(mylist)
# 示例
solution = Solution()
print(solution.reversed_right("abcdefg", 3)) #output:efgabcd
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值