算法day7

算法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/2
k * 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。

  • 23
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值