Golang单次遍历实现数组旋转的方法探讨
Golang单次遍历实现数组旋转的方法探讨 我正在阅读 《Go 程序设计语言》,作者 Alan Donovan。在第四章中,我在练习 4.4 的某些代码上遇到了困难,该练习要求“编写一个单次遍历即可完成旋转操作的版本?” 我想在左旋转次数方面稍作修改来实现它。
package main
import(
"fmt"
)
func rotate(s []int,shift int){
first := s[:shift]
copy(s,s[shift:]) // 这会改变 first 的值
s[len(s)-shift]=first // 这行不通 !!!
}
func main() {
data := []int{1,2,3,4,5,6}
left_shift := 3
rotate(data,left_shift) // 4 5 6 1 2 3
fmt.Println(data)
}
有人能帮我理解切片的概念吗?
更多关于Golang单次遍历实现数组旋转的方法探讨的实战教程也可以访问 https://www.itying.com/category-94-b0.html
是的。在我的情况下,我没有得到预期的行为。
更多关于Golang单次遍历实现数组旋转的方法探讨的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
我在文档中找到了一篇关于此行为的不错的博客文章:https://blog.golang.org/slices#TOC_4.
我知道 append 函数说明,如果长度不变,它会修改底层数组,但在你的 Playground 示例中,这似乎并未发生。
切片是指向特定结构的指针(https://blog.golang.org/slices-intro),因此 first 只是一个指向切片内容的指针。如果你修改它,first 的内容也会改变,因为它指向相同的地址。
所以你可以使用以下代码来实现你想要的功能:
func rotate(s []int, shift int) {
slc3 := make([]int, 0)
slc3 = append(slc3, s[shift:]...)
slc3 = append(slc3, s[:shift]...)
copy(s, slc3)
}
因为它指向相同的地址。 所以你可以使用这段代码来完成你想做的事情:
经过更多阅读后,我理解了切片的工作原理。因此,我也编写了我的旋转函数版本,其空间复杂度为 O(n)。
// space is O(n)
func rotate(s []int,shift int)[]int{
var newdata []int
first := s[:shift]
newdata = append(newdata,s[shift:]...)
newdata = append(newdata,first...)
return newdata
}
func main(){
data := []int{1,2,3,4,5,6}
left_shift := 3
fmt.Println(rotate(data,left_shift)) // 4 5 6 1 2 3
}
我一直在尝试用 O(1) 的空间复杂度实现相同的功能。这可能吗?
我提出了一种原地旋转的 O(1) 空间复杂度解决方案。
其基本思路是:当你将第一个元素向左移动 r 个位置时,保存被替换位置的值,然后继续将元素向左移动 r 个位置进行替换,直到替换回第一个元素为止。
接着,对第二个元素重复此操作,依此类推,直到处理完 r 个元素,此时数组中的所有值都已旋转完毕。这总共只需要 len(s) 次数组修改操作。
package main
import (
"fmt"
)
func main() {
s := []int{0, 1, 2, 3, 4, 5}
fmt.Println(s) // "[0 1 2 3 4 5]"
// 将 s 向左旋转两个位置。
rotate(s, 2)
fmt.Println(s) // "[2 3 4 5 0 1]"
}
// rotate 原地将切片 s 向左旋转 shift 个位置。
func rotate(s []int, shift int) {
for i, v := range s[:shift] {
for j := i + len(s) - shift; j >= 0; j -= shift {
s[j], v = v, s[j]
fmt.Printf("i=%d, j=%d, v=%d, s=%v\n", i, j, v, s)
}
}
}
输出:
[0 1 2 3 4 5]
i=0, j=4, v=4, s=[0 1 2 3 0 5]
i=0, j=2, v=2, s=[0 1 4 3 0 5]
i=0, j=0, v=0, s=[2 1 4 3 0 5]
i=1, j=5, v=5, s=[2 1 4 3 0 1]
i=1, j=3, v=3, s=[2 1 4 5 0 1]
i=1, j=1, v=1, s=[2 3 4 5 0 1]
[2 3 4 5 0 1]
要实现单次遍历完成数组旋转,关键在于理解切片底层数组的共享机制。以下是两种高效的单次遍历实现方法:
方法一:三次反转法(最优解)
func rotate(s []int, shift int) {
n := len(s)
shift %= n // 处理 shift 大于数组长度的情况
// 反转前 shift 个元素
reverse(s[:shift])
// 反转剩余元素
reverse(s[shift:])
// 反转整个切片
reverse(s)
}
func reverse(s []int) {
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
s[i], s[j] = s[j], s[i]
}
}
方法二:使用额外空间单次遍历
func rotate(s []int, shift int) {
n := len(s)
shift %= n
// 创建临时切片保存需要移动的元素
temp := make([]int, shift)
copy(temp, s[:shift])
// 单次遍历移动元素
for i := 0; i < n-shift; i++ {
s[i] = s[i+shift]
}
// 将临时切片中的元素放回末尾
for i := 0; i < shift; i++ {
s[n-shift+i] = temp[i]
}
}
原代码问题分析
你的代码问题在于:
first := s[:shift]创建的是底层数组的引用,不是独立副本copy(s, s[shift:])会覆盖first引用的内存区域s[len(s)-shift] = first语法错误,不能将切片赋值给整数元素
切片概念说明
// 切片是数组的视图,共享底层数组
data := []int{1,2,3,4,5,6}
slice1 := data[:3] // [1,2,3],与 data 共享底层数组
slice2 := data[3:] // [4,5,6],同样共享底层数组
// 修改 slice1 会影响 data
slice1[0] = 99
fmt.Println(data) // [99,2,3,4,5,6]
推荐使用三次反转法,时间复杂度 O(n),空间复杂度 O(1),且只遍历数组一次(每个元素被访问两次)。


