剑指offer:正则表达式匹配(Java实现简易词法分析)

一、题目描述

在这里插入图片描述
在这里插入图片描述

二、解题思路

看到题目,我首先想到的就是大三编译原理课上学过的词法分析阶段。一个正则表达式可以转换成一个NFA(非确定性有限状态自动机),一个NFA通过一定的转换可以转换成与之等价的一个DFA(确定性有限状态自动机),通过DFA上的状态转换我们就可以推断出一个字符串是否是一个满足正则表达式的合法字符了。

第一步:根据所给的pattern构建出与之等价的NFA

注:由于存在a*a等情况,直接构造DFA是十分困难且思路容易混乱的,相比之下先构建NFA再转化成DFA的方法思路十分清晰,到后期不容易乱了思绪。
对于一个正则表达式,我们按照如下图所示的方式构建出其对应的一个NFA
在这里插入图片描述

	//根据正则表达式获得对应的NFA,此处构建的NFA有唯一的初态和终态
    public Vector<Vector> getNFA(char[] pattern){
        if(pattern.length > 0){
            /*
            NFA为我们需要得到的结果
            NFA.get(0)存储的是一系列NFA中的单值映射集合
            NFA.get(1)存储的是NFA的终态
            NFA.get(2)存储的是NFA的字母表
             */
            Vector<Vector> NFA= new Vector<Vector>();
            /*
            reflection用于存储NFA中的所有单值映射
            对于reflection的每一个子集r来说
            r.get(0)存储的是原状态A
            r.get(1)存储的是经过的字符c
            r.get(2)存储的是目的状态B
            一个r的含义为:A经过字符C到达状态B,即(A,c)==>B
             */
            Vector<Vector> reflection = new Vector<Vector>();          
            //finallyStatus存放NFA的终态
            Vector<Integer> finallyStatus = new Vector<Integer>();
            //ch用于存放NFA的字符集
            Vector<Set> characters = new Vector<Set>();
            Set<Character> ch = new HashSet<Character>();
            //status用于定义NFA的一个状态
            int status = 1;
            //cur记录当前读入的字符
            char cur = ' ';
            for(int i = 0; i < pattern.length; i++){
                //用于存放一组单值映射r
                Vector subSet = new Vector();
                cur = pattern[i];
                /*
                一:如果当前的字符不是'*'
                  1.将该字符加入到ch集合中
                  2.构建一个单值映射r
                    当前状态为status,通过字符为cur,下一状态为status++
                  3.将单值映射r加入到reflection中
                二:如果当前字符是'*',如a*
                  1.由对于a我们执行了一,得到了类似(1,a)==>2这样的单值映射
                  2.我们需要将其变成(1,ε)==>2,(2,a)==>2,(2,ε)==>3的形式                 
                */
                if(cur != '*'){
                    ch.add(cur);
                    subSet.add(status);
                    subSet.add(cur);
                    status++;
                    subSet.add(status);
                    reflection.add(subSet);
                }else{
                    cur = (char)reflection.get(reflection.size()-1).get(1);
                    reflection.remove(reflection.size()-1);
                    status--;
                    subSet.add(status);
                    subSet.add('ε');
                    status++;
                    subSet.add(status);
                    reflection.add(subSet);
                    subSet = new Vector();
                    subSet.add(status);
                    subSet.add(cur);
                    subSet.add(status);
                    reflection.add(subSet);
                    subSet = new Vector();
                    subSet.add(status);
                    subSet.add('ε');
                    status++;
                    subSet.add(status);
                    reflection.add(subSet);
                }
            }
            //polish用于调整(1,.)==>2的情况
            Vector<Vector> polish = new Vector<Vector>();
            /*
            判断单值映射r是否满足(1,.)==>2情况
            如果满足,则记录r的开始状态和结束状态,将其放入polish中,然后在reflection中删除r
            */
            for(int i = 0; i < reflection.size(); i++){
                Vector vector = reflection.get(i);
                int beginStatus = (int)vector.get(0);
                char curchar = (char)vector.get(1);
                int endStatus = (int)vector.get(2);
                if(curchar == '.'){
                    reflection.remove(i);
                    Vector subSetOfPolish = new Vector();
                    subSetOfPolish.add(beginStatus);
                    subSetOfPolish.add(endStatus);
                    polish.add(subSetOfPolish);
                }
            }
            /*
            如果字符集中包含'.',则将'.'从字符集中删去,然后将所有字符加入到字符集中
            注:此处我只加了a,b进去,这样就可以通过测试了,但标准做法应该是所有字符都应加进去
            */
            if(ch.contains('.')){
                ch.add('a');
                ch.add('b');
                ch.remove('.');
            }
            /*
            对于polish中的每一组向量v
            将(v[0],c)==>v[1]加入reflection中,c为全体字符
            */
            for(Vector vector : polish){
                for(char character :ch){
                    Vector subSet = new Vector();
                    subSet.add(vector.get(0));
                    subSet.add(character);
                    subSet.add(vector.get(1));
                    reflection.add(subSet);
                }
            }
            //构建NFA
            finallyStatus.add(status);
            characters.add(ch);
            NFA.add(reflection);
            NFA.add(finallyStatus);
            NFA.add(characters);
            return NFA;
        }
        return null;
    }
第二步:实现NFA确定化过程需要的ε-closure(I)方法和move(I,a)方法

在这里插入图片描述
在这里插入图片描述

	//实现ε-closure闭包
    public Set<Integer> closure(Set<Integer> status, Vector<Vector> reflection){
        Set<Integer> result = new HashSet<Integer>();
        for(int curStatus : status){
            Stack<Integer> stack = new Stack<Integer>();
            //由于状态集合I的每个元素都属于ε-closure(I),所以将每个元素都推入栈中
            stack.push(curStatus);
            /*
            当stack栈非空时
            1.弹出栈顶元素i,将状态i加入到result集合中。
            2.计算状态i走一个ε可以到达的状态s,将s推入栈中
            */
            while(!stack.empty()){
                int temp = stack.pop();
                result.add(temp);
                for(int i = 0; i < reflection.size(); i++){
                    Vector subSet = reflection.get(i);
                    if((int)subSet.get(0) == temp && (char)subSet.get(1) == 'ε'){
                        stack.push((int)subSet.get(2));
                    }
                }
            }
        }
        return result;
    }

    //实现move(I,a)
    public Set<Integer> move(Set<Integer> status, char ch, Vector<Vector> reflection){
        Set<Integer> result = new HashSet<Integer>();
        //对于status集合中的每一个状态s,计算s经过一个a可以到达的状态s',将s'加入到result集合中
        for(int curStatus : status){
            for(int i = 0; i < reflection.size(); i++){
                Vector subSet = reflection.get(i);
                if((int)subSet.get(0) == curStatus && (char)subSet.get(1) == ch){
                    result.add((int)subSet.get(2));
                }
            }
        }
        return result;
    }
第三步:将NFA确定化为DFA
	//NFA确定化
    public Vector<Vector> transNFAToDFA(Vector<Vector> NFA){
        //最终得到的DFA
        Vector<Vector> DFA = new Vector<Vector>();
        //statusSet存放的是DFA的状态集合
        Vector statusSet = new Vector();
        //DFA中的单值映射集合及其子集
        Vector<Vector> reflectionOfDFA = new Vector<Vector>();
        //DFA的终态集合,终态可能不止一个
        Vector<Integer> finallyStatusOfDFA = new Vector<Integer>();
        //NFA中的初始状态
        Set<Integer> beginStatusOfNFA = new HashSet<Integer>();
        beginStatusOfNFA.add(1);
        //NFA中的单值映射集合
        Vector<Vector> reflectionOfNFA = NFA.get(0);
        //NFA的终态结点
        int finallyStatusOfNFA = (int)NFA.get(1).get(0);
        //stack用于构建状态转换表
        Stack<Set> stack = new Stack<Set>();
        //取得NFA字母表,用于进行a弧转换
        Set<Character> alphabet = (Set<Character>)NFA.get(2).get(0);
        /*
        NFA确定化的第一步:将NFA初态进行ε-closure转化,得到DFA的初态
        将得到的初态加入到statusSet中
        将得到的初态push到stack栈中
         */
        Set<Integer> set = closure(beginStatusOfNFA, reflectionOfNFA);
        statusSet.add(set);
        stack.push(set);
        /*
        不断的获取DFA的新状态:
        第一步:当stack栈非空时,弹出栈顶集合rawSet,rawSet为DFA已有的一个状态。
        第二步:对rawSet先进行move(rawSet,ch)得到moveSet,ch为NFA的字母表中的每一个字符。
        第三步:对moveSet进行ε-closure转化得到closureSet,如果closureSet不在subSet中,
               则closureSet是DFA的一个新状态,将其添加到subSet中,然后将其push到栈中。
        第四步:在DFA中,rawSet经过字符ch到达状态closureSet。
        第五步:当栈空时,意味着DFA不会再有新状态产生,循环结束。
         */
        while(!stack.empty()){
            //第一步
            Set<Integer> rawSet = stack.pop();
            for(char ch : alphabet){
                //第二步
                Set<Integer> moveSet = move(rawSet, ch, reflectionOfNFA);
                //第三步
                Set<Integer> closureSet = closure(moveSet, reflectionOfNFA);
                if(!statusSet.contains(closureSet)){
                    statusSet.add(closureSet);
                    stack.push(closureSet);
                }
                //第四步
                Vector reflection = new Vector();
                reflection.add(rawSet);
                reflection.add(ch);
                reflection.add(closureSet);
                reflectionOfDFA.add(reflection);
            }
        }
        /*
        1.对reflectionOfDFA进行调整,将DFA的状态用一个数字代替
          如:得到的DFA某一单值映射是({1,2,3},a)==>{4,5,6},在原NFA中,1、2是单独的一个状态,
          但在DFA中,NFA的状态1、2、3的集合构成DFA的一个状态,所以我们可以将其调整为(1,a)==>2
        2.将包含NFA终态结点的DFA状态结点加入DFA的终态集合中
          如:NFA的终态是5,DFA有状态{1,2,3}、{3,4,5}、{5,6},则状态{3,4,5}、{5,6}都为DFA的一个终态
         */
        for(Vector reflection : reflectionOfDFA){
            Set<Integer> rawSet = (Set<Integer>)reflection.get(0);
            Set<Integer> closureSet = (Set<Integer>)reflection.get(2);
            //numberStatus()函数见完整代码
            int beginStatus = numberStatus(rawSet, statusSet);
            int endStatus = numberStatus(closureSet, statusSet);
            reflection.set(0, beginStatus);
            reflection.set(2, endStatus);
            if(rawSet.contains(finallyStatusOfNFA)){
                if(!finallyStatusOfDFA.contains(beginStatus)){
                    finallyStatusOfDFA.add(beginStatus);
                }
            }
            if(closureSet.contains(finallyStatusOfNFA)){
                if(!finallyStatusOfDFA.contains(endStatus)){
                    finallyStatusOfDFA.add(endStatus);
                }
            }
        }
        DFA.add(reflectionOfDFA);
        DFA.add(finallyStatusOfDFA);
        return DFA;
    }
第四步:判断字符串是否满足正则表达式

在这里插入图片描述

	//判断字符串是否满足正则表达式
    public boolean match(char[] str, char[] pattern){
        //测试用例中的特殊情况
        if(pattern.length == 0){
            if(str.length == 0){
                return true;
            }else{
                return false;
            }
        }else{
            Vector<Vector> NFA = getNFA(pattern);
            Vector<Vector> DFA = transNFAToDFA(NFA);
            Vector<Vector> reflection = DFA.get(0);
            Vector<Integer> finallyStatus = DFA.get(1);
            if(str.length == 0){
                //测试用例中的特殊情况
                if(finallyStatus.contains(1)){
                    return true;
                }else{
                    return false;
                }
            }else{
                //从开始状态开始,不断的读入一个字符,得到转换后的下一状态
                int currentStatus = 1;
                for(int i = 0; i < str.length; i++){
                    char ch = str[i];
                    //transStatus()函数见完整代码
                    int nextStatus = transStatus(currentStatus,ch,reflection);
                    //此时情况为字符串仍有剩余字符,但DFA中不存在对应的单值映射,所以字符串不匹配正则表达式
                    if(nextStatus == 0){
                        return false;
                    }
                    currentStatus = nextStatus;
                }
                /*
                当字符串全都读完时
                如果当前状态是终态,则说明DFA可以接收此字符,该字符匹配正则表达式
                否则该字符只是匹配正则表达式的一部分
                 */
                if(finallyStatus.contains(currentStatus)){
                    return true;
                }else{
                    return false;
                }
            }
        }
    }

三、完整代码

import java.util.HashSet;
import java.util.Set;
import java.util.Stack;
import java.util.Vector;

public class Solution {
    //根据正则表达式获得对应的NFA,此处构建的NFA有唯一的初态和终态
    public Vector<Vector> getNFA(char[] pattern){
        if(pattern.length > 0){
            /*
            NFA为我们需要得到的结果
            NFA.get(0)存储的是一系列NFA中的单值映射集合
            NFA.get(1)存储的是NFA的终态
            NFA.get(2)存储的是NFA的字母表
             */
            Vector<Vector> NFA= new Vector<Vector>();
            /*
            reflection用于存储NFA中的所有单值映射
            对于reflection的每一个子集r来说
            r.get(0)存储的是原状态A
            r.get(1)存储的是经过的字符c
            r.get(2)存储的是目的状态B
            一个r的含义为:A经过字符C到达状态B,即(A,c)==>B
             */
            Vector<Vector> reflection = new Vector<Vector>();
            //finallyStatus存放NFA的终态
            Vector<Integer> finallyStatus = new Vector<Integer>();
            //ch用于存放NFA的字符集
            Vector<Set> characters = new Vector<Set>();
            Set<Character> ch = new HashSet<Character>();
            //status用于定义NFA的一个状态
            int status = 1;
            //cur记录当前读入的字符
            char cur = ' ';
            for(int i = 0; i < pattern.length; i++){
                //用于存放一组单值映射r
                Vector subSet = new Vector();
                cur = pattern[i];
                /*
                一:如果当前的字符不是'*'
                  1.将该字符加入到ch集合中
                  2.构建一个单值映射r
                    当前状态为status,通过字符为cur,下一状态为status++
                  3.将单值映射r加入到reflection中
                二:如果当前字符是'*',如a*
                  1.由对于a我们执行了一,得到了类似(1,a)==>2这样的单值映射
                  2.我们需要将其变成(1,ε)==>2,(2,a)==>2,(2,ε)==>3的形式
                */
                if(cur != '*'){
                    ch.add(cur);
                    subSet.add(status);
                    subSet.add(cur);
                    status++;
                    subSet.add(status);
                    reflection.add(subSet);
                }else{
                    cur = (char)reflection.get(reflection.size()-1).get(1);
                    reflection.remove(reflection.size()-1);
                    status--;
                    subSet.add(status);
                    subSet.add('ε');
                    status++;
                    subSet.add(status);
                    reflection.add(subSet);
                    subSet = new Vector();
                    subSet.add(status);
                    subSet.add(cur);
                    subSet.add(status);
                    reflection.add(subSet);
                    subSet = new Vector();
                    subSet.add(status);
                    subSet.add('ε');
                    status++;
                    subSet.add(status);
                    reflection.add(subSet);
                }
            }
            //polish用于调整(1,.)==>2的情况
            Vector<Vector> polish = new Vector<Vector>();
            /*
            判断单值映射r是否满足(1,.)==>2情况
            如果满足,则记录r的开始状态和结束状态,将其放入polish中,然后在reflection中删除r
            */
            for(int i = 0; i < reflection.size(); i++){
                Vector vector = reflection.get(i);
                int beginStatus = (int)vector.get(0);
                char curchar = (char)vector.get(1);
                int endStatus = (int)vector.get(2);
                if(curchar == '.'){
                    reflection.remove(i);
                    Vector subSetOfPolish = new Vector();
                    subSetOfPolish.add(beginStatus);
                    subSetOfPolish.add(endStatus);
                    polish.add(subSetOfPolish);
                }
            }
            /*
            如果字符集中包含'.',则将'.'从字符集中删去,然后将所有字符加入到字符集中
            注:此处我只加了a,b进去,这样就可以通过测试了,但标准做法应该是所有字符都应加进去
            */
            if(ch.contains('.')){
                ch.add('a');
                ch.add('b');
                ch.remove('.');
            }
            /*
            对于polish中的每一组向量v
            将(v[0],c)==>v[1]加入reflection中,c为全体字符
            */
            for(Vector vector : polish){
                for(char character :ch){
                    Vector subSet = new Vector();
                    subSet.add(vector.get(0));
                    subSet.add(character);
                    subSet.add(vector.get(1));
                    reflection.add(subSet);
                }
            }
            //构建NFA
            finallyStatus.add(status);
            characters.add(ch);
            NFA.add(reflection);
            NFA.add(finallyStatus);
            NFA.add(characters);
            return NFA;
        }
        return null;
    }

    //实现ε-closure闭包
    public Set<Integer> closure(Set<Integer> status, Vector<Vector> reflection){
        Set<Integer> result = new HashSet<Integer>();
        for(int curStatus : status){
            Stack<Integer> stack = new Stack<Integer>();
            //由于状态集合I的每个元素都属于ε-closure(I),所以将每个元素都推入栈中
            stack.push(curStatus);
            /*
            当stack栈非空时
            1.弹出栈顶元素i,将状态i加入到result集合中。
            2.计算状态i走一个ε可以到达的状态s,将s推入栈中
            */
            while(!stack.empty()){
                int temp = stack.pop();
                result.add(temp);
                for(int i = 0; i < reflection.size(); i++){
                    Vector subSet = reflection.get(i);
                    if((int)subSet.get(0) == temp && (char)subSet.get(1) == 'ε'){
                        stack.push((int)subSet.get(2));
                    }
                }
            }
        }
        return result;
    }

    //实现move(I,a)
    public Set<Integer> move(Set<Integer> status, char ch, Vector<Vector> reflection){
        Set<Integer> result = new HashSet<Integer>();
        for(int curStatus : status){
            for(int i = 0; i < reflection.size(); i++){
                Vector subSet = reflection.get(i);
                if((int)subSet.get(0) == curStatus && (char)subSet.get(1) == ch){
                    result.add((int)subSet.get(2));
                }
            }
        }
        return result;
    }

    //NFA确定化后给生成的DFA状态进行编号,如状态{1,2,3}变为状态1,状态{4,5,6}变为状态2
    public int numberStatus(Set<Integer> set, Vector statusSet){
        for(int i = 0; i < statusSet.size(); i++){
            if(statusSet.get(i).equals(set)){
                return i+1;
            }
        }
        return 0;
    }

    //NFA确定化
    public Vector<Vector> transNFAToDFA(Vector<Vector> NFA){
        //最终得到的DFA
        Vector<Vector> DFA = new Vector<Vector>();
        //statusSet存放的是DFA的状态集合
        Vector statusSet = new Vector();
        //DFA中的单值映射集合及其子集
        Vector<Vector> reflectionOfDFA = new Vector<Vector>();
        //DFA的终态集合,终态可能不止一个
        Vector<Integer> finallyStatusOfDFA = new Vector<Integer>();
        //NFA中的初始状态
        Set<Integer> beginStatusOfNFA = new HashSet<Integer>();
        beginStatusOfNFA.add(1);
        //NFA中的单值映射集合
        Vector<Vector> reflectionOfNFA = NFA.get(0);
        //NFA的终态结点
        int finallyStatusOfNFA = (int)NFA.get(1).get(0);
        //stack用于构建状态转换表
        Stack<Set> stack = new Stack<Set>();
        //取得NFA字母表,用于进行a弧转换
        Set<Character> alphabet = (Set<Character>)NFA.get(2).get(0);
        /*
        NFA确定化的第一步:将NFA初态进行ε-closure转化,得到DFA的初态
        将得到的初态加入到statusSet中
        将得到的初态push到stack栈中
         */
        Set<Integer> set = closure(beginStatusOfNFA, reflectionOfNFA);
        statusSet.add(set);
        stack.push(set);
        /*
        不断的获取DFA的新状态:
        第一步:当stack栈非空时,弹出栈顶集合rawSet,rawSet为DFA已有的一个状态。
        第二步:对rawSet先进行move(rawSet,ch)得到moveSet,ch为NFA的字母表中的每一个字符。
        第三步:对moveSet进行ε-closure转化得到closureSet,如果closureSet不在subSet中,
               则closureSet是DFA的一个新状态,将其添加到subSet中,然后将其push到栈中。
        第四步:在DFA中,rawSet经过字符ch到达状态closureSet。
        第五步:当栈空时,意味着DFA不会再有新状态产生,循环结束。
         */
        while(!stack.empty()){
            //第一步
            Set<Integer> rawSet = stack.pop();
            for(char ch : alphabet){
                //第二步
                Set<Integer> moveSet = move(rawSet, ch, reflectionOfNFA);
                //第三步
                Set<Integer> closureSet = closure(moveSet, reflectionOfNFA);
                if(!statusSet.contains(closureSet)){
                    statusSet.add(closureSet);
                    stack.push(closureSet);
                }
                //第四步
                Vector reflection = new Vector();
                reflection.add(rawSet);
                reflection.add(ch);
                reflection.add(closureSet);
                reflectionOfDFA.add(reflection);
            }
        }
        /*
        1.对reflectionOfDFA进行调整,将DFA的状态用一个数字代替
          如:得到的DFA某一单值映射是({1,2,3},a)==>{4,5,6},在原NFA中,1、2是单独的一个状态,
          但在DFA中,NFA的状态1、2、3的集合构成DFA的一个状态,所以我们可以将其调整为(1,a)==>2
        2.将包含NFA终态结点的DFA状态结点加入DFA的终态集合中
          如:NFA的终态是5,DFA有状态{1,2,3}、{3,4,5}、{5,6},则状态{3,4,5}、{5,6}都为DFA的一个终态
         */
        for(Vector reflection : reflectionOfDFA){
            Set<Integer> rawSet = (Set<Integer>)reflection.get(0);
            Set<Integer> closureSet = (Set<Integer>)reflection.get(2);
            int beginStatus = numberStatus(rawSet, statusSet);
            int endStatus = numberStatus(closureSet, statusSet);
            reflection.set(0, beginStatus);
            reflection.set(2, endStatus);
            if(rawSet.contains(finallyStatusOfNFA)){
                if(!finallyStatusOfDFA.contains(beginStatus)){
                    finallyStatusOfDFA.add(beginStatus);
                }
            }
            if(closureSet.contains(finallyStatusOfNFA)){
                if(!finallyStatusOfDFA.contains(endStatus)){
                    finallyStatusOfDFA.add(endStatus);
                }
            }
        }
        DFA.add(reflectionOfDFA);
        DFA.add(finallyStatusOfDFA);
        return DFA;
    }

    //模拟DFA的状态转化
    public int transStatus(int status, char ch, Vector<Vector> reflection){
        for(Vector vector : reflection){
            if((int)vector.get(0) == status && (char)vector.get(1) == ch){
                return (int)vector.get(2);
            }
        }
        return 0;
    }

    //判断字符串是否满足正则表达式
    public boolean match(char[] str, char[] pattern){
        //测试用例中的特殊情况
        if(pattern.length == 0){
            if(str.length == 0){
                return true;
            }else{
                return false;
            }
        }else{
            Vector<Vector> NFA = getNFA(pattern);
            Vector<Vector> DFA = transNFAToDFA(NFA);
            Vector<Vector> reflection = DFA.get(0);
            Vector<Integer> finallyStatus = DFA.get(1);
            if(str.length == 0){
                //测试用例中的特殊情况
                if(finallyStatus.contains(1)){
                    return true;
                }else{
                    return false;
                }
            }else{
                //从开始状态开始,不断的读入一个字符,得到转换后的下一状态
                int currentStatus = 1;
                for(int i = 0; i < str.length; i++){
                    char ch = str[i];
                    int nextStatus = transStatus(currentStatus,ch,reflection);
                    //此时情况为字符串仍有剩余字符,但DFA中不存在对应的单值映射,所以字符串非法
                    if(nextStatus == 0){
                        return false;
                    }
                    currentStatus = nextStatus;
                }
                /*
                当字符串全都读完时
                如果当前状态是终态,则说明DFA可以接收此字符,该字符匹配正则表达式
                否则该字符只是匹配正则表达式的一部分
                 */
                if(finallyStatus.contains(currentStatus)){
                    return true;
                }else{
                    return false;
                }
            }
        }
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值