LeetCode 94. 二叉树的中序遍历
LeetCode 94. 二叉树的中序遍历
这是一道非常基础的模版题,因此就不放出题目描述及输入输出示例和数据范围了。
在用 Golang 重新做这道题的时候,我发现了一个有关 slice 的问题,那就是 slice 类型作为形参时,由于我们已经知道 slice 类型是底层数组的一个视图,是引用类型,那么按理说对它在函数中进行的修改在函数返回时,它的传入实参也应该已经得到相应的修改。
但事实却与预期不符,如果直接以 slice 作为形参,那么无法返回正确的中序遍历结果:
// ❌ 无法得到预期的结果
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/
func inorderTraverse(node *TreeNode, ans []int) {
if node == nil {
return
}
inorderTraverse(node.Left, ans)
ans = append(ans, node.Val)
inorderTraverse(node.Right, ans)
}
func inorderTraversal(root *TreeNode) []int {
ans := []int{}
inorderTraverse(root, ans)
return ans
}
但是当我把形参换位 slice 的指针之后,却能够得到正确的结果,这不禁引发了我的思考:既然 slice 已经是引用类型了,为什么还需要传入指针才能完成对实参的修改呢?
想要回答这个问题,我们需要首先回顾 Golang slice 的底层原理。Golang 的 slice 有三个字段,分别是 unsafe.Pointer
,指向底层数组,以及 len
和 cap
,分别表示当前 slice 的长度以及容量。
因此如果我们朴素地传入一个 slice,并且在函数中调用了 append,那么很有可能 slice 的 len
和 cap
会在函数中被修改,而由于 Golang 只有传值调用,我们传入的 slice 当中只有 unsafe.Pointer
字段指向底层数组的地址,但 len
和 cap
字段是从函数外部通过调用复制进来的。所以在函数内部,底层数组当中的值确实被修改了,函数内部复制的 slice 的 len
和 cap
也被修改了,但是 len
和 cap
的修改对外部不可见。
综上,如果我们传值调用一个 slice 类型,那么它的 len
和 cap
字段仍然是复制进函数的,函数内部对 len
和 cap
的修改与外部实参无关。
所以如果我们想要使用 ans 这个 slice 保存正确的结果,应该传入 slice 的指针:
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/
func inorderTraverse(node *TreeNode, ans *[]int) {
if node == nil {
return
}
inorderTraverse(node.Left, ans)
*ans = append(*ans, node.Val)
inorderTraverse(node.Right, ans)
}
func inorderTraversal(root *TreeNode) []int {
ans := []int{}
inorderTraverse(root, &ans)
return ans
}