第二轮 Python 刷题笔记二:树

本文详细介绍了在第二轮LeetCode刷题中,针对树结构的五个题目,包括二叉树的前序、中序、后序遍历,以及N叉树的前序、后序遍历。作者强调了理解树的递归特性和掌握递归、迭代解法的重要性,并提供了相关题目解题思路和代码实现。在递归解法中,作者对比了不同遍历顺序,并讨论了如何通过栈来模拟递归过程。在迭代解法中,作者引入了颜色标记法和栈操作,优化了代码实现。最后,作者总结了树遍历的时间复杂度和空间复杂度,强调了熟悉栈在解题中的应用。
摘要由CSDN通过智能技术生成

在第二轮刷题一开始,我们是整理了关于数组的五道 LeetCode 题,按照很多算法训练的顺序,一般紧跟数组的是链表、树,那么我们第二篇就集中来整理下树的题目。之前提到,这是我们第二轮刷题,在先前刷到二叉树题目时,曾经整理过两篇关于树的基础知识点和四种遍历方法:

二叉树专题一:基础知识点,前中后序遍历

二叉树专题二:层级遍历及随机题目

如果只是刷一遍题目,可能了解如上的四种遍历代码模版便足够,但这样缺点很明显:只知道这样写代码,别人一问却不知其背后的道理——换言之,只是会解题而已。今天再次回顾整理树的题目,我们要着眼于解决树问题的理念与思路,吃透其背后的解法。

我们之前了解过树的概念:树是一种抽象数据类型或是实现这种抽象数据类型的数据结构,用来模拟具有树状结构性质的数据集合。它是由 n(n>0) 个有限结点组成一个具有层次关系的集合。

树图示

当树中每个节点最多只有两个分支、或子节点时,便是我们常说的二叉树。

当树是一棵空树;或其作为二叉树,其左子树上所有结点值均小于根结点值,其右子树上所有结点值均大于根结点值,且其左子树和右子树也符合如上规律时,我们称该树为二叉搜索树。

通常关于二叉树题目的解答区域内,也会有如下关于树结点的定义,一个 TreeNode 类,在其实例化时会定义父结点值 val,左子结点 left 和右子结点 right:

# Definition for a binary tree node.
class TreeNode:
    def __init__(self, x):
        self.val = x
        self.left = None
        self.right = None

定义中子结点均为 None,但真到题目中子结点也会是 TreeNode 的实例。此外,题目中多用根结点 root 来代表一棵树作为函数的输入,因为通过该根结点我们可以向下展开得到树中所有结点;或者用一个遍历各结点的列表来作为输入,我们把它当成其树中各结点组成的列表即可。

了解完如上关于树的基本概念后,我们也要明确树的“意义”。有数组、链表这些数据结构,为什么还要引入树这种结构来存储数据呢?这是因为数组、链表只能一维储存数据,即当前数据往下走,没有分支可选;但我们生活中,很多数据的下一个状态是个多项选择,比如我们漫漫人生路,每次面临抉择,是可以做多项选择的,这种面临选择的时刻我们即处于一个父结点,面对的选项为各种子结点,不过可惜的是树中可以回溯到上一结点,但人生却是单项的。

通过之前第一轮刷题时,也很明显感受到,二叉树的题解中出现递归的频率非常高,这是因为树的结构就是由一个父结点不断重复产生其子结点,与递归不断重复执行相同过程是一致的。所以结合着树的题目来练习递归解法,不仅可以加深对递归理解,也能够对树的结构和特定有更清晰的理解。

接下来我们重新回顾之前的题目,先尝试递归实现,再分析之前发现的套路模版,进一步加深对各题目的印象,这样之后遇到题目就不再畏惧其引申与变形了。

这次我们还是实时记录回顾各题目的思路,5-10 分钟考虑,没头绪就看题解,等消化了再来重新做,直到掌握。

题目一

第 144 题:二叉树的前序遍历

难度:中等

给定一个二叉树,返回它的前序遍历。

示例:

输入: [1,null,2,3]  
   1
    \
     2
    /
   3 

输出: [1,2,3]

自行尝试

首先回顾下前序遍历,这里前指根结点和子结点之间的位置关系,根-左-右,根结点位于子结点前面,所以为前序遍历:

前序遍历图示
如图,从根结点开始,最初根-左-右为 [8,3,10],但紧接着 3 作为左子树的根结点要同样进行前序遍历,这里就相当于对该结点调用前序遍历形成递归,同理对 10 这个右子结点也要进行前序遍历来填充我们的遍历列表。这样分析下来便可以写出递归实现代码:

# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def preorderTraversal(self, root: TreeNode) -> List[int]:
    	# 定义前序遍历的函数
        def helper(node):
        	# 终止条件,空结点返回空列表
            if not node:
                return []
            # 返回 根-左-右,根为根结点处值所在列表,左和右则递归执行 helper 函数,将三个结果列表加起来即最终结果
            return [node.val] + helper(node.left) + helper(node.right)
		# 从根结点开始执行该函数,将结果返回
        return helper(root)

当然,如果不想额外定义这函数,也可以直接在 preorderTraversal 这方法基础上进行递归:

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def preorderTraversal(self, root: TreeNode) -> List[int]:
        if not root:
            return []
        return [root.val] + self.preorderTraversal(root.left) + self.preorderTraversal(root.right)

还记得我们之前看到的颜色标记法模版吗?在二叉树专题一里,我们用其完成了前中后序遍历,此时有了递归实现遍历的经验之后,便可以发现其实现原理是自己建一个栈(可以理解为列表,但特点是后进的先出),来模拟递归过程来实现对各结点的全记录。

比如简单些只有 [8,3,10] 三个结点,我们建立的栈只要按照相反的顺序收录 右-左-根 结点,再通过栈输出,即可得到前序遍历结果。那如何区分哪些结点还要进行前序遍历、那些结点直接输出值呢?这就是颜色标记的作用,初次收录该结点时,该结点是以子结点形式出现的、还未进行前序遍历,当再次遇到该结点时、其已为父结点要输出值了。颜色标记即通过数字 1 和 0 来标记其出现的情况,代码实现:

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def preorderTraversal(self, root: TreeNode) -> List[int]:
    	# 以 (状态,结点) 的元组形式记录每个结点,最初栈中只有根结点
        stack = [(1,root)]
        result = []
        # 若栈非空,则循环
        while stack:
        	# 通过列表的 pop() 来实现栈的后进先出
            s,node = stack.pop()
            # 空结点跳过
            if node is None:
                continue
            # 若标记为 1,要对其进行遍历,按前序的相反顺序记录结点
            if s==1:
            	# 前序的倒序记录,子结点标记为 1,已经标过 1 的父结点状态置为 0
                stack.append((1,node.right))
                stack.append((1,node.left))
                stack.append((0,node))
            # 若标记为 0,将结点值记录到结果列表中
            else:
                result.append(node.val)
        # 返回结果列表
        return result

注意,这道题目要求以列表形式输出结果,如果还有其它操作,完全可以在 result.append(node.val) 这一步中去处理、实现,这样就不必等到所有结果全部拿到才去做处理。目前遇到不少题目都是问到做些判断,如何加速,便可在这里去实现。

学习题解

我们再来翻翻国外大神点赞最高的题解:

def preorderTraversal(self, root):
    ret = []
    stack = [root]
    while stack:
        node = stack.pop()
        if node:
            ret.append(node.val)
            stack.append(node.right)
            stack.append(node.left)
    return ret
# 来源:https://leetcode.com/problems/binary-tree-preorder-traversal/discuss/45273/Very-simple-iterative-Python-solution

哈哈哈,看过这代码,就会发现我们之前的颜色标记法代码的繁琐之处,在 s 为 1 时我们最后把 (0,node) 塞到了 stack 中,然而下一个循环就会通过 stack.pop() 将其取出,再进入 s 为 0 把 node.val 加入到结果列表中。那么,我们就完全如同如上题解代码中这般,去掉这个添加、再取出过程,直接将其添加到结果列表中!

这样一来,我们的代码可以更简洁了!但是,这里可以精简是因为栈中对父结点的处理恰好连在一起可以省略,若是中序、后序遍历还要看情况!

题目二

第 94 题:二叉树的中序遍历

难度:中等

给定一个二叉树,返回它的中序 遍历。

示例:

输入: [1,null,2,3]
   1
    \
     2
    /
   3

输出: [1,3,2]

自行尝试

前序遍历是 根-左-右,到了中序遍历,即根结点要位于子结点中间,变成了 左-根-右:

中序遍历图示
有了之前的经验,我们直接上手代码,先是递归实现:

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def inorderTraversal(self, root: TreeNode) -> List[int]:
        if not root:
            return []
        return self.inorderTraversal(root.left) + [root.val] + self.inorderTraversal(root.right)

只是调整了下根结点和子结点递归调用的位置顺序。那么接下来我们来先看下标记法能否应用刚习得的精简方法:

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def inorderTraversal(self, root: TreeNode) -> List[int]:
    	# 状态标记法实现
        stack = [(1,root)]
        result = []
        while stack:
            s,node = stack.pop()
            if not node:
                continue
            if s:
                stack.append((1,node.right))
                stack.append((0,node))
                stack.append((1,node.left))
            else:
                result.append(node.val)
        return result

注意,当 s 标记为 1 时,最后栈中加入的是 (1,node.left),那么接下来 pop() 出的也是该结点,无法与 s 为 0 状态相连,所以就没法按照之前看到的精简代码来实现。

学习题解

我们再去看看这次国外大神是否还有惊喜:

# recursively 递归
class Solution:
	def inorderTraversal1(self, root):
	    res = []
	    self.helper(root, res)
	    return res
	    
	def helper(self, root, res):
	    if root:
	        self.helper(root.left, res)
	        res.append(root.val)
	        self.helper(root.right, res)

我们之前的递归形式是 “左子结点递归调用 + [node.val] + 右子结点递归调用”,看着简洁,但如果想再其间做些额外处理可能难以拆分,上面这份递归代码就将该过程拆分,但需要额外将 res 这个列表参数进行传递。

# iteratively       
def inorderTraversal(self, root):
    res, stack = [], []
    while True:
    	# 深度遍历最左一侧所有结点
        while root:
            stack.append(root)
            root = root.left
        # 若栈为空,直接返回空列表
        if not stack:
            return res
        # 逐个返回栈中的左结点
        node = stack.pop()
        # 结果中添加左结点
        res.append(node.val)
        # 若有右结点,赋值过来再次代入循环遍历该结点以下的左结点
        root = node.right
# 来源:https://leetcode.com/problems/binary-tree-inorder-traversal/discuss/31381/Python-recursive-and-iterative-solutions.

这代码看着像是深度优先搜索,先取最左侧所有左子结点,取到尽头,取出结点存入结果,若有右子结点,则进入到右子结点遍历其所有最左侧左子结点;若无右子结点,则通过 stack.pop() 回溯到当前的根结点,再去找其右子结点,从而实现按 左-根-右 的中序遍历记录各结点值。

这解法看看就好,感觉理解不到位的话还挺难写的。。

题目三

第 145 题:二叉树的后序遍历

难度:困难

给定一个二叉树,返回它的 后序 遍历。

示例:

输入: [1,null,2,3]  
   1
    \
     2
    /
   3 

输出: [3,2,1]

自行尝试

老样子,后序即根结点位于子结点之后,按 左-右-根 的顺序遍历:

后序遍历图示
这篇文章中我已经刻意练习三遍了,但没写文章时每次重温题目都会手敲一遍,这样才牢记于心,递归实现:

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def postorderTraversal(self, root: TreeNode) -> List[int]:
        if not root:
            return []
        return self.postorderTraversal(root.left)+self.postorderTraversal(root.right)+[root.val]

状态标记法:

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def postorderTraversal(self, root: TreeNode) -> List[int]:
        result = []
        stack = [(1,root)]
        while stack:
            s,node = stack.pop()
            if not node: continue
            if s:
                stack.append((0,node))
                stack.append((1,node.right))
                stack.append((1,node.left))
            else:
                result.append(node.val)
        return result

可以看到 s 状态为 1 时栈中最后加入的是 (1,node.left),还是无法与之后的 s 状态 0 相连,没法套用前序遍历时的精简处理。

学习题解

继续观摩国外大神点赞最高的解法,同一个大神带来的两个解法:

class Solution:
    # @param {TreeNode} root
    # @return {integer[]}
    def postorderTraversal(self, root):
        traversal, stack = [], [(root, False)]
        while stack:
            node, visited = stack.pop()
            if node:
                if visited:
                    # add to result if visited
                    traversal.append(node.val)
                else:
                    # post-order
                    stack.append((node, True))
                    stack.append((node.right, False))
                    stack.append((node.left, False))
        return traversal

嗯,与我们的状态标记法是一致的,用 True 和 False 来标记状态,但是第二个解法,给跪了!

class Solution:
    # @param {TreeNode} root
    # @return {integer[]}
    def postorderTraversal(self, root):
        traversal, stack = [], [root]
        while stack:
            node = stack.pop()
            if node:
                # pre-order, right first
                traversal.append(node.val)
                stack.append(node.left)
                stack.append(node.right)

        # reverse result
        return traversal[::-1]
# 来源:https://leetcode.com/problems/binary-tree-postorder-traversal/discuss/45785/Share-my-two-Python-iterative-solutions-post-order-and-modified-preorder-then-reverse

是不是似曾相识,采用前序遍历然后取倒序?那么问题来了,我们上面三张图示中,同一棵二叉树,前序遍历拿到的结果是 [8,3,1,6,4,7,10,14,13],后续遍历拿到的结果
[1,4,7,6,3,13,14,10,8],怎么就相反了?

注意看,这里 stack 是先添加 node.left 再添加 node.right;而最初前序遍历中是按 根-左-右 的倒序、即先添加 node.right 再添加 node.left 的!我们可以将上面这方法理解为颜色标记法中在 s 为 1状态时最后 stack.append((0,node)) 然后接下来又立即在 s 状态 0 时将其取出,这样栈中添加结点顺序为 左-右-根,那么倒序记录到结果列表中成了 根-右-左,最后再取倒序又变成了 左-右-根!

或者不借助状态标记,在这段代码中,stack 栈中加子结点的顺序是先左后右,那么相应地加到结果列表中的顺序是 根-右-左,最后倒序得到 左-右-根,这样考虑更清晰。这么写够清晰简洁,但就怕熟悉度不够的话与前序遍历搞混、以及最后忘记倒序等。

题目四

第 589 题:N叉树的前序遍历

难度:简单

给定一个 N 叉树,返回其节点值的前序遍历。

例如,给定一个 3叉树 :

N叉树图示
返回其前序遍历: [1,3,5,6,2,4]。

自行尝试

LeetCode 答题区域注释代码中,有对 N 叉树类的定义:

"""
# Definition for a Node.
class Node:
    def __init__(self, val=None, children=None):
        self.val = val
        self.children = children
"""

显然,这里 children 是子结点列表,可以通过自测代码打印出来。这么看来 N 叉树只不过将二叉树左右两子结点 left、right 双变量换成了一个不定个数的列表,其处理过程是一致的。

首先递归,沿用熟悉的结果列表与递归调用相加的形式:

class Solution:
    def preorder(self, root: 'Node') -> List[int]:
        if not root:
            return []
        # 前序遍历,根在前
        result = [root.val]
		# 遍历子结点,递归调用并与resul整合到一起
        for itm in root.children:
            result+=self.preorder(itm)
        # 返回结果列表
        return result

通过状态标记以及栈的迭代实现:

class Solution:
    def preorder(self, root: 'Node') -> List[int]:
        stack = [(1,root)]
        result=[]
        while stack:
            s,node = stack.pop()
            if not node:
                continue
            if s:
            	# 前序为根-子结点,倒序为 子结点列表倒序-根
                for itm in node.children[::-1]:
                    stack.append((1,itm))
                stack.append((0,node))
            else:
                result.append(node.val)
        return result  

结合着二叉树前序遍历时那一版精简代码,我们可以去掉标记,代码简化为如下:

class Solution:
    def preorder(self, root: 'Node') -> List[int]:
        stack = [root]
        result = []
        while stack:
            node = stack.pop()
            if node:
                result.append(node.val)
                for itm in node.children[::-1]:
                    stack.append(itm)
        return result

学习题解

继续翻看国外大神点赞最高的题解:

class Solution:
    def preorder(self, root: 'Node') -> List[int]:
        ret, q = [], root and [root]
        while q:
            node = q.pop()
            ret.append(node.val)
            q += [child for child in node.children[::-1] if child]
        return ret

基本与我们精简后代码一致,不同点在于其向栈中添加时加了判断,这样就源头是避免 None 入栈,也不用后续检查,这一点可以借鉴。推子结点入栈时,采用列表解析先生成新的列表,再直接列表拼接,值得学习。

题目五

第 590 题:N叉树的后序遍历

难度:简单

给定一个 N 叉树,返回其节点值的后序遍历。

例如,给定一个 3叉树 :

N叉树图示
返回其后序遍历: [5,6,3,2,4,1].

自行尝试

后续遍历,即 子结点-根 的顺序,注意子结点位于 children 列表中,其余处理方式也同二叉树的后序遍历。

首先是递归法:

class Solution:
    def postorder(self, root: 'Node') -> List[int]:
        if not root:
            return []
        result = []
        for itm in root.children:
            result += self.postorder(itm)
        result += [root.val]
        return result

通过状态标记、栈实现的迭代:

class Solution:
    def postorder(self, root: 'Node') -> List[int]:
        result = []
        stack = [(1,root)]
        while stack:
            s,node = stack.pop()
            if not node:
                continue
            if s:
                stack.append((0,node))
                for item in node.children[::-1]:
                    stack.append((1,item))
            else:
                result.append(node.val)
        return result

学习题解

仍然是一位大神带来的递归、迭代双解法:

def postorder(self, root):
        """
        :type root: Node
        :rtype: List[int]
        """
        res = []
        if root == None: return res

        def recursion(root, res):
            for child in root.children:
                recursion(child, res)
            res.append(root.val)

        recursion(root, res)
        return res

同之前看到的类似,自定义的递归函数会传递 res 这个结果列表,避免我们代码中相加来拼接。

def postorder(self, root):
        res = []
        if root == None: return res

        stack = [root]
        while stack:
            curr = stack.pop()
            res.append(curr.val)
            stack.extend(curr.children)

        return res[::-1]
# 来源:https://leetcode.com/problems/n-ary-tree-postorder-traversal/discuss/155806/Python-iterative-and-recursive-solution-with-explanation

类似于二叉树的后序遍历,之前是按 推出根后按 左-右 顺序添加到栈,再按 根-右-左 的顺序记录到结果列表中,最终通过翻转得出 左-右-根 得到二叉树的后序遍历;类似地,在推出根后按正常顺序将子结点添加到栈,之后通过推出按 根-倒序子结点列表 顺序加入结果列表,最终翻转一下得到 子结点正序-根 的 N 叉树后续遍历结果。

问题六

第 429 题:N叉树的层序遍历

给定一个 N 叉树,返回其节点值的层序遍历。 (即从左到右,逐层遍历)。

例如,给定一个 3叉树 :

N叉树图示

返回其层序遍历:

[
     [1],
     [3,2,4],
     [5,6]
]

自行尝试

对于层序遍历,之前接触二叉树层序遍历时整理了一套广度优先搜索的解法:维护一个记录层结点的列表,最初第一层只有根结点,首先取该列表中结点值组成列表加入到结果中,然后遍历层中结点,依次将其子结点记录以生成下一层的列表,以此来实现迭代。

"""
# Definition for a Node.
class Node:
    def __init__(self, val=None, children=None):
        self.val = val
        self.children = children
"""
class Solution:
    def levelOrder(self, root: 'Node') -> List[List[int]]:
    	# 维护一个层列表,最初只有根结点
        level = [root]
        # 最终结果列表
        result = []
        # 若空树,直接返回空列表
        if not root:
            return []
        # 通过层列表来迭代循环
        while level:
        	# 下一层列表
            new_level = []
			# 取该层结点值组成列表
            level_result = [node.val for node in level if node]
            # 将该层结点值列表加入结果
            result.append(level_result)
			# 遍历层中结点
            for node in level:
            	# 若存在子结点列表,合并到下一层列表中
                if node.children:
                    new_level+= node.children
            # 下一层列表赋值给 level 代入下一循环       
            level = new_level
        # 返回最终结果
        return result

不同的层序遍历题目可能要求输出的格式有区别,比如这里就是每层的值都要放在一个列表中,这就要在遍历每一层结点时做相应处理。上面这解法是我目前较熟悉的,再去看看大神的解法。

学习题解

class Solution(object):
    def levelOrder(self, root):
        q, ret = [root], []
        while any(q):
            ret.append([node.val for node in q])
            q = [child for node in q for child in node.children if child]
        return ret
# 来源:https://leetcode.com/problems/n-ary-tree-level-order-traversal/discuss/148877/Python-5-lines-BFS-solution

注意代码中的 while 循环用到了 any() 函数,这是 Python 的内置函数,其功能如下:

any() 函数用于判断给定的可迭代参数 iterable 是否全部为 False,则返回 False,如果有一个为 True,则返回 True。元素除了是 0、空、FALSE 外都算 TRUE。

any(iterable)
Return True if any element of the iterable is true. If the iterable is empty, return False. Equivalent to:

def any(iterable):
    for element in iterable:
        if element:
            return True
    return False

用在这里最直接的表现就是不用在单独对空树情况进行处理,因为空树时 root 为 None,层列表 [root] 结果为 [None] 其 bool 为 True , any([None]) 则为 False 不会进入迭代过程。同时,这份代码中用两个列表解析式完成我们之前的操作,可以借鉴。

最后,再来看个递归解法:

def levelOrder(self, root: 'Node') -> List[List[int]]:

    def traverse_node(node, level):
        if len(result) == level:
            result.append([])
        result[level].append(node.val)
        for child in node.children:
            traverse_node(child, level + 1)

    result = []

    if root is not None:
        traverse_node(root, 0)
    return result

#作者:LeetCode
#链接:https://leetcode-cn.com/problems/n-ary-tree-level-order-traversal/solution/ncha-shu-de-ceng-xu-bian-li-by-leetcode/

自行构造了可以递归的函数,因为我们对每层的操作是重复性的:取当前层结点值加到列表再添加到结果中,取当前层结点的子结点来构造下一层。将其构造出独立函数,再通过层数来控制,实现递归过程。

树专题小结

首先说下时间复杂度,因为都是对树的遍历,基本遍历到树的结点就完成操作,时间复杂度为 O(n),n 对应树的 n 个结点;输出结果时也都是 n 个结点的数组或列表,空间复杂度也为 O(n)。

本篇我们从树的结构与其递归特性出发,再次回顾了树的前序、中序、后序遍历,从递归和迭代两个角度来解决关于二叉树、N 叉树的五道相关题目;最后也通过一道 N 叉树的层序遍历再次练习了广度优先搜索的用法。如果能熟练掌握以上代码解法,那么基于树遍历引申出的题目应该是可以比较轻松解决的。

通过一系列树的练习,我们掌握了相关遍历题目的解法,同时也应该借鉴诸多大神题解里结合具体题目规律对栈的应用,可能由 Python 学上来的朋友对栈没概念,大致理解也就是列表来实现后进先出的功能,但不得不说很多题目会考察栈这一数据结构的应用,要多熟悉并逐步掌握。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值