剑指Offer上

剑指Offer

面试题03. 数组中重复的数字

找出数组中重复的数字。
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
时间复杂度O(N),空间复杂度O(1)。

class Solution {
public:
    int findRepeatNumber(vector<int>& nums) {
        if(nums.size()==0){
            return NULL;
        }
        for(int i=0;i<nums.size();i++){
            while(nums[i]!=i){
            //见下面图片
                if(nums[i]==nums[nums[i]]){
                    return nums[i];
                }
                int temp=nums[i];
                nums[i]=nums[temp];
                nums[temp]=temp;
            }
        }
        return -1;
    }
};

在这里插入图片描述

面试题04. 二维数组中的查找

在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
思路:从右上角开始搜索若大于target则列数往左縮一列,若小于target则行数往下增一行

class Solution {
public:
    bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
        if(matrix.size()==0){
            return false;
        }
        int n=matrix.size()-1;
        int m=matrix[0].size()-1;
        int i=0;
        while(i<=n&&m>=0){
            if(matrix[i][m]>target){
                m--;
            }
            else if(matrix[i][m]<target){
                i++;
            }
            else{
            //target==matrix[i][m]
                return true;
            }
        }
        return false;
    }
};

面试题05. 替换空格

请实现一个函数,把字符串 s 中的每个空格替换成"%20"。
思路:先增加字符串长度至于其长度,从后往前依次替换填充的字符

class Solution {
public:
    string replaceSpace(string s) {
        if(s.size()==0)return s;

        int len=s.size();
        int count=0;
        for(int i=0;i<len;i++){
            if(s[i]==' '){
                count++;
            }
        }
        int l=2*count+s.size();
        int left=s.size()-1;
        int right= l-1;
        s.append(2*count,0);//增加s的长度
        while(left>=0){
            if(s[left]==' '){
                s[right]='0';
                s[right-1]='2';
                s[right-2]='%';
                right-=3;
            }
            else{
                s[right]=s[left];
                right-=1;
            }
            left--;
        }
        return s;
    }
};

面试题06. 从尾到头打印链表

输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
思路:使用栈

class Solution {
public:
    vector<int> reversePrint(ListNode* head) {
        stack<int>mystack;
        ListNode *p=head;
        while(p){
            mystack.push(p->val);
            p=p->next;
        }
        vector<int> vec;
        while(!mystack.empty()){
            vec.push_back(mystack.top());
            mystack.pop();
        }
        return vec;
    }
};

面试题07. 重建二叉树

输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
思路:使用4个指针分别指向子二叉树的头和尾
在这里插入图片描述

/**
 * 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 *build(vector<int>&preorder,int a1,int b1,vector<int>&inorder,int a2, int b2){
        TreeNode *root=new TreeNode(preorder[a1]);
        int i=a2;
        while(inorder[i]!=preorder[a1])i++;
        int left=i-a2;
        int right=b2-i;
        if(left>0)root->left=build(preorder,a1+1,a1+left,inorder,a2,i-1);
        if(right>0)root->right=build(preorder,a1+left+1,b1,inorder,i+1,b2);
        return root;
    }
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        if(preorder.size()==0||inorder.size()==0){
            return NULL;
        }
        return build(preorder,0,preorder.size()-1,inorder,0,inorder.size()-1);
    }
};

在这里插入图片描述

面试题09. 用两个栈实现队列

用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )

class CQueue {
public:
    CQueue() {}
    void appendTail(int value) {
        inner.push(value);
    }
    int deleteHead() {
        int temp;
        if(outer.empty()){
            while(!inner.empty()){
                outer.push(inner.top());
                inner.pop();
            }
        }
        //这儿是有先后顺序的
        if(inner.empty()&&outer.empty()){
            return -1;
        }
        int top=outer.top();
        outer.pop();
        return top;
    }
protected:
    stack<int> inner;
    stack<int> outer;
};
/**
 * Your CQueue object will be instantiated and called as such:
 * CQueue* obj = new CQueue();
 * obj->appendTail(value);
 * int param_2 = obj->deleteHead();
 */

面试题10- I. 斐波那契数列

写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项。斐波那契数列的定义如下:
F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

class Solution {
public:
    int fib(int n) {
        vector<int> res(101,0);
        res[0]=0;
        res[1]=1;
        for(int i=2;i<=n;i++){
            res[i]=(res[i-1]+res[i-2])%1000000007;
        }
        return res[n];
    }
};

面试题10- II. 青蛙跳台阶问题

一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
在这里插入图片描述

class Solution {
public:
    int numWays(int n) {
        vector<int> res(101,1);
        res.at(1)=1;
        res.at(2)=2;
        for(int i=3;i<=n;i++){
            res.at(i)=(res.at(i-1)+res.at(i-2))%1000000007;
        }
        return res.at(n);
    }
};

在这里插入图片描述

面试题11. 旋转数组的最小数字

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。
思路:见解题思路

class Solution {
public:
    int minArray(vector<int>& numbers) {
        int i=0;
        int j=numbers.size()-1;
        int mid=0;
        while(i<j){
            mid=i+(j-i)/2;
            //不能换3个if 原有见下图
            if(numbers[mid]>numbers[j]){
                i=mid+1;
            }
            else if(numbers[mid]<numbers[j]){
                j=mid;
            }
            else {
                j=j-1;
            }
        }
        return numbers[i];
    }
};

在这里插入图片描述

面试题12. 矩阵中的路径

请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。例如,在下面的3×4的矩阵中包含一条字符串“bfce”的路径(路径中的字母用加粗标出)。
[[“a”,“b”,“c”,“e”],
[“s”,“f”,“c”,“s”],
[“a”,“d”,“e”,“e”]]

class Solution {
public:
    bool  backtrack(vector<vector<char>>& board,int i,int j,string word,int index){
        //越界处理并判断是否相等
        if(i<0||i>=size||j<0||j>=sizeY||board[i][j]!=word[index]){
            return false;
        }
        //全部一致时
        if(index==word.size()-1){return true;}
        // 到这里说明已经board[i][j]=word[index]了
        // 为了防止深度遍历的时候,再回来用到这个字符,需要先修改一下
        char tmp=board[i][j];
        board[i][j]='/';
        //上下左右搜索
        bool res=backtrack(board,i-1,j,word,index+1)
                ||backtrack(board,i+1,j,word,index+1)
                ||backtrack(board,i,j-1,word,index+1)
                ||backtrack(board,i,j+1,word,index+1);
        // 从当前字符深度遍历完后,应该将字符设回原来的值,因为遍历到其他字符时,可能回用到这个字符
        board[i][j]=tmp;
        return res;
    }
    bool exist(vector<vector<char>>& board, string word) {
        size= board.size();
        if(size==0){return false;}
        sizeY=board[0].size();
        int i=0,j=0,index=0;
        for(int i=0;i<size;i++){
            for(int j=0;j<sizeY;j++){
                if(backtrack(board,i,j,word,0)){
                    return true;
                }
            }
        }
        return false;
    }
protected:
    int size;
    int sizeY;
};

面试题13. 机器人的运动范围

地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?
思路:DFS,满足本题条件则计数符加1,并设置该节点标志为true,同时判断该节点的子节点是否也满足本题条件,进行递归操作。

class Solution {
public:
//计算整数位数和函数
    int sum(int x){
        int s=0;
        while(x){
            s+=x%10;
            x/=10;
        }
        return s;
    }
    //深度优先搜索
    void dfs(int m,int n,int k,int i,int j,int &count,vector<vector<bool>>&visit){
        //满足位数和小于k
        if(sum(i)+sum(j)<=k){
            //满足条件的数量+1
            count++;
            /该格子被访问过
            visit[i][j]=true;
            //判断向右+1:(i+1, j),在矩阵内,且未被访问过
            if(i+1<m&&j<n&&!visit[i+1][j]){
                dfs(m,n,k,i+1,j,count,visit);
            }
            //判断向下+1:(i, j+1),在矩阵内,且未被访问过
            if(i<m&&j+1<n&&!visit[i][j+1]){
                dfs(m,n,k,i,j+1,count,visit);
            }
        }
    }
    int movingCount(int m, int n, int k) {
         //初始化标志数组
        vector<vector<bool>> visit(m,vector<bool>(n,false));
        //计数符
        int c=0;
        dfs(m,n,k,0,0,c,visit);
        return c;
    }
};

在这里插入图片描述
值得参考的文章:参考答案

面试题14- I. 剪绳子

给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m] 。请问 k[0]k[1]…*k[m] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
思路:DP 在这里插入图片描述

class Solution {
public:
    int cuttingRope(int n) {
        vector<int>dp(n+1,0);
        dp[1]=1;
        dp[2]=1;
        for(int i=3;i<n+1;i++){
            for(int j=0;j<=i/2;j++){
                //最外层的max是用来不同的j时的最大的乘积
                //例如F(4)=max(max(0,0),max(3*1,1*dp[3]),max(2*2,2*dp[2]))
                dp[i] = max(dp[i], max((i - j) * j, j * dp[i - j]));
            }
        }
        return dp[n];
    }
};

参考地址:参考

面试题14- II. 剪绳子 II

给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m] 。请问 k[0]k[1]…*k[m] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
思路:贪心算法,详情见书。

class Solution {
public:
    int cuttingRope(int n) {
        if(n<2){return 1;}
        if(n==2){return 1;}
        if(n==3){return 2;}
        long int sum=1;
        while(n>4){
            sum*=3;
            sum%=1000000007;
            n-=3;
        }
        return (sum*n)%1000000007;;
    }
};

面试题15. 二进制中1的个数

请实现一个函数,输入一个整数,输出该数二进制表示中 1 的个数。例如,把 9 表示成二进制是 1001,有 2 位是 1。因此,如果输入 9,则该函数输出 2。
思路:位运算 见书。

class Solution {
public:
    int hammingWeight(uint32_t n) {
        int count =0;
        while(n){
            count++;
            n=n&(n-1);
        }
        return count;
    }
};

面试题16. 数值的整数次方

实现函数double Power(double base, int exponent),求base的exponent次方。不得使用库函数,同时不需要考虑大数问题。
思路:快速幂算法。这里留意的是n为负数的时候,其实是需要将x取倒数。
另外一个坑是n为负数的时候,转为正数的时候会溢出,需要先转为long类型。

class Solution {
public:
    double myPow(double x, int n) {
        if (n == 0) return 1;
        long int N = n;//long 是long int的简称
        if (n < 0) {
            x = 1/x;
            N = -N;
        }
        double res = 1;
       
        while (N > 0) {
            //判断是否为奇数
            if ((N & 1) == 1) {
                res = res * x;
            }
            x *= x;
            N >>= 1;
        }
        return res;
    }
};

面试题17. 打印从1到最大的n位数

输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。

class Solution {
public:
    vector<int> printNumbers(int n) {
        vector<int>res;
        int max=pow(10,n);
        for(int i=1;i<max;i++){
            res.push_back(i);
        }
        return res;
    }
};

大数解法:

class Solution {
public:
    bool Increment(string& number) {
		bool isOverflow=false;//检测是否越界
        int nLength=number.size();
        //进位标志位
        int nTakeOver=0;
        for(int i=nLength-1;i>=0;i--){
            int nSum=number[i]-'0'+nTakeOver;
            if(i==nLength-1){
                //如果是个位,累加
                nSum++;
            }
            //如果个位遇10进1
            if(nSum==10){
                //如果在最高位进位则溢出
                if(i==0){
                    isOverflow=true;
                }
                else{
                    nTakeOver=1;
                    number[i]='0';//对个位重新设置值
                }
            }
            else{//没有进位
            //设置第i位数字并直接跳出循环
                number[i]=nSum+'0';
                break;
            }
        }
		return isOverflow;
	}
    void saveNumber(string number){
        string s;
        bool isBegin0=true;
        string::iterator it=number.begin();
        while(it!=number.end()){
            //判断高位是否为0例如 0020要越过高位的00然后从2开始输出
            if(isBegin0&&*it!='0')isBegin0=false;
            if(!isBegin0){s+=*it;}
            it++;
        }
        int num=stoi(s);
        res.push_back(num);
    }
    vector<int> printNumbers(int n) {
        if(n<=0){return res;}
        string number(n,'0');//创建一个能容纳最大值的字符数组//初始全部设置为0
        while(!Increment(number)){
            saveNumber(number);
        }
        return res;
    }
protected:
    vector<int> res;
};

递归解法

class Solution {
public:
    vector<int> printNumbers(int n) {
        string temp(n, '0');
        for (int i = 0; i <= 9; i++) {
            temp[0] = i + '0';
            sortedNumber(temp, n-1, 1);
        }
     
        return res;
    }
    void sortedNumber(string& temp, int n, int index) {
        if (n < index) {
            saveNum(temp);
            return;
        }
        else {
            for (int i = 0; i < 10; i++) {
                temp[index] = i + '0';
                sortedNumber(temp, n, index + 1);
            }
        }

    }
    void saveNum(string str) {
        auto idx = str.find_first_not_of('0');
        if (idx != string::npos) {
            res.push_back(stoi(str.substr(idx)));
        }
    }
private:
    vector<int>res;
};

面试题18. 删除链表的节点

给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。
返回删除后的链表的头节点。
注意:此题对比原题有改动
思路:很简单一遍过

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* deleteNode(ListNode* head, int val) {
        if(head==NULL){return NULL;}
        if(head->val==val){return head->next;}
        ListNode *p=head;
        ListNode *q= p;

        while(p!=NULL&&p->val!=val){q=p;p=p->next;}
        if(p==NULL){return head;}
        else{
            q->next=p->next;
        }
        return head;
    }
};

面试题20. 表示数值的字符串

请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100"、“5e2”、"-123"、“3.1416”、“0123"及”-1E-16"都表示数值,但"12e"、“1a3.14”、“1.2.3”、"±5"及"12e+5.4"都不是。

class Solution {
public:
    bool judgeP(string s){//判断底数是否合法
        bool result=false,point=false;
        int len=s.size();
        if (len==0)return false;
        for (int i=0;i<len;i++){
            if(s[i]=='+'||s[i]=='-'){//符号位不在第一位,返回false
                if(i!=0){return false;}
            }
            else if(s[i]=='.'){//有多个小数点,返回false
                if(point)return false;
                point =true;
            }
            else if(s[i]<'0'||s[i]>'9'){return false;}//非纯数字,返回false
            else {result=true;}
        }
        return result;
    }
    bool judgeS(string s){//判断指数是否合法
        int res=false;
        int len=s.size();
        //注意指数不能出现小数点,所以出现除符号位的非纯数字表示指数不合法
        if(len==0)return false;
        for(int i=0;i<len;i++){
            if(s[i]=='+'||s[i]=='-'){
                //符号位不在第一位,返回false
                if(i!=0)return false;
            }
            else if(s[i]<'0'||s[i]>'9'){return false;}//非纯数字,返回false
            else{res=true;}
        }
        return res;
    }
    bool isNumber(string s) {
        //1、从首尾寻找s中不为空格首尾位置,也就是去除首尾空格
        int i=s.find_first_not_of(' ');
        if(i==string::npos)return false;
        int j=s.find_last_not_of(' ');
        s=s.substr(i,j-i+1);
        if(s.empty())return false;
        //2、根据e或者E来划分底数和指数
        int e=s.find('e');
        int E=s.find('E');
        //3、指数为空,判断底数
        if((e==string::npos)&&(E==string::npos)) return judgeP(s);
         //4、指数不为空,判断底数和指数
        else {
            if (e==string::npos){return judgeP(s.substr(0,E))&&judgeS(s.substr(E+1));}
            else {return judgeP(s.substr(0,e))&&judgeS(s.substr(e+1));}
        }      
    }
};

面试题21. 调整数组顺序使奇数位于偶数前面

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。
思路:双指针

class Solution {
public:
    vector<int> exchange(vector<int>& nums) {
        int len=nums.size();
        int begin=0;
        int end=len-1;
        while(begin<end){
            while((begin<end)&&(nums[begin]&1)!=0)begin++;
            while((begin<end)&&(nums[end]&1)==0)end--;
            if(begin<end){
                int temp=nums[begin];
                nums[begin]=nums[end];
                nums[end]=temp;
            }
        }
        return nums;
    }
};

面试题22. 链表中倒数第k个节点

输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。例如,一个链表有6个节点,从头节点开始,它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个节点是值为4的节点。
思路:双指针(能独立完成)

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* getKthFromEnd(ListNode* head, int k) {
    	//特殊情况处理
        if(head==NULL||k==0){return NULL;}
        ListNode *p=head;
        ListNode *q=head;
        int count=k;
        while(count){
            p=p->next;
            count--;
        }       
        if(p==NULL){
        //判断是否k为正数第一个数
            if(count==0){
                return q;
            }
            //k大于节点数
            else{
                return NULL;
            }
        }
        while(p!=NULL){p=p->next;q=q->next;}
        return q;
    }    
};

142. 环形链表 II

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
说明:不允许修改给定的链表。
方法1:哈希set

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode *p1=head;
        unordered_set<ListNode*>s;
        while(p1!=NULL&&s.count(p1)==0){
            s.insert(p1);
            p1=p1->next;
        }
        if(s.count(p1)==1){
            return p1;
        }
        else{
            return NULL;
        }
    }
};

方法2:官方方法

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        if(head==NULL||head->next==NULL)return NULL;
        ListNode *p1=head;
        ListNode *p2=head;
        bool flag=false;
        while(p2!=NULL&&p2->next!=NULL){
            p1=p1->next;
            p2=p2->next->next;
            if(p1==p2){flag=true;break;}
        }
        if(flag){
            p2=p2->next;
            int count=1while(p1!=p2){p2=p2->next;count++;}
            ListNode *p1=head;
            ListNode *p2=head;
            while(count){p2=p2->next;count--;}
            while(p1!=p2){p1=p1->next;p2=p2->next;}
            return p1;
        }
        else{
            return NULL;
        }
    }
};

面试题24. 反转链表

定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
思路:定义3个指针暴力解决(一次过)

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if(head==NULL)return NULL;
        if(head->next==NULL)return head;
        ListNode *p=head;
        ListNode *temp=head;
        ListNode *q=NULL;
        while(p!=NULL){
            temp=p->next;
            p->next=q;
            q=p;
            p=temp;
        }
        return q;
    }
};

面试题25. 合并两个排序的链表

输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。
思路:暴力解决(一次搞定)

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        if(l1==NULL&&l2==NULL)return NULL;
        if(l1==NULL)return l2;
        if(l2==NULL)return l1;
        ListNode *p1=l1;
        ListNode *p2=l2;
        ListNode *res= new ListNode(0);
        ListNode *temp=res;
        while(p1!=NULL&&p2!=NULL){
            if(p1->val>=p2->val){
                temp->next=p2;
                p2=p2->next;
            }
            else{
                temp->next=p1;
                p1=p1->next;
            }
            temp=temp->next;
        }
        if(p1==NULL)temp->next=p2;
        else{
            temp->next=p1;
        }
        return res->next;
    }
};

面试题26. 树的子结构

输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)
B是A的子结构, 即 A中有出现和B相同的结构和节点值。
思路:递归
首先要遍历A找出与B根节点一样值的节点R
然后判断树A中以R为根节点的子树是否包含和B一样的结构

class Solution {
public:
    bool isSubStructure(TreeNode* A, TreeNode* B) {
        bool res = false;
        //当TreeA和TreeB都不为零的时候,才进行比较。否则直接返回false
        if (A!=NULL && B!=NULL)
        {
            //如果找到了对应TreeB的根节点的点
            if (A->val == B->val)
                //以这个根节点为为起点判断是否包含TreeB
                res = helper(A, B);
            //如果找不到,那么就再去TreeA的左子树当作起点,去判断是否包含TreeB
            if (!res)
                res = isSubStructure(A->left, B);
            //如果还找不到,那么就再去TreeA的右子树当作起点,去判断是否包含TreeB
            if (!res)
                res = isSubStructure(A->right, B);
        }
        // 返回结果
        return res;
    }
    bool helper(TreeNode* A, TreeNode* B)
    {
        //如果TreeB已经遍历完了都能对应的上,返回true
        if (B==NULL)
            return true;
        //如果TreeB还没有遍历完,TreeA却遍历完了。返回false
        if (A==NULL)
            return false;
        //如果其中有一个点没有对应上,返回false
        if (A->val != B->val)
            return false;
        //如果根节点对应的上,那么就分别去子节点里面匹配
        return helper(A->left, B->left) && helper(A->right, B->right);
    }
};

方法二:

class Solution {
public:
    bool helper(TreeNode *Node1,TreeNode *Node2){
        if(Node2==NULL)return true;
        if(Node1==NULL)return false;
        if(Node1->val!=Node2->val)return false;
        return helper(Node1->left,Node2->left)&&helper(Node1->right,Node2->right);
    }
    bool check(TreeNode *A,TreeNode *B){
        if(A==NULL)return false;
        if(A->val==B->val){
            if(helper(A,B)){
                return true;
            }
        }
        return check(A->left,B)||check(A->right,B);
    }
    bool isSubStructure(TreeNode* A, TreeNode* B) {
        if(B==NULL)return false;
        if(A==NULL)return false;
        return check(A,B);
    }
};

面试题27. 二叉树的镜像

请完成一个函数,输入一个二叉树,该函数输出它的镜像。
思路:直接交换节点(看完思路一次过)

/**
 * 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:
    void swap(TreeNode *Node){
        TreeNode *temp=Node->left;
        Node->left=Node->right;
        Node->right=temp;
    }
    void trackback(TreeNode *Node){
        if(Node==NULL)return ;
        swap(Node);
        if(Node->left)trackback(Node->left);
        if(Node->right)trackback(Node->right);
    }
    TreeNode* mirrorTree(TreeNode* root) {
        if(root==NULL)return NULL;
        trackback(root);
        return root;
    }
};

面试题28. 对称的二叉树

class Solution {
public:
    bool helper(TreeNode *left ,TreeNode *right){
        if(left==NULL&&right==NULL)return true;
        if(left==NULL||right==NULL)return false;
        if(left->val!=right->val)return false;
        return helper(left->left,right->right)&&helper(left->right,right->left);
    }
    bool isSymmetric(TreeNode* root) {
        if(root==NULL)return true;
        return helper(root,root);
    }
};

面试题29. 顺时针打印矩阵

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。

class Solution {
public:
    vector<int> spiralOrder(vector<vector<int>>& matrix) {
        if(matrix.size()==0||matrix[0].size()==0)return {};
        vector<int>res;
        int top=0,bottom=matrix.size()-1,left=0,right=matrix[0].size()-1;
        while(true){
            /*此算法模拟顺时针输出的过程,请联想打印过程*/
            /*1.top行从左到右遍历*/
            for(int i=left;i<=right;i++){
                res.push_back(matrix[top][i]);
            }
             /*top移动至下一行,并进行边界检测*/
            top++;
            if(top>bottom)break;
            /*2.right列从上到下遍历*/
            for(int i=top;i<=bottom;i++){
                res.push_back(matrix[i][right]);
            }
            right--;
            /*right左移,并进行边界检测*/
            if(left>right)break;
            /*3.bottom行从右往左遍历*/
            for(int i=right;i>=left;i--){
                res.push_back(matrix[bottom][i]);
            }
            bottom--;
            /*bottom行上移,并进行边界检测*/
            if(bottom<top)break;
            *4.left列从下往上遍历*/
            for(int i=bottom;i>=top;i--){
                res.push_back(matrix[i][left]);
            }
            left++;
            //*left右移,并进行边界检测*/
            if(left>right)break;
        }
        return res;
    }
};

面试题30. 包含min函数的栈

定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。
思路:书上的思路

class MinStack {
public:
    /** initialize your data structure here. */
    MinStack() {}
    void push(int x) {
        s_data.push(x);
        if(s_min.size()==0){
            s_min.push(x);
        }
        else{
            if(x<s_min.top())s_min.push(x);
            else s_min.push(s_min.top());
        }
    }
    
    void pop() {
        if(s_min.size()==0)return;
        s_data.pop();
        s_min.pop();
    }
    
    int top() {
        return s_data.top();
    }
    
    int min() {
        return s_min.top();
    }
protected:
    stack<int>s_data;
    stack<int> s_min;
};

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack* obj = new MinStack();
 * obj->push(x);
 * obj->pop();
 * int param_3 = obj->top();
 * int param_4 = obj->min();
 */

面试题31. 栈的压入、弹出序列

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。
思路:模拟,我们尝试按照 popped 中的顺序模拟一下出栈操作,如果符合则返回 true,否则返回 false。这里用到的贪心法则是如果栈顶元素等于 popped 序列中下一个要 pop 的值,则应立刻将该值 pop 出来。

class Solution {
public:
    bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
        stack<int> aux;
        int n=popped.size();
        int j=0;
        for(int i=0;i<pushed.size();i++){
            aux.push(pushed[i]);
            //不加会越界如[1,2,3,4,5] [4,5,3,2,1],最后aux会空,aux.top()为非法
            while(!aux.empty()&&aux.top()==popped[j]){
                aux.pop();
                j++;
            }
        }
        return aux.empty();
    }
};

面试题32 - II. 从上到下打印二叉树 II

从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。
思路:层序遍历(一次过)

/**
 * 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:
    vector<vector<int>> levelOrder(TreeNode* root) {
        if(root==NULL)return {};
        vector<vector<int>>res;
        vector<int>eachLevel;
        queue<TreeNode*> q;
        TreeNode *temp;
        q.push(root);
        int n=0;
        while(!q.empty()){
            n=q.size();
            for(int i=0;i<n;i++){
                temp=q.front();
                q.pop();
                eachLevel.push_back(temp->val);
                if(temp->left)q.push(temp->left);
                if(temp->right)q.push(temp->right);
            }
            res.push_back(eachLevel);
            eachLevel.resize(0);
        }
        return res;
    }
};

面试题32 - I. 从上到下打印二叉树

从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。
思路:层序遍历(一次过)

/**
 * 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:
    vector<int> levelOrder(TreeNode* root) {
        if(root==NULL)return{};
        queue<TreeNode*>q;
        vector<int>res;
        TreeNode *temp;
        q.push(root);
        while(!q.empty()){
            temp=q.front();
            res.push_back(temp->val);
            q.pop();
            if(temp->left)q.push(temp->left);
            if(temp->right)q.push(temp->right);
        }
        return res;
    }
};

面试题32 - III. 从上到下打印二叉树 III

请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。
思路:奇数插入容器前端

/**
 * 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:
    vector<vector<int>> levelOrder(TreeNode* root) {
        if(root==NULL)return {};
        vector<vector<int>>res;
        vector<int>EachLevel;
        queue<TreeNode*>q;
        TreeNode *temp;
        bool flag=false;
        q.push(root);
        while(!q.empty()){
            int len=q.size();
            for(int i=0;i<len;i++){
                temp=q.front();
                q.pop();
                if(temp->left)q.push(temp->left);
                if(temp->right)q.push(temp->right);
                if(flag){
                    EachLevel.insert(EachLevel.begin(),temp->val);
                }
                else{
                    EachLevel.push_back(temp->val);
                }
            }
            flag=!flag;
            res.push_back(EachLevel);
            EachLevel.resize(0);
        }
        return res;
    }
};

面试题33. 二叉搜索树的后序遍历序列

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。
思路:二叉搜索树特征,左子树所有节点的值<根节点的值<右子树节点的值
递归检查根节点是否将序列划分为左右子树

class Solution {
public:
    bool bfs(vector<int>& postorder,int begin,int end){
        if(begin>=end)return true;//单节点或空节点返回true
        int root=postorder[end];//后序遍历序列最后的值为根节点的值
        int l=begin;
        while(l<end&&postorder[l]<root)l++;//遍历左子树(值小于根),左子树序列post[lo, l);
        int r=l;
        while(r<end&&postorder[r]>root)r++;//遍历右子树(值大于根),右子树序列post[l, r);
        if(r!=end)return false;//若未将post[l, hi)遍历完,则非后序遍历序列 返回false
        return bfs(postorder,begin,l-1)&&bfs(postorder,l,end-1);//递归检查左右子树
    }
    bool verifyPostorder(vector<int>& postorder) {
        if(postorder.size()==0)return true;
        return bfs(postorder,0,postorder.size()-1);
    }
};

面试题34. 二叉树中和为某一值的路径

输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。
思路:回溯法,(vs studio疯狂测试得到的答案)

/**
 * 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:
    void bfs(TreeNode* root, int sum) {
        sum += root->val;
        if (root->left) {

            temp.push_back(root->left->val);
            bfs(root->left, sum);
            temp.pop_back();
        }
        if (root->right) {
            temp.push_back(root->right->val);
            bfs(root->right, sum);
            temp.pop_back();
      
        }
        if (root->left == NULL && root -> right == NULL) {
            if (sum == total) {
                res.push_back(temp);

            }
        }

    }
    vector<vector<int>> pathSum(TreeNode* root, int sum) {
        total = sum;
        if (root == NULL)return {};
        int n = 0;
        temp.push_back(root->val);
        bfs(root, n);
        return res;
    }
protected:
    vector<vector<int>>res;
    vector<int>temp;
    int total;
};

面试题35. 复杂链表的复制

请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。

/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* next;
    Node* random;
    
    Node(int _val) {
        val = _val;
        next = NULL;
        random = NULL;
    }
};
*/
class Solution {
public:
    Node* copyRandomList(Node* head) {
        if(head==NULL)return NULL;
        // Creating a new weaved list of original and copied nodes.
        Node *ptr=head;
        while(ptr!=NULL){
        // Cloned node
            Node *cpy=new Node(ptr->val);
      // Inserting the cloned node just next to the original node.
      // If A->B->C is the original linked list,
      // Linked list after weaving cloned nodes would be A->A'->B->B'->C->C'
            cpy->next=ptr->next;
            ptr->next=cpy;
            ptr=cpy->next;
        }
        ptr=head;
         // Now link the random pointers of the new nodes created.
         // Iterate the newly created list and use the original nodes' random pointers,
        // to assign references to random pointers for cloned nodes.

        while(ptr!=NULL){
            //因为NULL节点只有一个和其他节点不一样
            ptr->next->random=(ptr->random==NULL) ? NULL: ptr->random->next;
            ptr=ptr->next->next;
        }
        // Unweave the linked list to get back the original linked list and the cloned list.
    // i.e. A->A'->B->B'->C->C' would be broken to A->B->C and A'->B'->C'

        Node *ptrOldList=head;// A->B->C
        Node *ptrNewList=head->next;// A'->B'->C'
        Node *headNew= head->next;
        while(ptrOldList!=NULL){
            ptrOldList->next=ptrOldList->next->next;
            ptrNewList->next=(ptrNewList->next==NULL)? NULL:ptrNewList->next->next;
            ptrOldList=ptrOldList->next;
            ptrNewList=ptrNewList->next;
        }
        return headNew;
    }
};

面试题36. 二叉搜索树与双向链表

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。
在这里插入图片描述

/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* left;
    Node* right;

    Node() {}

    Node(int _val) {
        val = _val;
        left = NULL;
        right = NULL;
    }

    Node(int _val, Node* _left, Node* _right) {
        val = _val;
        left = _left;
        right = _right;
    }
};
*/
class Solution {
public:
    void helper(Node *root,Node *&head, Node *&pre){
        if(!root)return;
        helper(root->left,head,pre);
        if(!head){
        //先找头结点然后head指向头结点如上图里面的1
            head=root;
            pre=root;
        }
        else{
            pre->right=root;
            root->left=pre;
            pre=root;
        }
        helper(root->right,head,pre);
    } 
    Node* treeToDoublyList(Node* root) {
        if(root==NULL)return NULL;
        Node *head=NULL,*pre=NULL;
        helper(root,head,pre);
        head->left=pre;
        pre->right=head;
        return head;
    }
};

面试题37. 序列化二叉树

请实现两个函数,分别用来序列化和反序列化二叉树。
思路:先序遍历二叉树然后重组

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Codec {
public:

    // Encodes a tree to a single string.
    void dfs(TreeNode *root,string &res) {
         if (root == NULL) {
             res += "$,";
             return;
         }
         else {
             res += to_string(root->val);
             res += ',';
         }
         dfs(root->left,res);
         dfs(root->right,res);
     }
     // Encodes a tree to a single string.
     string serialize(TreeNode* root) {
         if (root == NULL)return "";
         string res = "";
         dfs(root,res);
         return res.substr(0, res.size() - 1);
        

    }
    TreeNode *help(int &i,string &s){
        if(s[i]=='$'){
            i+=2;
            return NULL;
        }
        int st=i;
        while(s[i]!=',')i++;
        TreeNode *root = new TreeNode (stoi(s.substr(st,i)));
        i++;
        root->left =help(i,s);
        root->right=help(i,s);
        return root;

    }
    // Decodes your encoded data to tree.
    TreeNode* deserialize(string data) {
        if(data=="")return NULL;
        int st=0;
        return help(st,data);
    }
}
// Your Codec object will be instantiated and called as such:
// Codec codec;
// codec.deserialize(codec.serialize(root));

面试题38. 字符串的排列

输入一个字符串,打印出该字符串中字符的所有排列。
你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。
思路:回溯剪枝,

class Solution {
public:

    void dfs(string &s, int start){
        if(start>=s.size()){
            res.push_back(s);
            return;
        }
        for(int i=start;i<s.size();i++){
            if(judge(s,start,i))continue;//如果start和i之间有字符等于s[i],则跳过(剪枝操作)
            swap(s[start],s[i]);
            dfs(s,start+1);
            swap(s[start],s[i]);
        }
    }
    bool judge(string &s,int start,int end){
        for(int i=start;i<end;i++){
            if(s[i]==s[end])return true;
        }
        return false;
    }
    vector<string> permutation(string s) {
        dfs(s,0);
        return res;
    }
protected:
    vector<string>res;
};

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

面试题39. 数组中出现次数超过一半的数字

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
思路:哈希表法

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        unordered_map<int ,int>map;
        for(auto x:nums){
            map[x]++;
            if(map[x]>nums.size()/2)return x;
        }
        return 0;
    }
};

面试题40. 最小的k个数

输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
方法二:堆
思路和算法
我们用一个大根堆实时维护数组的前 k 小值。首先将前 k 个数插入大根堆中,随后从第 k+1个数开始遍历,如果当前遍历到的数比大根堆的堆顶的数要小,就把堆顶的数弹出,再插入当前遍历到的数。最后将大根堆里的数存入数组返回即可。在下面的代码中,由于 C++ 语言中的堆(即优先队列)为大根堆,我们可以这么做。

class Solution {
public:
    vector<int> getLeastNumbers(vector<int>& arr, int k) {
        vector<int>vec(k,0);
        if(k==0)return vec;
        priority_queue <int>Q;
        for(int i=0;i<k;i++)Q.push(arr[i]);
        for(int i=k;i<arr.size();i++){
            if(Q.top()>arr[i]){
                Q.pop();
                Q.push(arr[i]);
            }
        }
        for(int i=0;i<k;i++){
            vec[i]=Q.top();
            Q.pop();
        }
        return vec;
    }
};

面试题41. 数据流中的中位数

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
例如,
[2,3,4] 的中位数是 3
[2,3] 的中位数是 (2 + 3) / 2 = 2.5
设计一个支持以下两种操作的数据结构:
void addNum(int num) - 从数据流中添加一个整数到数据结构中。
double findMedian() - 返回目前所有元素的中位数。
思路:大顶堆小顶堆,我们将中位数左边的数保存在大顶堆中,右边的数保存在小顶堆中。这样我们可以O(1) 时间内得到中位数

class MedianFinder {
public:
    /** initialize your data structure here. */
    MedianFinder() {

    }
    void addNum(int num) {
        high.push(num);//先两边都加num
        lowe.push(high.top());
        high.pop();
        //然后比较2个堆的大小,维护两个堆元素个数
        if(high.size()<lowe.size()){
            high.push(lowe.top());
            lowe.pop();
        }
    }
    double findMedian() {
        return high.size()>lowe.size()?double(high.top()):(high.top()+lowe.top())*0.5;
    }
protected:
    priority_queue<int>high;
    priority_queue<int,vector<int>,greater<int>>lowe;
};

/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder* obj = new MedianFinder();
 * obj->addNum(num);
 * double param_2 = obj->findMedian();
 */

面试题42. 连续子数组的最大和

输入一个整型数组,数组里有正数也有负数。数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。
要求时间复杂度为O(n)。
思路:动态规划

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        vector<int> dp(nums.size(),0);
        dp[0]=nums[0];
        int max=dp[0];
        for(int i=1;i<nums.size();i++){
            if(dp[i-1]>0){
                dp[i]=dp[i-1]+nums[i];
            }
            else{
                dp[i]=nums[i];
            }
            if(max<dp[i]){
                max=dp[i];
            }
        }
        return max;
    }
};

面试题44. 数字序列中某一位的数字

数字以0123456789101112131415…的格式序列化到一个字符序列中。在这个序列中,第5位(从下标0开始计数)是5,第13位是1,第19位是4,等等。
请写一个函数,求任意第n位对应的数字。
思路:和书上一样

class Solution {
public:
    int findNthDigit(int n) {
    if (n < 10)return n;
    int base = 1;
    //计算n在哪个位数区间,
    while (n > 9 * pow(10, base - 1) * base) {
        n -= 9 * pow(10, base - 1) * base;
        base++;
    }
    //若输入n为1001,则此时n是812还需减1,
    //因为之前少减了一个1 0~9是10个数while循环
    //里面第一次循环为n>9而非10所以要在下面减1;
    n--;
    int mod = n % base;//求对应数字的第几个位
    int res = n / base + pow(10, base - 1);
    string final = to_string(res);
    string total;
    total += final[mod];
    return stoi(total);
    }
};

面试题45. 把数组排成最小的数

输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。
思路:和书上一样,首先要明白静态成员函数和非静态成员函数的区别,静态成员函数属于类,非静态成员函数属于对象(可以这么理解),非静态成员函数里的参数默认有this指针,但是sort函数里的op,不需要这个隐形参数,所以,把这个函数设置位static成员函数则没有this指针,参数和普通函数是一样的了。

class Solution {
public:
    static bool cmp(string &a,string &b){
        return a+b<b+a;
    }
    string minNumber(vector<int>& nums) {
        vector<string>str_num;
        for(auto x:nums){
            str_num.push_back(to_string(x));
        }
        sort(str_num.begin(),str_num.end(),cmp);
        string res="";
        for(int i=0;i<str_num.size();i++){
            res+=str_num[i];
        }
        return res;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值