问题
困惑,leetcode第538题,写成下面这种,不能得到正确的答案:
class Solution_a:def traverse(self, root, cur_sum):# 思路:中序遍历可以打印出有序的数组,那么反一下,先右后中再左就可以打印出降序的数组,然后再维护一个字段记录累计和if not root:returnself.traverse(root.right, cur_sum)cur_sum += root.valroot.val = cur_sumself.traverse(root.left, cur_sum)returndef convertBST(self, root: Optional[TreeNode]) -> Optional[TreeNode]:global cur_sumcur_sum = 0self.traverse(root, cur_sum)return root
但是改成下面这样(将cur_sum初始化为一个属性),就可以了:
class Solution_a:def __init__(self):self.cur_sum = 0def traverse(self, root):# 思路:中序遍历可以打印出有序的数组,那么反一下,先右后中再左就可以打印出降序的数组,然后再维护一个字段记录累计和if not root:returnself.traverse(root.right)self.cur_sum += root.valroot.val = self.cur_sumself.traverse(root.left)returndef convertBST(self, root: Optional[TreeNode]) -> Optional[TreeNode]:self.traverse(root)return root
思考
本质的区别和原因是什么呢?
第一种写法下,我以为是:我在convertBST函数里定义了一个全局变量cur_sum(后面会醒悟这个以为也是错误的,因为全局变量不会在一个函数内部去定义),但是调用traverse时,cur_sum是以一个参数的形式传入的,当函数内部存在一个与全局变量同名的局部变量时,会使用自己内部的局部变量。举个二叉树的例子,用第一种写法调用的结果是:
如果这个以为是对的话,那我不把cur_sum以参数的形式传入应该就行了,试试看:
报错如下,这说明全局变量就不是这么定义的,不然traverse内部怎么找不到它?
不死心,我尝试再改一下这个定义的位置看看:
依然不行!!还不死心,我再改一下位置,直接放在调用的地方试试:
依然不对!!!说明global 变量名 并不是我以为的用来定义一个全新的全局变量,然后就到处可以用了!global到底怎么用,问问GPT的到如下回复:
在Python中,global关键字用于在函数内部声明一个全局变量,以便在函数内部对全局变量进行修改。
好的,小菜鸡认为自己悟了,再给我一次机会,修改成下图,老老实实的在所有函数外部定义一个全局变量,在traverse中想要更改全局变量时,使用global关键字(如果没有将其声明为全局变量,则在函数内部更改或创建的任何变量都是局部变量),规范操作:
!!!啊啊啊,它终于对了!但是刷leetcode的时候不方便这么写啊,因为调用的代码在外面,不是咱自己写的呢。所以还是老老实实用第二种方法吧。
第二种写法self.cur_sum相当于在一开始,就初始化了一个属性,在类的所有函数里都可以调用(很像全局变量的感觉),所以可以得到正确结果。
总结
- 全局变量的定义:没有在任何函数内部定义,并且具有全局范围的变量;
- 局部变量的定义:在函数内部定义的变量,范围仅限于该函数;
- python中global关键字的用法:是在一个函数内部声明一个全局变量,以便在函数内部对全局变量进行修改,这个全局变量本身已经定义过了,而不是我想当然认为的定义了一个全新的全局变量!‼️如果没有将其声明为全局变量,则在函数内部更改或创建的任何变量都是局部变量。
- 当全局变量和局部变量的命名一样时,在一个函数内部还是会使用自己内部的局部变量;
问题延伸
那怎么理解下面这段代码里root的更新呢?root以一个参数的形式传递给了traverse,在traverse的内部修改了节点的value,这些修改的操作最终在convertBST函数里返回,为什么也可以生效?(原地修改的原理)
因为root是可变对象,具体见下图。在函数内部修改树的节点值时,即使没有显式的返回root,原始的root也会被更新。
class Solution_a:def __init__(self):self.cur_sum = 0def traverse(self, root):# 思路:中序遍历可以打印出有序的数组,那么反一下,先右后中再左就可以打印出降序的数组,然后再维护一个字段记录累计和if not root:returnself.traverse(root.right)self.cur_sum += root.valroot.val = self.cur_sumself.traverse(root.left)returndef convertBST(self, root: Optional[TreeNode]) -> Optional[TreeNode]:self.traverse(root)return root
参考资料
- Python全局变量和局部变量
- Python中的值类型与引用类型