目录
代码随想录day8 字符串章节
344、反转字符串
思路:
541、反转字符串II
思路:
剑指Offer 05. 替换空格
思路:
151.翻转字符串里的单词
思路:
剑指Offer58-II.左旋转字符串
思路:
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。
示例 1:
输入:["h","e","l","l","o"]
输出:["o","l","l","e","h"]示例 2:
输入:["H","a","n","n","a","h"]
输出:["h","a","n","n","a","H"]
先说一说题外话:
对于这道题目一些同学直接用库函数 reverse,调一下直接完事了, 相信每一门编程语言都有这样的库函数。如果这么做题的话,这样大家不会清楚反转字符串的实现原理了。
但是也不是说库函数就不能用,是要分场景的。如果在现场面试中,我们什么时候使用库函数,什么时候不要用库函数呢?
如果题目关键的部分直接用库函数就可以解决,建议不要使用库函数。毕竟面试官一定不是考察你对库函数的熟悉程度, 如果使用python和java 的同学更需要注意这一点,因为python、java提供的库函数十分丰富。
如果库函数仅仅是 解题过程中的一小部分,并且你已经很清楚这个库函数的内部实现原理的话,可以考虑使用库函数。建议大家平时在leetcode上练习算法的时候本着这样的原则去练习,这样才有助于我们对算法的理解。不要沉迷于使用库函数一行代码解决题目之类的技巧,不是说这些技巧不好,而是说这些技巧可以用来娱乐一下。
真正自己写的时候,要保证理解可以实现是相应的功能。接下来再来讲一下如何解决反转字符串的问题。
大家应该还记得,我们已经讲过了 207.反转链表。在反转链表中,使用了双指针的方法。
那么反转字符串依然是使用双指针的方法,只不过对于字符串的反转,其实要比链表简单一些。
因为字符串也是一种数组,所以元素在内存中是连续分布,这就决定了反转链表和反转字符串方式上还是有所差异的。
对于字符串,我们定义两个指针(也可以说是索引下标),一个从字符串前面,一个从字符串后面,两个指针同时向中间移动,并交换元素。不难写出如下Go代码
func reverseString(s []byte) {
left, right := 0, len(s)-1 // 定义left为第一个,right为最后一个
for left < right { // 循环条件,不会是等于
s[left], s[right] = s[right], s[left] // 前后对应交换
left++ //然后left右移一下
right-- //然后right左移一下
}
}
// 更加简单直接的代码
func reverseString(s []byte) {
for i:=0;i
这道题目还是比较简单的,但是我正好可以通过这道题目说一说在刷题的时候,使用库函数的原则。
如果题目关键的部分直接用库函数就可以解决,建议不要使用库函数。
如果库函数仅仅是 解题过程中的一小部分,并且你已经很清楚这个库函数的内部实现原理的话,可以考虑使用库函数。
在字符串相关的题目中,库函数对大家的诱惑力是非常大的,因为会有各种反转,切割取词之类的操作,这也是为什么字符串的库函数这么丰富的原因。
相信大家本着我所讲述的原则来做字符串相关的题目,在选择库函数的角度上会有所原则,也会有所收获。
给定一个字符串 s 和一个整数 k,从字符串开头算起,每计数至 2k 个字符,就反转这 2k 字符中的前 k 个字符。
输入:s = "abcdefg", k = 2 输出:"bacdfeg"
题目翻译:就是说一串字符串,每次遍历2k字符串,如果当前的字符串长度小于k,则反转所有字符串,如果当前的字符串长度大于k则反转前k个字符串,即[i,i+k]字符串。
if i+k > len(s) 说明len(s) < k,即剩余字符小于k,应该反转所有字符串。
if i+k <= len(s) 说明len(s) >= k,即剩余字符大于等于k,应该反转i到i+k之间的字符。
这道题目其实也是模拟,实现题目中规定的反转规则就可以了。一些同学可能为了处理逻辑:每隔2k个字符的前k的字符,写了一堆逻辑代码或者再搞一个计数器,来统计2k,再统计前k个字符。
其实在遍历字符串的过程中,只要让 i += (2 * k),i 每次移动 2 * k 就可以了,然后判断是否需要有反转的区间。
因为要找的也就是每2 * k 区间的起点,这样写,程序会高效很多。所以当需要固定规律一段一段去处理字符串的时候,要想想在在for循环的表达式上做做文章。
func reverseStr(s string, k int) string {
ss := []byte(s) //go语言反转数组要改为[]byte, 使用于ascii编码
for i:=0;i k, 要反转前k个
reverse(ss[i:i+k])
} else {
reverse(ss[i:len(ss)]) // 说明当前len(ss) < k, 要反转所有
}
}
return string(ss)
}
func reverse(nums []byte) { // 反转的函数
for i:=0;i
请实现一个函数,把字符串 s 中的每个空格替换成"%20
示例 1: 输入:s = "We are happy."
输出:"We%20are%20happy."
思路就是用一个额外的辅助空间result,然后遍历字符串s,当遍历到的不是' '的时候统统加进去result,当遇到' '的时候加进去%20。
如果想把这道题目做到极致,就不要只用额外的辅助空间了!首先扩充数组到每个空格替换成"%20"之后的大小。然后从后向前替换空格,也就是双指针法,过程如下:i指向新长度的末尾,j指向旧长度的末尾。当i遇到非空格的时候,s[j] = s[i]照常,然后i和j都--。当i遇到空格的时候,s[j] = 0, s[j-1] = 2 , s[j-2] = %,然后i--,j要-3。
有同学问了,为什么要从后向前填充,从前向后填充不行么?
从前向后填充就是O(n^2)的算法了,因为每次添加元素都要将添加元素之后的所有元素向后移动。其实很多数组填充类的问题,都可以先预先给数组扩容带填充后的大小,然后在从后向前进行操作。
这么做有两个好处:
代码1 :使用额外的辅助空间。
func replaceSpace(s string) string {
s_b := []byte(s) // 先将字符串转为[]byte类型
res := []byte{} // 构建额外的辅助切片
for i:=0;i
代码2:不使用额外的辅助空间。
首先扩充数组到每个空格替换成"%20"之后的大小。然后从后向前替换空格,也就是双指针法,过程如下:i指向新长度的末尾,j指向旧长度的末尾。当i遇到非空格的时候,s[j] = s[i]照常,然后i和j都--。当i遇到空格的时候,s[j] = 0, s[j-1] = 2 , s[j-2] = %,然后i--,j要-3。
// 原地修改
func replaceSpace(s string) string {
s_b := []byte(s)
s_b_len := len(s_b)
spaceCount := 0 //用来标记空格数量的
// 计算空格数量
for _, v := range s_b {
if v == ' ' {
spaceCount++
}
}
// 扩展原有切片
resizeCount := spaceCount * 2
tmp := make([]byte, resizeCount) // 新建一个切片,大小为要扩充的大小
s_b = append(s_b, tmp...) // 增加到b中
i := s_b_len - 1 // i为原来字符串长度的最后一个
j := len(s_b) - 1 // j为扩充字符串长度的最后一个
for i >= 0 { // 遍历条件是遍历到原来字符串的第一个,也就更换完第一个为止
if s_b[i] != ' ' { // 不是空格的话,将原来字符串的最后一个填充到新长度的字符串的最后一个值
s_b[j] = s_b[i]
i-- //然后i和j要往左移动
j--
} else { // s_b[i] == ‘ ’, 如果遇到空格,那么扩充长度后的字符串要增加为%20
s_b[j] = '0' // 也就是最后一个是0,要注意是'0',不是int的0
s_b[j-1] = '2' // 然后是2,要注意是'2',不是int的2
s_b[j-2] = '%' // 然后是%
i-- //此时i--往左移
j = j - 3 //此时j要移动三个位置。
}
}
return string(s_b)
}
给定一个字符串,逐个翻转字符串中的每个单词。
示例 1:
输入: "the sky is blue"
输出: "blue is sky the"
今天解决翻转字符串中的单词,估计很多小婊贝一看到这个题,脑子里第一反应就是水题。因为很多的编程语言都自带有库函数,几行代码就可以解决。但是不行呀,这么玩儿这道难度中等的题,水题的帽子就带实了。刷题初期还是要老老实实,少用现成的东西,自己老老实实的实现代码,编写对应的函数。话至于此,接下来我们来开开心心肝题。
想一下,我们将整个字符串都反转过来,那么单词的顺序指定是倒序了,只不过单词本身也倒序了,那么再把单词反转一下,单词不就正过来了。
所以解题思路如下:
1、移除多余空格
2、将整个字符串反转
3、将每个单词反转
举个例子,源字符串为:"the sky is blue "
这样我们就完成了翻转字符串里的单词。下面我会给出用了辅助空间和没用辅助空间的解法。
图解
以 s = " hello world " 为例。根据题目解析中所说,图解分为 3 步。
// 初始化指针
left, right := 0, len(b) - 1
第一步:去除多余空格。
去除多余空格总体来说是 3 个位置:开头、结尾、字符串内。
(1) 去除开头空格
左指针向右跑,碰到第一个不是空格的字符停下。
// 去除开头的空格
for left < right && b[left] == ' '{
left += 1
}
(2) 去除结尾空格
右指针向左跑,碰到第一个不是空格的字符停下。
// 去除结尾的空格
for left < right && b[right] == ' ' {
right -= 1
}
(3) 去除字符串内多余空格
左指针向右跑,如果当前是字符,就往前跑,如果当前是空格,如果前一个不是空格,就往前跑,如果前一个也是空格,那这个空格就删掉,因为多余了。
new_b := []byte{}
// 去除单词间多余的空格
for left <= right {
if b[left] != ' ' {
new_b = append(new_b, b[left])
// 如果当前是空格,且前一个字符不是空格,则添加
} else if left > 0 && b[left] == ' ' && b[left - 1] != ' ' {
new_b = append(new_b, b[left])
}
left++
}
第二步:反转整个字符串
反转字符串大家都很熟了,就是直接把字符串反转过来。
//2.翻转整个字符串
reverse(new_b, 0, len(new_b)-1)
第三个:反转每个单词
//3.翻转单个单词
i := 0
for i < len(new_b) {
j := i
for ; j < len(new_b) && new_b[j] != ' '; j++ {
}
reverse(new_b, i, j-1)
i = j
i++
}
反转函数的编写
func reverse(b []byte, left, right int) {
for left < right {
b[left], b[right] = b[right], b[left]
left++
right--
}
}
所以整体代码如下:
func reverseWords(s string) string {
b := []byte(s)
// 初始化指针
left, right := 0, len(b) - 1
// 去除开头的空格
for left < right && b[left] == ' '{
left += 1
}
// 去除结尾的空格
for left < right && b[right] == ' ' {
right -= 1
}
new_b := []byte{}
// 去除单词间多余的空格
for left <= right {
if b[left] != ' ' {
new_b = append(new_b, b[left])
// 如果当前是空格,且前一个字符不是空格,则添加
} else if left > 0 && b[left] == ' ' && b[left - 1] != ' ' {
new_b = append(new_b, b[left])
}
left++
}
//2.翻转整个字符串
reverse(new_b, 0, len(new_b)-1)
//3.翻转单个单词
i := 0
for i < len(new_b) {
j := i
for ; j < len(new_b) && new_b[j] != ' '; j++ {
}
reverse(new_b, i, j-1)
i = j
i++
}
return string(new_b)
}
func reverse(b []byte, left, right int) {
for left < right {
b[left], b[right] = b[right], b[left]
left++
right--
}
}
上面的的解决方式的时间复杂度显然为 O(n)。下面给出o(1)的解决方法。我还不是很看得懂捏。
func reverseWords(s string) string {
fast := 0
slow := 0
b := []byte(s)
//删除头部空格
for len(b) > 0 && fast < len(b) && b[fast] == ' ' {
fast++
}
//删除中间空格
for ; fast < len(b); fast++ {
if fast-1 > 0 && b[fast-1] == ' ' && b[fast] == ' ' {
continue
}
b[slow] = b[fast]
slow++
}
//删除尾部
if slow-1 > 0 && b[slow-1] == ' ' {
b = b[:slow-1]
} else {
b = b[:slow]
}
//2.翻转整个字符串
reverse(b,0,len(b)-1)
//3.翻转单个单词
i:=0;
for i < len(b) {
j:=i
for ; j < len(b) && b[j] != ' '; j++ {
}
reverse(b,i,j-1)
i=j
i++
}
return string(b)
}
func reverse(b []byte,left,right int ) {
for left < right {
b[left],b[right] = b[right],b[left]
left++
right--
}
}
这个解法好像就是o(1)。
字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。
示例 1:
输入: s = "abcdefg", k = 2
输出: "cdefgab"
为了让本题更有意义,提升一下本题难度:不能申请额外空间,只能在本串上操作。
不能使用额外空间的话,模拟在本串操作要实现左旋转字符串的功能还是有点困难的。
那么我们可以想一下上一题目 151.翻转字符串里的单词中讲过,使用整体反转+局部反转就可以实现,反转单词顺序的目的。这道题目也非常类似,依然可以通过局部反转+整体反转 达到左旋转的目的。
具体步骤为:
最后就可以得到左旋n的目的,而不用定义新的字符串,完全在本串上操作。
例如 :示例1中 输入:字符串abcdefg,n=2
如图:
最终得到左旋2个单元的字符串:cdefgab。思路明确之后,那么代码实现就很简单了
func reverseLeftWords(s string, n int) string {
b := []byte(s)
// 1. 反转前n个字符
// 2. 反转第n到end字符
// 3. 反转整个字符
reverse(b, 0, n-1)
reverse(b, n, len(b)-1)
reverse(b, 0, len(b)-1)
return string(b)
}
// 切片是引用传递
func reverse(b []byte, left, right int){
for left < right{
b[left], b[right] = b[right],b[left]
left++
right--
}
}
内容许多来自代码随想录/李狗蛋。
以上。