算法day7
- 344 反转字符串
- 541 反转字符串II
- 卡码网:54 替换数字
- 151 翻转字符串里的单词
- 卡码网:右边旋转字符串
344 反转字符串
这个比较简单,一个for循环前面和后面做交换就完事了。
时间o(n)
func reverseString(s []byte) {
for i:=0;i<len(s)/2;i++{
s[i],s[len(s)-1-i]=s[len(s)-1-i],s[i]
}
}
当然这个题也可以这样写,后面这种我个人感觉这种双指针要方便一点,反正都是基于交换。
func reverse(runes []rune,left int,right int){
for i,j:=left,right;i<j;i,j=i+1,j-1{
runes[i],runes[j]=runes[j],runes[i]
}
}
- 541 反转字符串II
这个题我写的太臭了,这里就当个反面教材,从这个例子上我思考过后进行了修改。思路基本上就是顺着题目的思路写的。真要说用了什么算法,应该就用了双指针。
第一次写的: (这个是错的)
func reverseStr(s string, k int) string {
transfer:=[]rune(s)
l:=len(transfer)
left :=0
for i:=0;i<l;i++{
if i-left+1 == 2*k{
temp:=(left+i+1)/2
for j:=left;j<temp/2;j++{
transfer[j],transfer[temp-j-1]=transfer[temp-j-1],transfer[j]
}
left = i
}
if i==l-1 && i-left+1<k{
temp1:=(left+i+1)/2
count:=0
for left < temp1{
transfer[left],transfer[i-count]=transfer[i-count],transfer[left]
count++
left++
}
}
if i==l-1 && i-left+1>=k&&i-left+1<2*k{
right:=left+k+1
mid := (left+right)/2
count:=0
for left<mid{
transfer[left],transfer[right-count]=transfer[right-count],transfer[left]
count++
left++
}
}
}
res:=string(transfer)
return res
}
改进版
func reverseStr(s string, k int) string {
runes := []rune(s)
for start := 0; start < len(runes); start += 2 * k {
end := start + k - 1
if end > len(runes)-1 {
end = len(runes) - 1
}
for i, j := start, end; i < j; i, j = i+1, j-1 {
runes[i], runes[j] = runes[j], runes[i]
}
}
return string(runes)
}
分析我怎么改进的
1.我前面我感觉我写的if太多了,而且一步步的往后迭代也太慢了,用的变量也是有点多了,看着就不舒服。所以我就对整体的过程做了修改。还有我之前想的是用双指针那种left在原地,right往后++登记,这样处理其实我后来想想我根本可以不用right来代表后面那个指针,完全可以用right+2k就可以知道后面那个指针了。
现在来看改进:
1.改成一次跨2k,一步一步的迭代其实是没必要的。
2.看看三个题目要求,1:每计数2k就反转前k个,2:剩余字符少于k就全部反转,3:计数少于2k但是大于等于k,还是反转前k个。
所以这里可以进行总结:1,3可以看作一回事,无论怎样我都反转前k个,2就是进行全部反转,所以这里代码改进后也很好写。
3.结合1,2两个改进,由于我每次都跨2k,现在我只要检查2k的一半k,也就是我要反转的子序列的最后那个位置是否合法,进行判断处理即可。也就是判断end := stark+k-1 看这个位置是否满足在数组内,如果不在就把end改到合法范围就行了。这就达到了要么反转前k,要么剩余全部反转的要求。
4.剩下就是简单的交换操作了。
这里也进行一个改进,平时我很喜欢根据下标来计算的那种交换写法,别的写法我可能懒得去想。这里再积累一个更清楚的写法
or i, j := start, end; i < j; i, j = i+1, j-1 {
runes[i], runes[j] = runes[j], runes[i]
}
这样双指针网中间靠,比起我去计算i的变化去交换要显得值观很多。
5.go语法上的使用
字符串内部字符交换操作是一定要转rune切片操作完后再转回string字符串。
主要关注这个转字符串,就是直接string(runes),就是强制类型转换。
for i,j:=start,end;i<j;i,j=i+1,j-1{
runes[i],runes[j]=runes[j],runes[i]
}
这个地方的语法也是很重要,之前我老是想像c++里面那种用,分割开写两个赋值,在go语言中,这必须保证;中是一个完整的赋值语句,也就是i,j:=start,end就是一个常用的完整的赋值语句.
i,j=i+1,j-1这个也是。
下面的交换操作也是。
时间复杂度分析,外层执行次数n/(2k)次,内层每次k/2次
相乘:n/2k * k/2 = n/4 也就是o(n).
替换数字
这个还是很简单,第一次就过了,就是个循环处理。用个res收集结构即可。遇到非数字就append,辅导数字就把number给append到res。
package main
import "fmt"
func main(){
var n string
fmt.Scanln(&n)
str := "number"
insert := []rune(str)
res := []rune{}
for _,v := range n{
if v<='9'&&v>='0'{
res = append(res,insert...)
}else{
res = append(res,v)
}
}
fmt.Println(string(res))
}
时间复杂度分析:
这个代码最坏的时间复杂度是o(n*m)
这与append有关,append在大多数情况下认为是o(1)的,但是当append底层数组需要扩容时,那就会编程o(n),n是新数组的长度,因为这个扩容涉及到分配新数组和旧数组元素的赋值。
版本二
这个题的极致做法,上个版本我们还是用了辅助空间。
来看这个题的解法:
1.先扫描数组,计算之后,扩充数组到每个数字字符换成number之后的大小。扩充的过程实际上就是每加入一个元素,所有元素后移,每个元素往后写的过程。这显然是O(N^2)。
151 翻转字符串里的单词
第一次自己写的版本:
纯粹按照题目的要求顺着写的,感觉就是暴力解法:
import "strings"
func reverseWords(s string) string {
trimmed := strings.TrimSpace(s)
runes :=[]rune(trimmed)
l:= len(trimmed)
res := []string{}
temp:=[]rune{}
for i:=0;i<l;i++{
if runes[i]!=' '{
temp=append(temp,runes[i])
}else if len(temp)>0 {
str:=string(temp)
res = append(res,str)
temp=temp[:0]
}
}
if len(temp) > 0{
res=append(res,string(temp))
}
l1 := len(res)
for i,j:=0,l1-1;i<j;i,j=i+1,j-1{
res[i],res[j] = res[j],res[i]
}
return strings.Join(res," ")
}
思考:
我在分析时间复杂度的时候,对这个append持有疑问,因为通常情况下append的时间复杂度可以认为是O(1),但是一旦append的底层数组树妖扩容,那么append的时间复杂度就会上升到o(N)。
这里的解释:在需要扩容的时候,单词append的时间复杂度会到o(N),但是这种情况发生的概率是比较低的,Go的扩容策略通常会增加当前容量的一定比例(比如增加一半或翻倍),这意味着随着切片不断增长,扩容操作的频率会降低。从长远的角度来看,可以认为append操作可以认为平均的时间复杂度可以认为是均摊o(1)。
综上,时间复杂度这个题代码的时间复杂度分析:O(N),每个循环的复杂度量级只到O(N)。空间也是O(N)
不足:
我认为我用了稍微有点多的库函数,也不知道是好是坏。等下看看题解再进行改进。
改进版
先说思路:
1.先移除多余空格
2.整个字符串反转
3.单词反转
func reverseWords(s string) string {
b := []byte(s)
// 移除前面、中间、后面存在的多余空格
slow := 0
for i := 0; i < len(b); i++ {
if b[i] != ' ' {
if slow != 0 {
b[slow] = ' '
slow++
}
for i < len(b) && b[i] != ' ' { // 复制逻辑
b[slow] = b[i]
slow++
i++
}
}
}
b = b[0:slow]
// 翻转整个字符串
reverse(b)
// 翻转每个单词
last := 0
for i := 0; i <= len(b); i++ {
if i == len(b) || b[i] == ' ' {
reverse(b[last:i])
last = i + 1
}
}
return string(b)
}
func reverse(b []byte) {
left := 0
right := len(b) - 1
for left < right {
b[left], b[right] = b[right], b[left]
left++
right--
}
}
55. 右旋转字符串
之前我做过一个和这个题非常类似的一个题,所以我看到这个题马上就写出来了。整体大反转,然后前k个元素反转,后n-k个再反转就是正确结果了。
也就是三个reverse就结束了。显然时间复杂度是O(N)。
package main
import "fmt"
func reverse(runes []rune,left int,right int){
for i,j:=left,right;i<j;i,j=i+1,j-1{
runes[i],runes[j]=runes[j],runes[i]
}
}
func main(){
var str string
var n int
fmt.Scanln(&n)
fmt.Scanln(&str)
runes :=[]rune(str)
reverse(runes,0,len(runes)-1)
reverse(runes,0,n-1)
reverse(runes,n,len(runes)-1)
fmt.Println(string(runes))
}
写的时候的问题:
我写的时候发现一个问题卡了我好久,我原先的代码直接提交会报数组越界,但是我把这段代码 fmt.Scanf(“%d %s”, &n, &str),改成了fmt.Scanln(&n)
fmt.Scanln(&str)就提交通过了。
原因分析:
对这个Scanf没理解好
题目给的输入是这样的
2
abcdefg
两个输入都有换行。
fmt.Scanf(“%d %s”, &n, &str),这个输入的问题在于,一个整数和一个字符串在同一行,二者之间用空格分隔。
如果我还是想用Scanf来实现这样的输入,那就必须这么做
fmt.Scanf(“%d\n”, &n) // 读取整数后的换行符
fmt.Scanf(“%s”, &str) // 读取字符串
属于是没理解好Scanf。