Python内存变量中的引用详解

116. 填充每个节点的下一个右侧节点指针

class Solution:
    def connect(self, root: 'Optional[Node]') -> 'Optional[Node]':
        if not root:
            return 
        queue = [root]

        while queue:
            #for i in range(len(queue)):
            node = queue.pop(0)
            #res.append(node.val).           '不需要返回数值?#是数值吗'
            if node.next and node.left and node.right:
                node.right.next = node.next.left

            if node.left and node.right:
                node.left.next = node.right
                queue.append(node.left)
                queue.append(node.right)
                
        return root       
  • 疑问1:这题是要二叉树的每个节点的next指向其兄弟节点,而不是返回节点的值,函数返回的是修改后的root,调用者可以通过遍历树来验证next指针是否连接正确。
  • 疑问2:queue = [root] 存储的是 root 对象的引用(内存地址),而非对象本身,不是节点的值。(当创建一个节点 root 时,Python 会在内存中分配一个对象,并让 root 变量指向这个对象的地址。)
  • queue = [root]  # 列表中存储的是指向 Node 对象的指针
  • 疑问3:Python中,一切变量本质上都是对象的引用,不存在 “直接存储对象本身” 的情况。(万物皆对象:整数、字符串、列表、自定义类(如 Node)等都是对象,每个对象有唯一的内存地址(引用);变量名本质是对象的 “别名”,存储的是对象的内存地址(引用),而非对象的值或副本。
    a = 10
    print(id(a))  # 输出 a 的内存地址(引用)
  • 不可变对象:(数值、字符串、元组)
  • 任何“修改”都会创建新对象;对不可变对象重新赋值,创建新对象,原对象本身不变,新对象引用;
  • 可变对象:(列表、字典、自定义类)
  • 通过引用修改其属性会直接影响对象本身;
  • class MyClass:
        def __init__(self, val):
            self.val = val
    
    obj1 = MyClass(10)
    obj2 = obj1           # obj2 引用同一对象
    obj2.val = 20         # 通过引用修改对象属性
    print(obj1.val)       # 输出:20(对象本身被修改)

    通过id()函数判断,若两个变量id相同,则指向同一对象(引用)

    a = [1, 2, 3]
    b = a
    print(id(a) == id(b))  # 输出:True(同一引用)

    本题另外一种解法:

      def connect(self, root: 'Optional[Node]') -> 'Optional[Node]':
            if not root:
                return root
           "queue 只是遍历过程中临时使用的辅助工具"
            queue = [root]
            while queue:
                level = len(queue)
                for i in range(len(queue)): 
                    node = queue.pop(0)
            "这个时候用level固定值,因为len()调用函数会在此时重新计算长度,而已经pop一个"
                    if i < level - 1:
                        node.next = queue[0]
                    if node.left:
                        queue.append(node.left)
                    if node.right:
                        queue.append(node.right)  
            return root      
  • 疑问:为什么使用queue做遍历的工具;
  • 111. 二叉树的最小深度

     def minDepth(self, root: Optional[TreeNode]) -> int:
            if root is None:
                return 0
        "调用递归加上self.函数"
            level_left = self.minDepth(root.left)
            level_right = self.minDepth(root.right)
        "需要判断==0即空节点的特殊情形"
            if min(level_left,level_right) == 0:
                res = max(level_left,level_right) + 1
            else:
                res = min(level_left,level_right) + 1
            return res

    类函数调用&普通函数调用:

    函数定义在类的内部,第一个参数是 self 时,它被称为实例方法

    1. 通过 self,可以访问类的属性和其他方法。
    2. 每个实例对象都有自己的属性和方法副本,self 确保递归调用的是当前这个实例的方法。

    实例方法必须通过实例对象调用,无法直接通过类名调用(除非使用@classmethod)

    # 正确调用方式
    sol = Solution()
    sol.minDepth(root)  "# self 自动绑定到 sol 实例"
    
    # 错误调用方式(缺少实例)
    Solution.minDepth(root)  # 会报错,缺少 self 参数
    

    若在类外部定义相同的函数,则无需self:

    # 类外部的独立函数(无需 self)
    def minDepth(root: Optional[TreeNode]) -> int:
        if root is None:
            return 0
        ...

    226. 翻转二叉树

    1. 本题错误原因:交换左右孩子,交换的是(引用地址)指针,不是数值
    2. queue只是辅助把树遍历完的一个工具,而题目是想真正的反转这棵树。如果只改变queue的值不是node并不能真正改变这棵树.
    def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
            if not root:
                return None
            
            queue = [root]
            while queue:
                node = queue.pop(0)
                if node.right:
                    queue.append(node.right)
                if node.left:
                    queue.append(node.left)
                node.left, node.right = node.right, node.left
                
            return root
  • 使用node.left = queue[0],node.right = queue[1] 超时
  • 101. 对称二叉树

    二叉树写递归,单层递归逻辑(可以用任一层的根节点做模拟),如使用后序遍历处理的逻辑:

    递归遍历一个二叉树根root:

  • node.left:就会一直左到底5 
  • node.right:从5那层对应的6 
  • node:(树的最后一步数据处理顺序:返回值;比较bool;加法运算)对应的根节点3
  • 左树遍历:左5-右6-根3,向上一层的:左3-右4-根2,
  • (等待同一层右子树的根),但是右子树同样先递归
  • 右树遍历:依然先按照node.left-node.right-node递归到底左6-右5-根3,向上一层的:左4-右3-根2,返回根2,
  • (等待完成)
  • 根:左2-右2
  • 同时递归遍历两个树root1 root2:

    (root1-left, root2-right)按照node.left一直left到底,按照node.right一直right到底;

    root1 = node.left       root2 = node.right

    (node.left-right, node.right-left)按照node.left一直right到底,按照node.right一直left到底;

    递归return,终止递归,返回的是当前层的结果;回溯,逐层返回;(有的需要回溯时候,逐层返回值收集起来;有的只需要把递归走一遍)

    没到终止条件,则继续使用函数递归;

    现在感觉leetcode解题,就是看谁规律掌握的好?找到一种可行的解法。

    找到规律后处理,就是算法,就叫巧妙的解法。

    以及对存储结构的了解,【#】是指返回字符串#?

    作者:weixin_40879974

    物联沃分享整理
    物联沃-IOTWORD物联网 » Python内存变量中的引用详解

    发表回复