代码随想录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
}
// 看了视频思路非常清晰
posted @ 2024-08-01 13:08  周公瑾55  阅读(14)  评论(0)    收藏  举报