二叉树总结

leetcode 二叉树题目总结


二叉树结构:

 class TreeNode {
	     int val;
	     TreeNode left;
	     TreeNode right;
	     TreeNode() {}
	     TreeNode(int val) { this.val = val; }
	     TreeNode(int val, TreeNode left, TreeNode right) {
	         this.val = val;
	         this.left = left;
	         this.right = right;
	     }
}

一.基本问题

遍历

前序遍历

leetcode 144 二叉树的前序遍历

public class Test {
	public static void main(String[] args) {
		TreeNode node1=new TreeNode(1);
		TreeNode node3=new TreeNode(3);
		TreeNode node2=new TreeNode(2,node1,node3);
		TreeNode node6=new TreeNode(6);
		TreeNode node=new TreeNode(5,node2,node6);
		List<Integer> result=firstTreeNode(node);
		for(int i:result){
			System.out.print(i);
		}
		
	}
	//stack方式
	public static List firstTreeNode(TreeNode node){
		Stack<TreeNode> stack=new Stack<TreeNode>();
		List<Integer> result=new ArrayList<Integer>();
		stack.push(node);
		while(!stack.isEmpty()){
			TreeNode curNode=stack.pop();
			if(curNode==null){
				continue;
			}
			int val=curNode.val;
			result.add(val);
			stack.push(curNode.right);
			stack.push(curNode.left);

		}
		return result;
	}


        //递归方式
	public static List firstTreeNode2(TreeNode node){
		if(node==null){
			return null;
		}
		List<Integer> result=new ArrayList<Integer>();
		dfs(node,result);
		return result;
		
		
	}
	public static void dfs(TreeNode node,List<Integer>result){
		if(node==null){
			return;
		}
		int val=node.val;
		result.add(val);
		dfs(node.left,result);
		dfs(node.right,result);
	}
	
}

leetcode 255 验证前序遍历序列二叉搜索树


class Solution {
    public boolean verifyPreorder(int[] arr) {
        Stack<Integer> s = new Stack<>();
        int min = Integer.MIN_VALUE;
        for(int n : arr) {
            //是不是总体递增的【其实此时看的是局部递增】
            if(n<min) return false;
            //n>栈顶,会不断弹出,否则直接push进栈
            while(s.size()>0 && n>s.peek())
                min = s.pop();
            
            s.push(n);
        }
        return true;
    
    }



//先序遍历,从根到左子树递减,碰到递增后,当前字数开始右子树,pop出小于右子树的最大元素,就是当前字数的根,由于先序遍历,后面一定不能有比当前根更小的元素,可以有更大的,二插搜索树有很多中形态,后续有很大的数组可能是最根元素的右子树元素
归并的思想
public boolean check(int[] arr, int i, int j){
    if(i>=j) return true;
    
    int root = arr[i];
    // k为右子树的开始下标,
    int k=i+1;
    while(k<=j && arr[k]<root) k++;
    
    //验证右子树是否都>root
    for(int m=k; m<=j; m++) 
        if(arr[m]<root) return false;
    //如果根合格,看其左右子树是否合格
    return check(arr,i+1,k-1) && check(arr, k, j);
}

后序遍历

遍历顺序是:左右中;可以先求中右左的顺序,再逆序过来(水流按最左子树,最左子树的右子树,最左子树的根,逆向的话最后是刚好相反)


public static List<Integer> lastTreeNode(TreeNode node){
		Stack<TreeNode> stack=new Stack<TreeNode>();
		Stack<Integer> stack2=new Stack<Integer>();
		List<Integer> result=new ArrayList<Integer>();
		if(node==null) return null;
		stack.push(node);
		while(!stack.isEmpty()){
			TreeNode node1=stack.pop();
			if(node1==null) continue;
			stack2.push(node1.val);
			TreeNode left=node1.left;
			TreeNode right=node1.right;
			stack.push(left);
			stack.push(right);
		}
		while(!stack2.isEmpty()){
			result.add(stack2.pop());
		}
		return result;
		
	}

//递归方式后序遍历	
public static List<Integer> lastTreeNode2(TreeNode node,List<Integer> listResult){
		if(node ==null){
			return null;
		}
		lastTreeNode2(node.left,listResult);
		lastTreeNode2(node.right,listResult);
		listResult.add(node.val);
		return listResult;
		
	}

中序遍历

vector<int> inorderTraversal(TreeNode* root) {
    vector<int> ans;
    if(!root) return ans;
    
    stack<TreeNode*> st;
    while(!st.empty()||cur){
        while(cur){
            st.push(cur);
            cur=cur->left;
        }
        TreeNode* cur=st.top();
        st.pop();
        ans.push_back(cur->val);
        cur=cur->right;
    }
    
    return ans;
}



java
public static List<Integer> MidleTreeNode(TreeNode node){
		List<Integer> resultList=new ArrayList<Integer>();
		Stack<TreeNode> stack=new Stack<TreeNode>();
		if(node==null){
			return resultList;
		}
		stack.push(node);
		TreeNode tempNode=node;
		while(!stack.isEmpty()){
			while(tempNode.left!=null){
				stack.push(tempNode.left);
				tempNode=tempNode.left;
			}
			TreeNode curNode=stack.pop();
			resultList.add(curNode.val);
			TreeNode right=curNode.right;
			if(right!=null){
				tempNode=right;
				stack.push(right);
			}
		}
		return resultList;
		
		
		
	}


//递归的方式
public static List<Integer> MidleTreeNode2(TreeNode node,List<Integer> listResult){
		if(node ==null){
			return null;
		}
		MidleTreeNode2(node.left,listResult);
		listResult.add(node.val);
		MidleTreeNode2(node.right,listResult);
		return listResult;
}

leetcode 98 利用中序遍历来验证是否二叉搜索树

解法1(成功,1ms,很快)
递归算法
如果以下四种情况,则返回false,否则为true
1 左节点的最右节点>自己
2 右节点的最左节点<自己
3 调用左节点为参数的这个方法返回false
4 调用右节点

之所以用左节点的最右,如果最右不是左侧子树的max
则在递归左子树时会返回false

package pid098;

import java.util.LinkedList;
import java.util.List;

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
public boolean isValidBST(TreeNode root) {
    if(root==null){
    	return true;
    }
	boolean result=true;
	if(root.left!=null){
		if(getMaxVal(root.left)>=root.val){
			result=false;
		}
	}
	if(root.right!=null){
		if(getMinVal(root.right)<=root.val){
			result=false;
		}
	}
    if(!isValidBST(root.left)){
    	result=false;
    }
    if(!isValidBST(root.right)){
    	result=false;
    }
	
	return result;
    }

public int getMaxVal(TreeNode root){
	while(root.right!=null){
		root=root.right;
	}	
	return root.val;
}
public int getMinVal(TreeNode root){
	while(root.left!=null){
		root=root.left;
	}	
	return root.val;
}

}


方法2
太厉害了!
用中序遍历得到list,只要左边大于右边就ok(二叉搜索树中序遍历是递增数组)

class Solution {
    public boolean isValidBST(TreeNode root) {
          if(root == null){
                return true;
           }
           List<Integer> list = new ArrayList<Integer>();
           inOrder(root,list);

           for(int i=0;i<list.size()-1;i++){
               if(list.get(i) >= list.get(i+1))
                   return false;
           }

           return true;
    }
    private void inOrder(TreeNode root, List<Integer> list){
        if(root == null){
            return ;
        }
        inOrder(root.left,list);
        list.add(root.val);
        inOrder(root.right,list);
    }
}


方法3(别人的)
同样用递归的思想
从根节点开始,往左走,当前节点小于上一步的根节点,再往右走,确定最小节点小于上一步的根节点,每走一步,需要在一个范围中,超出范围即非搜索树

即检验 left是否在 min 与val之间
right在 val与max之间
 public  boolean isValidBST(TreeNode root) {
	 if(root==null){
	    return false;
	  }
	Long min=Long.MIN_VALUE,max=Long.MAX_VALUE;
	return isValidTree(root,min,max);
		   
}
public  boolean isValidTree(TreeNode root,long min,long max){
    if(root==null){
        return true;
    }
    if(root.val<=min||root.val>=max){
		return false;
	}
    return isValidTree(root.left,min,root.val)&&isValidTree(root.right,root.val,max);
}

leetcode 95

给你一个整数 n ,请你生成并返回所有由 n 个节点组成且节点值从 1n 互不相同的不同 二叉搜索树 。可以按 任意顺序 返回答案。

class Solution {
    public List<TreeNode> generateTrees(int n) {
		 	if(n<1){
		 		return new ArrayList();
		 	}
		 	return generateTrees(n,1,n);
	    }
	 
	 
	 public static  List<TreeNode> generateTrees(int n,int start,int end){
		 List<TreeNode> resultNode=new ArrayList<TreeNode>();
		 if(start>end){
			 resultNode.add(null);
			 return resultNode;
		 }
		 if(start==end){
			 resultNode.add(new TreeNode(start));
			 return resultNode;
		 }
		 for(int i=start;i<=end;i++){
			 List<TreeNode> treeNodesLeft=generateTrees(n,start,i-1);
			 List<TreeNode> treeNodesRight=generateTrees(n,i+1,end);
			 for(int k=0;k<treeNodesLeft.size();k++){
				 for(int j=0;j<treeNodesRight.size();j++){
					 TreeNode curNode=new TreeNode(i);
					 curNode.left=treeNodesLeft.get(k);
					 curNode.right=treeNodesRight.get(j);
					 resultNode.add(curNode);
				 }
				 
			 }
			
		 }
		 return resultNode;
	 }
}

leetcode 96二叉树的中序遍

给你一个整数 n ,求恰由 n 个节点组成且节点值从 1n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。

动态规划

 public int numTrees(int n) {

          int [] result=new int [n+1];

          result[0]=1;

           for(int i=1;i<=n;i++){

                for(int j=1;j<=i;j++){

                    result[i]+=(result[j-1]*result[i-j]);

                 }

           }

           return result[n];

        }

leetcode 99 利用中序遍历来恢复二叉搜索树

给你二叉搜索树的根节点 root ,该树中的两个节点被错误地交换。请在不改变其结构的情况下,恢复这棵树。

进阶:使用 O(n) 空间复杂度的解法很容易实现。你能想出一个只使用常数空间的解决方案吗?

class Solution {
   public void recoverTree(TreeNode root) {
		List<Integer> middleTreeList = new ArrayList<Integer>();
		getMiddleTeeeList(root, middleTreeList);
		int[] movedNodes=getMovedNodes(middleTreeList);
		revocerTree(root,movedNodes);

	}

	private void revocerTree(TreeNode root, int[] movedNodes) {
		// TODO Auto-generated method stub
        if(root==null){
            return;
        }
		if(root.val==movedNodes[0]||root.val==movedNodes[1]){
			root.val=root.val==movedNodes[0]?movedNodes[1]:movedNodes[0];
		}
		revocerTree(root.left,movedNodes);
		revocerTree(root.right,movedNodes);
	}

	private int[] getMovedNodes(List<Integer> middleTreeList) {
		// TODO Auto-generated method stub
		int x=-1,y=-1; 
		for(int i=0;i<middleTreeList.size()-1;i++){
			if(middleTreeList.get(i)>middleTreeList.get(i+1)){
				y=middleTreeList.get(i+1);
				if(x==-1){
					x=middleTreeList.get(i);
				}
			}
		}
		return new int[]{x,y};
	}

	private void getMiddleTeeeList(TreeNode root, List<Integer> middleTreeList) {
		// TODO Auto-generated method stub
		if(root==null){
			return;
		}
		getMiddleTeeeList(root.left,middleTreeList);
		middleTreeList.add(root.val);
		getMiddleTeeeList(root.right,middleTreeList);

	}
}

leetcode 230 求二叉搜索树的第k小元素
先求出中序序列,返回第k个即可

class Solution {
    public int result=0;
    public int kthSmallest(TreeNode root, int k) {
        int []a=new int[]{k};
        middleScan(root,a);
        return result;
    }
    public void middleScan(TreeNode root,int []a){
        if(root==null){
            return;
        }
        middleScan(root.left,a);
        a[0]--;
        if(a[0]==0){
            result=root.val;
        }
        middleScan(root.right,a);
    }
}

莫里斯遍历:空间复杂度O(1)

用递归和迭代的方式都使用了辅助的空间,而莫里斯遍历的优点是没有使用任何辅助空间。
缺点是改变了整个树的结构,强行把一棵二叉树改成一段链表结构。

我们将黄色区域部分挂到节点5的右子树上,接着再把2和5这两个节点挂到4节点的右边。
这样整棵树基本上就变改成了一个链表了,之后再不断往右遍历。

改变结构

不改变结构

时间复杂度:找到每个前驱节点的复杂度是O(n),因为n个节点的二叉树有n-1条边,每条边只可能使用2次(一次定位到节点,一次找到前驱节点),故时间复杂度为O(n)
空间复杂度:O(1)

class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        TreeNode pre = null;
        while(root!=null) {
            //如果左节点不为空,就将当前节点连带右子树全部挂到
            //左节点的最右子树下面
            if(root.left!=null) {
                pre = root.left;
                while(pre.right!=null) {
                    pre = pre.right;
                }
                pre.right = root;
                //将root指向root的left
                TreeNode tmp = root;
                root = root.left;
                tmp.left = null;
            //左子树为空,则打印这个节点,并向右边遍历    
            } else {
                res.add(root.val);
                root = root.right;
            }
        }
        return res;
    }
}

不破坏原有结构

// 莫里斯中序遍历
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> ldr = new ArrayList<Integer>();
        TreeNode cur = root;
        TreeNode pre = null;
        while(cur!=null){
            if(cur.left==null){//左子树为空,输出当前节点,将其右孩子作为当前节点
                ldr.add(cur.val);
                cur = cur.right;
            }
            else{
                pre = cur.left;//左子树
                while(pre.right!=null&&pre.right!=cur){//找到前驱节点,即左子树中的最右节点
                    pre = pre.right;
                }
            //如果前驱节点的右孩子为空,将它的右孩子设置为当前节点。当前节点更新为当前节点的左孩子。
                if(pre.right==null){
                    pre.right = cur;
                    cur = cur.left;
                }
            //如果前驱节点的右孩子为当前节点,将它的右孩子重新设为空(恢复树的形状)。输出当前节点。当前节点更新为当前节点的右孩子。
                if(pre.right==cur){
                    pre.right = null;
                    ldr.add(cur.val);
                    cur = cur.right;
                }
            }
        }
        return ldr;
    }
}

来到的当前节点,记为cur;
1)如果cur无左孩子,cur向右移动(cur=cur->right)
2)如果cur有左孩子,找到cur左子树上的最右节点,记为most_right
    如果most_right的right指针指向空,让它指向cur,cur向左移动
    如果most_right的right指针指向cur,让它指向空,cur向右移动

基本模板:

void morrisTraversal(TreeNode* root) {
    if (root == nullptr) return;

    TreeNode* cur = root;
    while (cur) {
        if (cur->left == nullptr) {
            //打印时机
            cur = cur->right;
        }
        else {
            TreeNode* most_right = cur->left;
            while (most_right->right != nullptr && most_right->right != cur)
                most_right = most_right->right;
            if (most_right->right == nullptr) {
                most_right->right = cur;
                cur = cur->left;
            }
            else {
                most_right->right = nullptr;
                //打印时机
                cur = cur->right;
            }
        }
    }
}

leetcode 99 利用莫里斯遍历恢复二叉搜索树

class Solution {
public:
    TreeNode* pre=nullptr;
    TreeNode* x=nullptr;
    TreeNode* y=nullptr;
    void morrisTraversal(TreeNode* root) {
    if (root == nullptr) return;
    TreeNode* cur = root;
    while (cur) {
        if (cur->left == nullptr) {
        }
        else {
            TreeNode* most_right = cur->left;
            while (most_right->right != nullptr && most_right->right != cur)
                most_right = most_right->right;
            if (most_right->right == nullptr) {
                most_right->right = cur;
                cur = cur->left;
                continue;
            }
            else {
                most_right->right = nullptr;
            }
        }
        if(pre==nullptr) pre=cur;
        if(pre->val>cur->val){
            y=cur;
            if(x==nullptr){
                x=pre;
            }
        }
        pre=cur;
        cur=cur->right;
    }
}
    void recoverTree(TreeNode* root) {
        morrisTraversal(root);
        if(x!=nullptr&&y!=nullptr){
            int temp=x->val;
            x->val=y->val;
            y->val=temp;
        }
    }
};

层次遍历

leetcode 102 二叉树层次遍历
leetcode 107 二叉树的层次遍历2:从下往上的遍历
leetcode 103 二叉树的锯齿形层次遍历
leetcode 429 N叉树的层次遍历
leetcode 104 求二叉树的最大深度:使用层次遍历
leetcode 116:填充每个节点的下一个右侧指针(完美二叉树)
leetcode 117:填充每个节点的下一个右侧指针(普通二叉树)
leetcode 199:二叉树的右视图

leetcode 102 二叉树层次遍历
如果不需要确定当前遍历到了哪一层

while queue 不空:
    cur = queue.pop()
    将 cur的值 塞到 ans 中;
    if cur的左儿子 有效:
        queue.push(左儿子)
    if cur的右儿子 有效:
        queue.push(右儿子)

如果要确定当前遍历到了哪一层

while queue 不空:
    size = queue.size()
    声明临时vector
    while (size --) {
        cur = queue.pop()
        将 cur的值 加入 临时vector
        if cur的左儿子 有效:
            queue.push(左儿子)
        if cur的右儿子 有效:
            queue.push(右儿子)
    }
    将 临时vector 塞进 ans 中
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> res;
        if(!root) return res;
        
        queue<TreeNode*> que;
        que.push(root);
        while (que.size() != 0) {
            int size = que.size();
            vector<int> temp;
            while (size --) {
                TreeNode* cur = que.front();
                que.pop();
                temp.push_back(cur->val);
                if(cur->left)
                    que.push(cur->left);
                if(cur->right)
                    que.push(cur->right);
            }
            res.push_back(temp);
        }
        return res;
    }
};
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> res;
        dfs(res, root, 0);
        return res;
    }
    void dfs(vector<vector<int>>& res, TreeNode* root, int level) {
        if (!root) return;
        if (level >= res.size())
            res.push_back(vector<int>());
        res[level].push_back(root->val);
        dfs(res, root->left, level + 1);
        dfs(res, root->right, level + 1);
    }
};

leetcode 107 二叉树的层次遍历2:从下往上的遍历
相比上题,简单的把上题的结果逆序即可

leetcode 103 二叉树的锯齿形层次遍历
在原来的模板基础上,维护level作为层数;
在插入元素到临时vector时,根据level的奇偶来进行正序或逆序的插入即可

class Solution {
public:
    vector<vector<int>> zigzagLevelOrder(TreeNode* root) {
        vector<vector<int>> res;
        if (root == NULL)   return {};
        
        queue<TreeNode*> q;     
        q.push(root);  

        int level=0;
        while (!q.empty()) 
        {  
            vector<int>temp;                //存放每一层的元素值
            int count=q.size();             //队列大小表示当前层数的元素个数
            while(count--)                  //count--逐个对该层元素进行处理
            {
                TreeNode *t=q.front();             
                q.pop();    
                if(level%2 ==0) 
                    temp.push_back(t->val);
                else
                    temp.insert(temp.begin(),t->val);
                if(t->left) q.push(t->left);
                if(t->right)q.push(t->right);
            }
            level++;
            res.push_back(temp);           //将当层元素的vector加入res中
        }
        return res;
    }
};

leetcode 429 N叉树的层序遍历

/*
// Definition for a Node.
class Node {
public:
    int val;
    vector<Node*> children;

    Node() {}

    Node(int _val) {
        val = _val;
    }

    Node(int _val, vector<Node*> _children) {
        val = _val;
        children = _children;
    }
};
*/

class Solution {
public:
    vector<vector<int>> levelOrder(Node* root) {
        vector<vector<int>> ans;
        if(!root) return ans;

        queue<Node*> q;
        q.push(root);
        while(!q.empty()){
            int count=q.size();
            vector<int> temp;
            while(count--){
                Node* cur=q.front();
                q.pop();
                temp.push_back(cur->val);
                for(auto node:cur->children){
                    q.push(node);
                }
            }
            ans.push_back(temp);
        }

        return ans;
    }
};

leetcode 104 求二叉树的最大深度:使用层次遍历

class Solution {
public:
    int maxDepth(TreeNode* root) {
        if(!root) return 0;

        queue<TreeNode*> q;
        q.push(root);
        int level=0;
        while(!q.empty()){
            int count=q.size();
            while(count--){
                TreeNode* cur=q.front();
                q.pop();
                if(cur->left)
                    q.push(cur->left);
                if(cur->right)
                    q.push(cur->right);
            }
            level++;
        }
        return level;
    }
};

leetcode 116:填充每个节点的下一个右侧指针(完美二叉树)

class Solution {
public:
    Node* connect(Node* root) {
        if(root == nullptr) return root;
        queue<Node*> q({root});
        while(!empty(q)){
            Node* tmp = new Node(-1);
            int count = q.size();
            while(count--){
                tmp->next = q.front(); 
                q.pop();
                tmp = tmp->next;
                if(tmp->left) 
                    q.push(tmp->left);
                if(tmp->right) 
                    q.push(tmp->right);
            }
        }
        return root;
    }
};
class Solution {
public:
    void connect_m(Node* root, Node* nextOne) {
        if(root == nullptr) return;
        connect_m(root->left, root->right);
        root->next = nextOne;
        connect_m(root->right, (nextOne ? nextOne->left: nullptr));
    }
    Node* connect(Node* root) {
        connect_m(root, nullptr);
        return root;
    }
};

leetcode 117:填充每个节点的下一个右侧指针(普通二叉树)
思路:类似层次遍历的思想
当前层cur通过next不断右移;每次处理下一层级的节点相连;
处理完一层后移动到下一层;直到没有下一层为止;

Node connect(Node root) {
    Node cur = root;
    while (cur != null) {
        Node dummy = new Node();
        Node tail = dummy;
        //遍历 cur 的当前层
        while (cur != null) {
            if (cur.left != null) {
                tail.next = cur.left;
                tail = tail.next;
            }
            if (cur.right != null) {
                tail.next = cur.right;
                tail = tail.next;
            }
            cur = cur.next;
        }
        //更新 cur 到下一层
        cur = dummy.next;
    }
    return root;
}

leetcode 199:二叉树的右视图

class Solution {
public:
    vector<int> rightSideView(TreeNode* root) {
        vector<int> ans;
        if(!root) return ans;

        queue<TreeNode*> q;
        q.push(root);
        while(!q.empty()){
            int count=q.size();
            while(count--){
                TreeNode* cur=q.front();
                q.pop();

                if(count==0)
                    ans.push_back(cur->val);

                if(cur->left)
                    q.push(cur->left);
                if(cur->right)
                    q.push(cur->right);
            }
        }

        return ans;
    }
};

由序列构造二叉树

leetcode 105 根据一棵树的前序遍历与中序遍历构造二叉树

拿到中序序列的值和对应的下标;
这里可以维护一个全局的pre序列的index,
在递归先建立左子树再建立右子树时,可以保证每次递归建立使用的root的值是前序序列的顺序
class Solution {
    Map<Integer,Integer> inorderMap=new HashMap<Integer,Integer>();
    public TreeNode buildTree(int[] preorder, int[] inorder) {
          if(preorder==null||inorder==null||preorder.length!=inorder.length||preorder.length==0||inorder.length==0){
              return null;
          }
          for(int i=0;i<inorder.length;i++){
              inorderMap.put(inorder[i],i);
          }
          return buildTreeDFS(preorder,0,preorder.length-1,inorder,0,inorder.length-1);


	    }
        public TreeNode buildTreeDFS(int[] preorder,int preorderStart,int preorderEnd,int[] inorder,int inorderStart,int inorderEnd){
            if(inorderEnd<inorderStart){
                return null;
            }
            TreeNode newNode =new TreeNode(preorder[preorderStart]);
            int middleIndex=inorderMap.get(preorder[preorderStart]);
            int leftCount=middleIndex-inorderStart;
            newNode.left=buildTreeDFS(preorder,preorderStart+1,preorderStart+leftCount,inorder,inorderStart,middleIndex-1);
            newNode.right=buildTreeDFS(preorder,preorderStart+leftCount+1,preorderEnd,inorder,middleIndex+1,inorderEnd);
            return newNode;
        }
	 
}

leetcode 106 根据一棵树的中序遍历与后序遍历构造二叉树

拿到中序序列的值和对应的下标;
这里可以维护一个全局的pre序列的index,
在递归先建立右子树再建立左子树时,可以保证每次递归建立的root的值是后序序列的逆序
class Solution {
    public TreeNode buildTree(int[] inorder, int[] postorder) {
		  if(inorder==null||inorder.length==0){
			  return new TreeNode();
		  }
		  Map<Integer,Integer> mapInOrder=new HashMap<Integer,Integer>();
		  for(int i=0; i<inorder.length;i++){
			  mapInOrder.put(inorder[i], i);
		  }
		  return dfsBuildTree(inorder,0,inorder.length-1,postorder,0,postorder.length-1,mapInOrder);
		  

	    }
	 private TreeNode dfsBuildTree(int[] middleOrder,int middleStart,int middleEnd,  int[] postorder,int postStart,int postEnd,Map<Integer,Integer> mapInOrder){
		 if(middleStart>middleEnd){
			 return null;
		 }
		 int middleRootIndex=mapInOrder.get(postorder[postEnd]);
		 int rightLength=middleEnd-middleRootIndex;
		 TreeNode root=new TreeNode(postorder[postEnd]);
		 root.left=dfsBuildTree(middleOrder,middleStart,middleRootIndex-1,postorder,postStart,postEnd-rightLength-1,mapInOrder);
		 root.right=dfsBuildTree(middleOrder,middleRootIndex+1,middleEnd,postorder,postEnd-rightLength,postEnd-1,mapInOrder);
		 return root;	
	 }
}

889. 根据前序和后序遍历构造二叉树

class Solution {
  public TreeNode constructFromPrePost(int[] pre, int[] post) {
		  if(pre==null||pre.length==0){
				return null;
			}
			if(pre.length==1){
				return new TreeNode(pre[0]);
			}
			return dfsConstructFromPrePost(pre,0,pre.length-1,post,0,post.length-1);
		  
		  
		  
	  }
	private TreeNode dfsConstructFromPrePost(int[] pre, int preStart, int preEnd, int[] post, int postStart,int postEnd) {
		if((preStart>preEnd)||(postStart>postEnd)){
			return null;
		}
        if((preStart==preEnd)||(postStart==postEnd)){
            return new TreeNode(pre[preStart]);
        }
		// TODO Auto-generated method stub
		
		int leftLength=0;
        int preMiddle=0;  //前序遍历,左右子树的分界线,也是左子树的最后一个
        int postMiddle=0;  //后续遍历左右子树的分界线
		for(int i=postStart;i<=postEnd;i++){
			if(post[i]==pre[preStart+1]){
				leftLength=i-postStart+1;
                postMiddle=i;
			}
		}
        preMiddle=preStart+leftLength;
        TreeNode root=new TreeNode(pre[preStart]);
		int rightLength=postEnd-postStart-leftLength;
		root.left=dfsConstructFromPrePost(pre,preStart+1,preMiddle,post,postStart,postMiddle);
		root.right=dfsConstructFromPrePost(pre,preMiddle+1,preEnd,post,postMiddle+1,postEnd-1);
		return root;
	}


//    public TreeNode constructFromPrePost(int[] pre, int[] post) {
// 		if(pre==null||pre.length==0){
// 			return null;
// 		}
// 		if(pre.length==1){
// 			return new TreeNode(pre[0]);
// 		}
// 		TreeNode root=new TreeNode(pre[0]);
// 		int leftLength=0;
// 		for(int i=0;i<post.length;i++){
// 			if(post[i]==pre[1]){
// 				leftLength=i+  1;
// 			}
// 		}
// 		int rightLength=post.length-1-leftLength;
// 		root.left=constructFromPrePost(Arrays.copyOfRange(pre, 1, leftLength+1),Arrays.copyOfRange(post, 0, leftLength));
// 		root.right=constructFromPrePost(Arrays.copyOfRange(pre, leftLength+1, rightLength+leftLength+1),Arrays.copyOfRange(post, leftLength, leftLength+rightLength));
// 		return root;
//     }
}

leetcode 108 将有序数组转换为二叉树

class Solution {
    public TreeNode sortedArrayToBST(int[] nums) {
		 if(nums==null||nums.length==0){
			 return null;
		 }
		return sortedArrayToTree(nums,0,nums.length-1);
	    }
	
	 private TreeNode sortedArrayToTree(int [] nums,int start,int end){
		if(start>end){
			return null;
		}
		if(start==end){
			return new TreeNode(nums[start]);
		}
		int rootIndex=(start+end)/2;
		TreeNode root=new TreeNode(nums[rootIndex]);
		root.left=sortedArrayToTree(nums,start,rootIndex-1);
		root.right=sortedArrayToTree(nums,rootIndex+1,end);
		return root;
		 
	 };
}

leetcode 109 有序链表转换为平衡二叉搜索树
和上题的有序数组相比,
通过快慢指针找到中间节点
注意边界条件的处理

class Solution {
    public TreeNode sortedListToBST(ListNode head) {
        if(head==null){
				return null;
			}
			ListNode slow=head;
			ListNode fast=head;
			ListNode preNode=null;
			while(fast!=null&&fast.next!=null){
				preNode=slow;
				slow=slow.next;
				fast=fast.next.next;
			}
		    ListNode middle=slow;
		    if(middle==null){
		    	return null;
		    }
		    TreeNode root=new TreeNode(middle.val);
		    if(preNode!=null){
		    	preNode.next=null;
		    	root.left=sortedListToBST(head);
		    }
		    root.right=sortedListToBST(middle.next);
			return root;
    }
}

递归解决二叉树问题:

1.确定递归函数的参数和返回值
2.确定终止条件
3.确定单层递归的逻辑

1)是否满足某种性质类:
leetcode 98 是否为二叉搜索树
leetcode 100 两棵二叉树是否相同
leetcode 101 二叉树是否是镜像对称的
leetcode 110 二叉树是否高度平衡

2)定义递归函数,递归的每层的逻辑一致:求深度,路径和等
leetcode 104 求二叉树的最大深度
leetcode 111 求二叉树的最小深度
leetcode 124 求二叉树的最大路径和
leetcode 156 上下翻转二叉树
leetcode 222 完全二叉树的节点个数
leetcode 235 二叉搜索树的最近公共祖先
leetcode 236 二叉树的公共祖先
leetcode 250 计数相同值子树的个数

3)树上进行dfs,到达终点时结算
leetcode 112 求二叉树的路径总和
leetcode 113 求二叉树的路径总和2
leetcode 437 求路径总和等于给定值的路径数
leetcode 129 求根到叶子节点数字之和
leetcode 257 二叉树的所有路径(根到叶子节点)

leetcode 98 是否为二叉搜索树

class Solution {
    public  boolean isValidBST(TreeNode root) {
			if(root==null){
				return false;
			}
			Long min=Long.MIN_VALUE,max=Long.MAX_VALUE;
			return isValidTree(root,min,max);
		   

	    }
		public  boolean isValidTree(TreeNode root,long min,long max){
			if(root==null){
				return true;
			}
			if(root.val<=min||root.val>=max){
				return false;
			}
			return isValidTree(root.left,min,root.val)&&isValidTree(root.right,root.val,max);
		}


}

leetcode 100 两棵二叉树是否相同

class Solution {
public:
    bool isSameTree(TreeNode* p, TreeNode* q) {
        if(p!=nullptr&&q!=nullptr){
            return p->val==q->val&&isSameTree(p->left,q->left)&&isSameTree(p->right,q->right);
        }
        else{
            if(p==nullptr&&q==nullptr) return true;
            else return false;
        }
    }
};

leetcode 101 是否对称二叉树

class Solution {
   public boolean isSymmetric(TreeNode root) {
		 	if(root==null){
		 		return false;
		 	}
		 	return isSymmetricDFS(root.left,root.right);
	    }

	 private boolean isSymmetricDFS(TreeNode p, TreeNode q) {
		// TODO Auto-generated method stub
		        if(p==null&&q==null){
				return true;
			}
			if((p==null&&q!=null)||(p!=null&&q==null)){ 
				return false;
			}
		        return (p.val==q.val)&&isSymmetricDFS(p.left,q.right)&&                      isSymmetricDFS(p.right,q.left);
	}
}

leetcode 110 二叉树是否高度平衡

class Solution {
   boolean isBalance=true;
	  public boolean isBalanced(TreeNode root) {
          if(root==null){
              return isBalance;
          }
		  isBalanceDFS(root);
		  return isBalance;
	    }
	  private int isBalanceDFS(TreeNode root){
		  if(root==null){
			  return 0;
		  }
		  int levelLeft=isBalanceDFS(root.left);
		  int levelRight=isBalanceDFS(root.right);
		  if(Math.abs(levelLeft-levelRight)>1){
			  isBalance=false;
		  }
		  return Math.max(levelLeft, levelRight)+1;
		  
	  }
}

leetcode 104 求二叉树的最大深度

DFS
class Solution {
    int level=0;
    public int maxDepth(TreeNode root) {
        if(root==null){
            return 0;
        }
        dfs(root,1);
        return level;
    }
    public void dfs(TreeNode root,int level1){
        if(root==null){
            return;
        }
        level=Math.max(level,level1);
        if(root.left!=null){
            dfs(root.left,level1+1);
        }
        if(root.right!=null){
            dfs(root.right,level1+1);
        }
    }
}

BFS
class Solution {
    public int maxDepth(TreeNode root) {
        if(root==null){
            return 0;
        }
        Queue<TreeNode> queue=new LinkedList<TreeNode>();
        queue.offer(root);
        int deep=0;
        while(!queue.isEmpty()){
            int size=queue.size();
            for(int i=0;i<size;i++){
                TreeNode curNode=queue.poll();
                if(curNode.left!=null){
                    queue.offer(curNode.left);
                }
                if(curNode.right!=null){
                     queue.offer(curNode.right);
                 }
            }
            deep++;
        }
        return deep;
    }
}

leetcode 111 二叉树的最小深度

DFS
class Solution {
    int minDepth=Integer.MAX_VALUE;
    public int minDepth(TreeNode root) {
        if(root==null){
            return 0;
        }
        minDepthDFS(root,1);
        return minDepth;
    }
    public void minDepthDFS(TreeNode root,int level){
        if(root==null){
            return;
        }
        if(root!=null&&root.left==null&&root.right==null){
            minDepth=Math.min(minDepth,level);
            return ;
        }
       if(root.left!=null){
            minDepthDFS(root.left,level+1);
       }
      if(root.right!=null){
           minDepthDFS(root.right,level+1);
       }
      

    }
}

BSF
class Solution {
    int minDepth=Integer.MAX_VALUE;
    public int minDepth(TreeNode root) {
        if(root==null){
            return 0;
        }
       Queue<TreeNode> queue=new LinkedList<TreeNode>();
        queue.offer(root);
        int deep=1;
        while(!queue.isEmpty()){
            int size=queue.size();
            for(int i=0;i<size;i++){
                TreeNode curNode=queue.poll();
                if(curNode.left!=null){
                    queue.offer(curNode.left);
                }
                if(curNode.right!=null){
                     queue.offer(curNode.right);
                 }
                 if(curNode.left==null&&curNode.right==null){
                     return deep;
                 }
            }
            deep++;
        }
        return deep;
    }
}

leetcode 222 完全二叉树的节点个数
要用到完全二叉树的性质来减少递归次数

解题思路:

    利用完全二叉树的性质求解:完全二叉树的叶子结点都位于最后一层和倒数第二层上,所以可以通过对左右子树高度的判定来判断左右子树的节点数, 设左子树的高度为left,右子树的高度为right。若left等于right,则说明左子树是满二叉树,有2^left-1个节点加上根节点root共有2^left个节点;若left不等于right,则说明右子树是满二叉树,加上根节点的节点数为2^right。
    通用解法:递归求解、层次遍历

class Solution {
   	 public int countNodes(TreeNode root) {
	            if(root==null){
	            	return 0;
	            }
	           int leftLevel=countLevel(root.left);
	           int rightLevel=countLevel(root.right);
	           if(leftLevel==rightLevel){
	        	  return (int) (Math.pow(2, leftLevel)+countNodes(root.right)); 
	           }
	           else{
	        	   return (int) (Math.pow(2, rightLevel)+countNodes(root.left)); 
	           }

	    }
	 
	 private  int countLevel(TreeNode root){
		//  if(root==null){
		// 	 return 0;
		//  }
		 int count=0;
		 while(root!=null){
			 root=root.left;
			 count++;
		 }
		 return count;
	 }
}

leetcode 156 翻转二叉树

Given a binary tree where all the right nodes are either leaf nodes with a sibling (a left node that shares the same parent node) or empty, flip it upside down and turn it into a tree where the original right nodes turned into left leaf nodes. Return the new root.
For example:
Given a binary tree  {1,2,3,4,5} ,

    1
   / \
  2   3
 / \
4   5

return the root of the binary tree [4,5,2,#,#,3,1].

   4
  / \
 5   2
    / \
   3   1  


思路: 题意是说每个结点的右子树要么为空, 要么一定有一个左子树孩子和一个右子树孩子, 因此树的形状是左偏的. 所以我们要将最左边的子树作为最终的新根结点, 然后递归的将其父结点作为其右孩子,并且父结点的右孩子作为其左孩子. 一个非常重要的地方是每次一定要将父结点的左右孩子都置为空, 因为父结点设置成其左孩子的右孩子之后成了叶子结点, 需要将其指针断掉.

代码如下:

    /**
     * Definition for a binary tree node.
     * struct TreeNode {
     *     int val;
     *     TreeNode *left;
     *     TreeNode *right;
     *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
     * };
     */
    class Solution {
    public:
        TreeNode* upsideDownBinaryTree(TreeNode* root) {
            if(!root || !root->left) return root;
            TreeNode *newRoot = upsideDownBinaryTree(root->left);
            root->left->left = root->right;
            root->left->right = root;
            root->left = NULL, root->right = NULL;
            return newRoot;
        }
    }

leetcode 235 二叉搜索树的最近公共祖先

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root==null){
		return null;
         }
         while(true){
             if(root.val<p.val&&root.val<q.val){
                 root=root.right;
             }
              else if(root.val>p.val&&root.val>q.val){
                 root=root.left;
             }
             else{
                break;
             }
         }
         return root;
    }

leetcode 236 二叉树的最近公共祖先

class Solution {
     static TreeNode resultNode;
	  public static TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
		  dfs2(root,p.val,q.val);
		  return resultNode;
	  }
	  private static boolean dfs2(TreeNode root, int val, int val2) {
		// TODO Auto-generated method stub
		  if(root==null){
			  return false;
		  }
		  boolean inCurrent=false;
		  if(root.val==val||root.val==val2){
			  inCurrent=true;
		  }
		  boolean inLeft=dfs2(root.left,val,val2); 
		  boolean inRight=dfs2(root.right,val,val2); 
		  if((inLeft&&inRight)||(inCurrent&&inLeft)||(inCurrent&&inRight)){
			  resultNode=root;
		  }
		  return inLeft||inCurrent||inRight;
	}

leetcode 112 求二叉树是否存在路径(从根节点出发到叶子节点),路径值的总和为给定值

class Solution {
    boolean hasPathSum=false;
	 public boolean hasPathSum(TreeNode root, int targetSum) {
		 if(root==null){
			 return hasPathSum;
		 }
        dfsHasPathSum(root,targetSum,0);
		 return hasPathSum;
	    }
	
	 private void dfsHasPathSum(TreeNode root, int targetSum, int sum) {
                 if(root==null){
                     return;
                 }
                  sum+=root.val;
		// TODO Auto-generated method stub
		 if(root.left==null&&root.right==null){
			 if(targetSum==sum){
				 hasPathSum=true;
			 }
			 return;
		 }
		 dfsHasPathSum(root.left,targetSum,sum);
		 dfsHasPathSum(root.right,targetSum,sum);
	}
}

leetcode 113 求上题的具体路径

class Solution {
      List<List<Integer>> result=new ArrayList<List<Integer>>();
	    public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
	        if(root==null){
	            return result;
	        }
	        pathSumDFS(root,targetSum,new ArrayList<Integer>(),0);
            return result;
	    }
	    private void pathSumDFS(TreeNode root, int targetSum,List<Integer> tempResult,int pathSum){
	        if(root==null){
	            return;
	        }
	        pathSum+=root.val;
	        tempResult.add(root.val);
	        if(root.left==null&&root.right==null){
	            if(targetSum==pathSum){
	                result.add(new ArrayList(tempResult));
	            }
	        }
	        pathSumDFS(root.left,targetSum,tempResult,pathSum);
	        pathSumDFS(root.right,targetSum,tempResult,pathSum);
	        tempResult.remove(tempResult.size()-1);
	    }
}

leetcode 437 求路径总和等于给定值的路径数(与上题不同,路径不用从根出发和从叶子结束
 

解题思路

这道题用到了一个概念,叫前缀和。就是到达当前元素的路径上,之前所有元素的和。

前缀和怎么应用呢?

在同一个路径之下(可以理解成二叉树从root节点出发,到叶子节点的某一条路径),如果两个数的前缀总和是相同的,那么这些节点之间的元素总和为零。进一步扩展相同的想法,如果前缀总和currSum,在节点A和节点B处相差target,则位于节点A和节点B之间的元素之和是target。

因为本题中的路径是一棵树,从根往任一节点的路径上(不走回头路),有且仅有一条路径,因为不存在环。(如果存在环,前缀和就不能用了,需要改造算法)

抵达当前节点(即B节点)后,将前缀和累加,然后查找在前缀和上,有没有前缀和currSum-target的节点(即A节点),存在即表示从A到B有一条路径之和满足条件的情况。结果加上满足前缀和currSum-target的节点的数量。然后递归进入左右子树。

左右子树遍历完成之后,回到当前层,需要把当前节点添加的前缀和去除。避免回溯之后影响上一层。因为思想是前缀和,不属于前缀的,我们就要去掉它。
核心代码

// 当前路径上的和
currSum += node.val;
// currSum-target相当于找路径的起点,起点的sum+target=currSum,当前点到起点的距离就是target
res += prefixSumCount.getOrDefault(currSum - target, 0);
// 更新路径上当前节点前缀和的个数
prefixSumCount.put(currSum, prefixSumCount.getOrDefault(currSum, 0) + 1);

代码

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public int pathSum(TreeNode root, int sum) {
        // key是前缀和, value是大小为key的前缀和出现的次数
        Map<Integer, Integer> prefixSumCount = new HashMap<>();
        // 前缀和为0的一条路径
        prefixSumCount.put(0, 1);
        // 前缀和的递归回溯思路
        return recursionPathSum(root, prefixSumCount, sum, 0);
    }

    /**
     * 前缀和的递归回溯思路
     * 从当前节点反推到根节点(反推比较好理解,正向其实也只有一条),有且仅有一条路径,因为这是一棵树
     * 如果此前有和为currSum-target,而当前的和又为currSum,两者的差就肯定为target了
     * 所以前缀和对于当前路径来说是唯一的,当前记录的前缀和,在回溯结束,回到本层时去除,保证其不影响其他分支的结果
     * @param node 树节点
     * @param prefixSumCount 前缀和Map
     * @param target 目标值
     * @param currSum 当前路径和
     * @return 满足题意的解
     */
    private int recursionPathSum(TreeNode node, Map<Integer, Integer> prefixSumCount, int target, int currSum) {
        // 1.递归终止条件
        if (node == null) {
            return 0;
        }
        // 2.本层要做的事情
        int res = 0;
        // 当前路径上的和
        currSum += node.val;

        //---核心代码
        // 看看root到当前节点这条路上是否存在节点前缀和加target为currSum的路径
        // 当前节点->root节点反推,有且仅有一条路径,如果此前有和为currSum-target,而当前的和又为currSum,两者的差就肯定为target了
        // currSum-target相当于找路径的起点,起点的sum+target=currSum,当前点到起点的距离就是target
        res += prefixSumCount.getOrDefault(currSum - target, 0);
        // 更新路径上当前节点前缀和的个数
        prefixSumCount.put(currSum, prefixSumCount.getOrDefault(currSum, 0) + 1);
        //---核心代码

        // 3.进入下一层
        res += recursionPathSum(node.left, prefixSumCount, target, currSum);
        res += recursionPathSum(node.right, prefixSumCount, target, currSum);

        // 4.回到本层,恢复状态,去除当前节点的前缀和数量
        prefixSumCount.put(currSum, prefixSumCount.get(currSum) - 1);
        return res;
    }
}

时间复杂度:每个节点只遍历一次,O(N).

空间复杂度:开辟了一个hashMap,O(N).

leetcode 129 求根到叶子节点数字之和
给定一个二叉树,它的每个结点都存放一个 0-9 的数字,每条从根到叶子节点的路径都代表一个数字。
例如,从根到叶子节点路径 1->2->3 代表数字 123。
计算从根到叶子节点生成的所有数字之和。

class Solution {
    int result=0;
    public int sumNumbers(TreeNode root) {
        if(root==null){
            return result;
        }
        dfsSumNumbers( root, 0);
        return result;
    }
    public void  dfsSumNumbers(TreeNode root,int sum){
         if(root==null){
             return ;
         }
         sum=sum*10+root.val;
         if(root.left==null&&root.right==null){
             result+=sum;
         }
        dfsSumNumbers( root.left, sum);
        dfsSumNumbers( root.right, sum);
     }

}

leetcode 257 二叉树的所有路径(根到叶子节点)

class Solution {
    List<String> result=new ArrayList<String>();
    public List<String> binaryTreePaths(TreeNode root) {
        if(root==null){
            return result;
        }
        binaryTreePaths(root,"");
        return result;
    }
   private void  binaryTreePaths(TreeNode root, String tempResult){
        if(root==null){
            return;
        }
        tempResult+=root.val;
       
        if(root.left==null&&root.right==null){
           
            result.add(tempResult.toString());
        }
    
     binaryTreePaths(root.left, tempResult+"->");
     binaryTreePaths(root.right, tempResult+"->");
  
    
}

}

leetcode 124 二叉树中的最大路径和
这里的路径定义:从任意节点到任意节点
递归函数定义:返回当前节点的单边最大路径和

class Solution {
    int maxSum = Integer.MIN_VALUE;

    public int maxPathSum(TreeNode root) {
        maxGain(root);
        return maxSum;
    }

    public int maxGain(TreeNode node) {
        if (node == null) {
            return 0;
        }
        
        // 递归计算左右子节点的最大贡献值
        // 只有在最大贡献值大于 0 时,才会选取对应子节点
        int leftGain = Math.max(maxGain(node.left), 0);
        int rightGain = Math.max(maxGain(node.right), 0);

        // 节点的最大路径和取决于该节点的值与该节点的左右子节点的最大贡献值
        int priceNewpath = node.val + leftGain + rightGain;

        // 更新答案
        maxSum = Math.max(maxSum, priceNewpath);

        // 返回节点的最大贡献值
        return node.val + Math.max(leftGain, rightGain);
    }
}

将二叉树转换为其他结构

leetcode 114 二叉树展开为先序遍历链表

 */
class Solution {
    public void flatten(TreeNode root) {
        if(root==null){
            return;
        }
        flatten(root.left);
        TreeNode right=root.right;
        if(root.left!=null){
            TreeNode tempRoot=root;
            tempRoot.right=tempRoot.left;
            tempRoot.left=null;
            while(tempRoot.right!=null){
                tempRoot=tempRoot.right;
            }
            tempRoot.right=right;
        }
        flatten(right);		
	    }
}

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值