代码随想录day16 || 513 树左下角值,112 路径之和,116 中序后序遍历构造二叉树
切片传递问题
question: 什么情况下传递切片,什么情况下传递切片指针,为什么有时候会修改原始副本,有时候又不会呢?
type sli []int
func main() {
slice := []int{1}
fmt.Printf("slice: %p\n", slice)
change1(slice)
fmt.Println("=================================")
s2 := sli{2}
fmt.Printf("sli: %p\n", s2)
s2.change2()
fmt.Printf("after change2 s2: %v, s2: %p\n", s2, s2)
s2.change3()
fmt.Printf("after change3 s2: %v, s2: %p\n", s2, s2)
}
func change1(l []int) {
fmt.Printf("slice: %p\n", l)
l = append(l, 1)
fmt.Printf("after append slice: %p\n", l)
}
func (s sli) change2() {
fmt.Printf("sli: %p\n", s)
s = append(s, 1)
fmt.Printf("after append sli: %p\n", s)
}
func (s *sli) change3() {
fmt.Printf("sli: %p\n", *s)
*s = append(*s, 1)
fmt.Printf("after append sli: %p\n", *s)
}
//slice: 0xc0000120c0
//slice: 0xc0000120c0
//after append slice: 0xc0000120d0
//=================================
//sli: 0xc0000120e0
//sli: 0xc0000120e0
//after append sli: 0xc0000120f0
//after change2 s2: [2], s2: 0xc0000120e0
//sli: 0xc0000120e0
//after append sli: 0xc000012100
//after change3 s2: [2 1], s2: 0xc000012100
func main() {
slice := []int{1, 2}
fmt.Printf("slice: %p\n", slice)
change1(slice)
fmt.Printf("after change2 slice: %v, slice: %p\n", slice, slice)
fmt.Println("=================================")
s2 := sli{2}
fmt.Printf("sli: %p\n", s2)
s2.change2()
fmt.Printf("after change2 s2: %v, s2: %p\n", s2, s2)
s2.change3()
fmt.Printf("after change3 s2: %v, s2: %p\n", s2, s2)
}
func change1(l []int) {
fmt.Printf("slice: %p\n", l)
l[1] = 3
fmt.Printf("after append slice: %p\n", l)
}
func (s sli) change2() {
fmt.Printf("sli: %p\n", s)
s[1] = 3
fmt.Printf("after append sli: %p\n", s)
}
func (s *sli) change3() {
fmt.Printf("sli: %p\n", *s)
(*s)[1] = 3
fmt.Printf("after append sli: %p\n", *s)
}
//slice: 0xc0000a4010
//slice: 0xc0000a4010
//after append slice: 0xc0000a4010
//after change2 slice: [1 3], slice: 0xc0000a4010
//=================================
//sli: 0xc0000a4040
//sli: 0xc0000a4040
//after append sli: 0xc0000a4040
//after change2 s2: [1 3], s2: 0xc0000a4040
//sli: 0xc0000a4040
//after append sli: 0xc0000a4040
//after change3 s2: [1 3], s2: 0xc0000a4040
-
总结
对于切片类型作为函数参数,以及切片的值类型方法,本质上都是传递了一份指向原始切片底层数组的副本,所以,如果函数或者方法内部堆切片操作没有开辟新的空间(比如append操 作),而是在切片内部进行操作(比如交换元素),那么就不会修改副本指向,所以此时对于副本的修改能够作用到原始切片,反之,则不会对于指针类型的引用方法,如果方法内部开辟了新的内存空间,那么由于指针类型方法指向的是原始切片的指针,所以,修改之后指向了新的切片指针,指针类型的方法会重新分配切片, 原始切片也会指向新的底层数组。同时也会使得原始切片指向新的地址,达到内部修改影响到原始切片
531 树最左叶子节点
func findBottomLeftValue(root *TreeNode) int {
// 思路,层序遍历,记录每一层最左侧
q := list.New()
size := 1
left := root.Val
q.PushBack(root)
for q.Len() > 0 {
var count int
for i:=0; i<size; i++{ // 遍历每一层
node := q.Remove(q.Front()).(*TreeNode)
if i == 0 {
left = node.Val
}
if node.Left != nil {
q.PushBack(node.Left)
count++
}
if node.Right != nil {
q.PushBack(node.Right)
count++
}
}
size = count
}
return left
}
// 时间每个节点遍历一次n, 空间队列n
var dep int
var res int
func findBottomLeftValue(root *TreeNode) int {
// 迭代回溯
dep, res = -1, 0 // 此处不重新赋值,多次测试会引用上次测试的值
traversal(root, 0)
return res
}
func traversal(root *TreeNode, d int) { // 分别传入节点以及路径(用来回溯),返回值为叶子节点的深度
if root.Left == nil && root.Right == nil && d > dep {
dep = d
res = root.Val
return
}
if root.Left != nil {
d++
traversal(root.Left, d)
d--
}
if root.Right != nil {
d++
traversal(root.Right, d)
d-- // 回溯到上一个节点的深度
}
}
// 想到了回溯,但是没想好参数应该怎么放置,这里的参数放到了全局变量中,不用作为递归函数参数加入
112 路径之和
func hasPathSum(root *TreeNode, targetSum int) bool {
// 思路,回溯算法,保存路径数组
if root == nil {
return false
}
return pathSum(root, &[]int{}, targetSum)
}
//在递归函数中,如果你需要修改切片本身(例如,重新分配或扩展切片),你应该传递切片的指针,或者使用全局变量。
func pathSum(root *TreeNode, path *[]int, target int) bool {
// 题设要求从根节点遍历到叶子节点,所以判断为叶子节点就是递归终止条件
// 前序,中左右
*path = append(*path, root.Val)
fmt.Println(*path)
if root.Left == nil && root.Right == nil {
if sum(*path) == target {
return true
}
return false
}
var res bool
// 单次递归逻辑
if root.Left != nil {
res = pathSum(root.Left, path, target)
if res == true {
return true // 至少找到一条路径和
}
// 递归里面会把root.Left.Val 加入到路径中,为了之后的顺序逻辑保持同一层,所以需要回溯
*path = (*path)[0 : len(*path) - 1]
}
if root.Right != nil {
res = pathSum(root.Right, path, target)
if res == true {
return true // 至少找到一条路径和
}
*path = (*path)[0 : len(*path) - 1]
}
return res
}
func sum(li []int) int {
var s int
for _, v := range li {
s += v
}
return s
}
116 通过中后序构造二叉树
func buildTree(inorder []int, postorder []int) *TreeNode {
// 1,后序数组的末尾元素就是根节点
// 2,通过根节点遍历中序数组(左根右),所以根节点左边就是左子树,右边就是右子树
// 3,切割完成中序数组之后得到左右子树的节点个数(数组长度),然后将后序遍历(左右根)根节点前面元素按照长度划分左子树右子树
// 3,递归传入左右子树
// 递归终止条件
if len(postorder) == 0 {
return nil
}
value := postorder[len(postorder) - 1]
var root = &TreeNode{value, nil, nil}
if len(postorder) == 1 { // 此时就是叶子节点
return root
}
// 2, 切割中序数组[0: len()-1] ==> [0: idx], idx, [idx+1: len()-1]
var inLeft, inRight []int
for i := 0; i < len(inorder); i++ {
if inorder[i] == root.Val{ // 数组值等于根节点的值,此时就是根节点位置
inLeft = inorder[0 : i]
inRight = inorder[i+1 : len(inorder)]
break
}
}
// 3, 根据左中序数组长度,切割后序数组 [0: len-1] ==> [0: len(inleft)], [len(inleft), len()-2], len()-1, 之后一个元素为根节点
var postLeft, postRight []int
postLeft = postorder[0 : len(inLeft)]
postRight = postorder[len(inLeft) : len(postorder) - 1]
//fmt.Println(inLeft, inRight, postLeft, postRight)
// 递归遍历左右子树
root.Left = buildTree(inLeft, postLeft)
root.Right = buildTree(inRight, postRight)
return root
}
// 看了视频思路非常清晰

浙公网安备 33010602011771号