July leetcoding Challenge 2021

Gray Code
给出参数n,将区间[0,2n-1]之间的数字,从0开始排列成格雷码。格雷码的特点为,相邻的两数字转换为二进制时,仅有一位不同。

根据格雷码的特点,相邻数字只有一位不同,共有2n种不同情况,因此可以设计dfs对所有情况进行遍历。因为必须遍历完区间内全部数字,当n=16时,迭代的深度就达到了1e5,100%GG。

class Solution {
public:
    vector<int> ans;
    vector<int> grayCode(int n) {
        int lim = 1<<n;
        vector<bool> vis(lim,false);
        vector<vector<int> > adj;
        
        for(int i=0;i<lim;i++){
            vector<int> tmp;
            for(int dig = 0;dig<n;dig++){
                tmp.push_back(i^(1<<dig));
            }
            adj.push_back(tmp);
        }
        
        vis[0] = true;
        if(dfs(0,1,1<<n,adj,vis))
            ans.push_back(0);
        vector<int> reAns;
        for(int i=lim-1;i>=0;i--)
            reAns.push_back(ans[i]);
        return reAns;
    }
    bool dfs(int pos,int count,int lim,vector<vector<int> > adj,vector<bool> vis){
        int len = adj[pos].size();
        for(int i=0;i<len;i++){
            int node = adj[pos][i];
            if(vis[node]){
                if(count==lim){
                    return true;
                }
            }else{
                vis[node] = true;
                if(dfs(node,count+1,lim,adj,vis)){
                    ans.push_back(node);
                    return true;
                }else{
                    vis[node] = false;
                }
            }
        }
        return false;
    }
};

于是进一步改进,把递归改为循环实现。
这里利用stack先入后出的特性实现递归的循环实现。假设有如下递归过程
在这里插入图片描述部分递归顺序如下
在这里插入图片描述现仿照递归,每次遍历一个节点将全部子节点压入栈中,每次取栈顶元素作为下一个遍历对象,当栈中某元素被访问两次后将该元素出栈。
实例过程如下图所示。这个图有点小问题,AⅠ1和BⅠ1之间并不是相邻的,中间还有A的其他子节点,把这些节点补上的话能更直观一点。

在这里插入图片描述由此利用stack将递归改为循环,代码如下

class Solution {
public:
    vector<int> grayCode(int n) {
        int lim = 1<<n;
        vector<bool> vis(lim,false);
        vector<int> ans;
        
        stack<int> S;S.push(0);
        while(ans.size()<lim){
            int pos = S.top();
            
            if(vis[pos]){
                S.pop();ans.pop_back();
                vis[pos] = false;
                continue;
            }else{
                vis[pos] = true;
                ans.push_back(pos);
                for(int dig=0;dig<n;dig++){
                    int node = pos^(1<<dig);
                    if(!vis[node])
                        S.push(node);
                }
            }
            
        }
        return ans;
    }
};

Find K Closest Elements

给出一个数组arr,找出该数组中距离x最近的前k个元素。
定义数字a与数字x的距离为 ∣ a − x ∣ |a-x| ax,若数字a与数字b和x距离相同,则较小的数字被认为距离更近。

思路上比较简单,计算每个元素与x的距离并排序,取排序结果的前k个即可。
这里主要说一下快速排序的递归实现,每次递归调用将数组分成三部分。以数组中某元素作为基准,数组左侧元素均小于基准数,右侧均大于基准数。

步骤如下

  1. 选取最左侧元素作为基准数
  2. 从数组右侧遍历找到第一个小于基准数的元素,交换到右侧
  3. 从数组左侧遍历找到第一个大于基准数的元素,交换到左侧
  4. 重复2、3,直到左侧遍历指针与右侧遍历指针相遇
  5. 左侧遍历指针与右侧遍历指针相遇,填入基准数,对基准数左右两侧子数组进行排序

当进行到没有子数组时结束排序。

class Solution {
public:
    bool cmp(int a,int b,int x){
        int d1 = abs(a-x),d2 = abs(b-x);
        if(d1!=d2)
            return d1<d2;
        else
            return a<=b;
    }
    void quikSort(vector<int>& arr,int l,int r,int x){
        if(l>=r)
            return;
        int star = l,finish = r;
        int num = arr[star];
        while(star<finish){
            while(star<finish && cmp(num,arr[finish],x))
                finish --;
            if(star<finish)
                arr[star] = arr[finish];
            while(star<finish && cmp(arr[star],num,x))
                star ++;
            if(star<finish)
                arr[finish] = arr[star];
        }
        arr[star] = num;
        quikSort(arr,l,star-1,x);
        quikSort(arr,star+1,r,x);
    }
    vector<int> findClosestElements(vector<int>& arr, int k, int x) {
        quikSort(arr,0,arr.size()-1,x);
        
        vector<int> ans(arr.begin(),arr.end());
        ans.resize(k);
        sort(ans.begin(),ans.end());
        return ans;
    }
};

Max Sum of Rectangle No Larger Than K
求小于k的最大子矩阵元素和
brute force :遍历每一个包含元素matrix[i][j]的子矩阵

class Solution {
public:
    int maxSumSubmatrix(vector<vector<int>>& matrix, int k) {
        int n = matrix.size(),m = matrix[0].size();
        int sum[n+1][m+1];
        for(int i=0;i<=m;i++)
            sum[0][i] = 0;
        for(int i=1;i<=n;i++){
            sum[i][0] = sum[i-1][0];
            for(int j=1;j<=m;j++){
                sum[i][j] = sum[i][j-1] + matrix[i-1][j-1];
                
            }
           
            for(int j=1;j<=m;j++)
                sum[i][j] += sum[i-1][j];
        }
        
        int ans = INT_MIN;
        for(int x2=0;x2<n;x2++){
            for(int y2=0;y2<m;y2++){
                for(int x1=0;x1<=x2;x1++){
                    for(int y1=0;y1<=y2;y1++){
                        int tmp = sum[x2+1][y2+1] - sum[x1][y2+1] - sum[x2+1][y1] + sum[x1][y1];
                        if(tmp<=k)
                            ans = max(ans,tmp);
                        }
                }
                if(ans==k)
                    break;
            }
            if(ans==k)
                break;
        }
        
        return ans;
    }

以上n2做法过于残忍,再给一个更加优美的方案。这里推荐leetcode官方题解,写的非常详细,通俗易懂。
首先考虑子矩阵只有一行的情况,这样即使子矩阵为多行的情况下,也能通过列作和化为一维。
记sum[i]表示前i个元素和。若当前位置i的前缀和为S,那么想让以位置i结束的子矩阵和小于k,则至少移除和为 x = S − k x = S - k x=Sk的部分。为了快速搜索x,将所有前缀和放入有序集和中进行二分搜索。

class Solution {
public:
    int maxSumSubmatrix(vector<vector<int>>& matrix, int k) {
        int n = matrix.size(),m = matrix[0].size();
        vector<int> cum(m+1,0),store(m+1,0);
        
        int ans = INT_MIN;
        for(int i=0;i<n;i++){
            for(int row = i;row>=0;row--){
                set<int> sum;
                sum.insert(0);
                for(int j=1;j<=m;j++){
                    store[j] += matrix[row][j-1];
                    cum[j] += cum[j-1] + store[j];
                    int S = cum[j]; 
                    auto found = sum.lower_bound(S - k);
                    if(found!=sum.end()){
                        int val = S - *found;
                        if(val<=k)
                            ans = max(val,ans);
                    }
                    sum.insert(S);
                }
                for(int _=0;_<=m;_++)
                    cum[_] = 0;
            }
            for(int _=0;_<=m;_++)
                store[_] = 0;
        }
        return ans;
    }
};

Count Vowels Permutation
基础dp,状态方程见题意

class Solution {
public:
    const int MOD = 1e9+7;
    int countVowelPermutation(int n) {
        vector<vector<long long> > dp(n,vector<long long>(5,0));
        for(int i=0;i<5;i++)
            dp[0][i] = 1;
        for(int i=1;i<n;i++){
            dp[i][0] = (dp[i][0] + dp[i-1][1]) % MOD;
            dp[i][1] = (dp[i][1] + dp[i-1][0] + dp[i-1][2]) % MOD;
            for(int _=0;_<5;_++)
                if(_!=2)
                    dp[i][2] = (dp[i][2] + dp[i-1][_]) % MOD;
            dp[i][3] = (dp[i][3] + dp[i-1][2] + dp[i-1][4]) % MOD;
            dp[i][4] = (dp[i][4] + dp[i-1][0]) % MOD;
        }
        
        long long ans = 0;
        for(int i=0;i<5;i++)
            ans = (ans + dp[n-1][i]) % MOD;
        
        return (int)ans%MOD;
    }
};

Reshape the Matrix

class Solution {
public:
    vector<vector<int>> matrixReshape(vector<vector<int>>& mat, int r, int c) {
        int m = mat.size(),n = mat[0].size();
        if(n*m == r*c){
            vector<vector<int> > ans (r,vector<int>(c,0));
            for(int i=0;i<m;i++){
                for(int j=0;j<n;j++){
                    int index = i*n + j;
                    int row = index / c;
                    int col = index - row*c;
                    ans[row][col] = mat[i][j];
                }
            }
            return ans;
        }else{
            return mat;
        }
    }
};

Reduce Array Size to The Half
贪心,按数字出现次数降序排序,依次删除出现次数最多的

class Solution {
public:
    int minSetSize(vector<int>& arr) {
        int n = arr.size();
        int aim = n/2;
        priority_queue<int> heap;
        map<int,int> sta;
        for(int ele : arr){
            sta[ele] ++;
        }
        for(auto it=sta.begin();it!=sta.end();it++){
            heap.push(it->second);
        }
        
        int ans = 0;
        while(n>aim){
            int num = heap.top();
            heap.pop();
            
            n -= num;
            ans++;
        }
        return ans;
    }
};

Kth Smallest Element in a Sorted Matrix


Maximum Length of Repeated Subarray
找出两个序列的最长公共子列。
记dp[i][j] 为以序列nums1位置i结尾 与 以序列nums2位置j结尾 的最长公共子列长度。当nums[i]==nums[j]时,从上一状态进行转移,dp[i][j] = dp[i-1][j-1]+1。
因为序列中数字范围较小,将nums2中出现的数字与其位置进行映射,便于查找。

class Solution {
public:
    int findLength(vector<int>& nums1, vector<int>& nums2) {
        int len1 = nums1.size(),len2 = nums2.size();
        vector<int> sta[128];
        for(int i=0;i<len2;i++){
            sta[nums2[i]].push_back(i);
        }
        vector<vector<int> > dp(len1,vector<int>(len2,0));
        //ini
        for(int i=0;i<sta[nums1[0]].size();i++){
            int j=sta[nums1[0]][i];
            dp[0][j] = 1;
        }
        
        int ans = 0;
        for(int i=1;i<len1;i++){
            int ele = nums1[i];
            for(int ind=0;ind<sta[ele].size();ind++){
                int j = sta[ele][ind];
                if(j!=0){
                    dp[i][j] = dp[i-1][j-1] + 1;
                }else{
                    dp[i][j] = 1;
                }
                ans = max(ans,dp[i][j]);
            }
        }
        return ans;
    }
};

Longest Increasing Subsequence
经典最长单调上升子列LIS问题,dp入门题目之一笨比如我还是设计不出nlogn的
首先说一下比较简单的dp设计,复杂度n2。记dp[i]表示以nums[i]结尾的最长上升列,在遍历数组nums时,只需查找 当前位置pos前,结尾元素小于nums[pos] 的所有子列中最长的一个。

for(int i = 0;i<pos;i++)
	if(nums[i]<nums[pos])
		dp[pos] = max(dp[pos],dp[i]+1)

如此遍历数组就能得到LIS了。
但是在遍历过程中是有很多重复的
// fig.replace
为了避免重复访问,需要在 所有位置pos前,最大的dp[i]值 中快速查找半天没想出来怎么设计这么个结构呜呜呜

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int n = nums.size();
        vector<int> len;
        len.push_back(nums[0]);
        
        int ans = 1;
        for(int i=1;i<n;i++){
            int ele = nums[i];
            if(ele>len.back()){
                len.push_back(ele);
                ans ++;
            }else{
                auto it = lower_bound(len.begin(),len.end(),ele);
                *it = ele;
            }
        }
        return ans;
    }
};

Find Median from Data Stream
multiset实践应用。

class MedianFinder {
public:
    /** initialize your data structure here. */
    MedianFinder() {
        len = 0;
        isEven = true;
    }
    
    void addNum(int num) {
        store.insert(num);
        isEven = !isEven;
        if(len==0)
            it = store.begin();
        else{
            int mid = *it;
            if(num >= mid && !isEven) it = next(it);
            if(num < mid && isEven) it = prev(it);
        }
        len ++;
    }
    
    double findMedian() {
        if(isEven){
            return ((double)(*it+*next(it)))/2;
        }else{
            return (double)*it;
        }
    }
private:
    int len;
    bool isEven;
    multiset<int> store;
    multiset<int>::iterator it ;
};

Isomorphic Strings
考察字符串s是否能映射到字符串t。注意s中两个字母不能映射到t的同一个字母上,测试数据如s=“abc”,t=“iii”

class Solution {
public:
    bool isIsomorphic(string s, string t) {
        vector<int> mapping(130,-1);
        vector<bool> used(130,false);
        int len = s.size();
        
        bool ans = true;
        for(int i=0;i<len;i++){
            int c1 = s[i],c2 = t[i];
            int  mValue = mapping[c1];
            if((mValue==c2) || (!used[c2] && mValue<0)){
                mapping[c1] = c2;
                used[c2] = true;
            }else{
                ans = false;
                break;
            }
        }
        return ans;
    }
};

Find Peak Element
太懒了写个双指针,打卡完毕

class Solution {
public:
    int findPeakElement(vector<int>& arr) {
        int n = arr.size();
        if(n==1 || arr[0]>arr[1])
            return 0;
        if(n-2 >= 0 && arr[n-2]<arr[n-1])
            return n-1;
        int left = 1,right = n-2;
        int ans;
        while(left<=right){
            if(isPeak(arr[left],arr[left-1],arr[left+1])){
                ans = left;
                break;
            }else{
                left += getStep(arr[left],arr[left-1],arr[left+1]);
            }
            if(isPeak(arr[right],arr[right+1],arr[right-1])){
                ans = right;
                break;
            }else{
                right -= getStep(arr[right],arr[right+1],arr[right-1]);
            }
        }
        return ans;
    }
    bool isPeak(int a,int b,int c){
        return a>b && a>c;
    }
    int getStep(int a,int b,int c){
        return 1;
    }
};

Custom Sort String
比较简单的构造题。把统计str中各字母出现的次数,按order给的顺序排列,再把order中没出现的加上

class Solution {
public:
    string customSortString(string order, string str) {
        int len1 = order.size(), len2 = str.size();
        vector<bool> appear(26,false);
        map<char,int> sta;

        string sb1;
        for(int i=0;i<len1;i++){
            int index = order[i] - 'a';
            appear[index] = true;
        }
        for(int i=0;i<len2;i++){
            sta[str[i]] ++;
            if(!appear[str[i]-'a'])
                sb1 += str[i];
        }

        string sb2;
        for(int i=0;i<len1;i++){
            char c = order[i];
            while(sta[c] -- )
                sb2 += c;
        }

        return sb2 + sb1;
    }
};

Valid Triangle Number
在给出的数组中,找出所有可以构成三角形的不同三元组(值相同位置不同视为不同)。
定二搜一。假设b<a<c,根据三角形性质应满足如下方程
{ a + b > c a − b < c \begin{cases} a + b > c \\ a - b < c \end{cases} {a+b>cab<c
故a b 值确定,则c范围确定,对nums排序,查找第三条可行边的范围。

class Solution {
public:
    int triangleNumber(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        int ans = 0;
        
        int len = nums.size();
        for(int i=0;i<len;i++){
            int b = nums[i];
            for(int j=i+1;j<len;j++){
                int a = nums[j];
                int lower = a-b+1,upper = a+b-1;
                if(upper<lower)
                    continue;

                int ind1 = (lower_bound(nums.begin()+j+1,nums.end(),lower) - nums.begin());
                int ind2 = (upper_bound(nums.begin()+j+1,nums.end(),upper) - nums.begin());
                ans += ind2 - ind1;
            }
        }
        return ans;
    }
};

Three Equal Parts
突破点在于arr中1的个数,如果arr中1的个数是3的倍数,那么该数组可能分为三部分,否则不可能。
当数组满足这个必要不充分条件,在按1个数划分的基础上,根据0的位置进行调整,最后比对三部分是否一致

class Solution {
public:
    vector<int> threeEqualParts(vector<int>& arr) {
        int len = arr.size();
        int cnt = 0;
        for(auto ele : arr){
            cnt += (ele==1);
        }
        if(cnt%3 != 0){
            return vector<int>(2,-1);
        }
        if(cnt==0){
            vector<int> ans;
            ans.push_back(0);
            ans.push_back(len-1);
            return ans;
        }
        vector<int> ans;
        int ind = cnt/3,tmp = 0;
        for(int i=0;i<len;i++){
            tmp += (arr[i]==1);
            if(tmp==ind){
                ans.push_back(i);
                break;
            }
        }
        tmp = 0;
        for(int i=len-1;i>=0;i--){
            tmp += (arr[i]==1);
            if(tmp==ind+1){
                ans.push_back(i+1);
                break;
            }
        }
        
        int suf0 = 0;
        tmp = len-1;
        while(arr[tmp]==0){
            suf0 ++;
            tmp --;
        }
        ans[0] += suf0,ans[1] += suf0;
        int leading0 = 0;
        tmp = 0;
        while(arr[tmp]==0){
            leading0 ++;
            tmp ++;
        }
        
        bool isAns = true;
        for(int i=0;i<=ans[0]-leading0;i++){
            if(arr[ans[0]-i]!=arr[ans[1]-i-1] || arr[ans[0]-i]!=arr[len-1-i]){
                isAns = false;
                break;
            }
        }
        if(isAns){
            return ans;
        }else
            return vector<int> (2,-1);
    }
};

Reverse Nodes in k-Group
翻转链表的plus版,每k个一组进行翻转。
在这里插入图片描述

大概过程如图所示虽然画的我也不太明白
经过k个节点后讲链表翻转,并接到原链表上,这样保证在不足k个节点时,该部分仍按原序排列,不被翻转。
如果接下来有不少于k个节点,再进行翻转,注意记录翻转链表的头尾,再将这部分接到上一个翻转链表上

class Solution {
public:
    ListNode* ans;
    ListNode* reverseKGroup(ListNode* head, int k) {
        start = head;
        first = true;
        dfs(head,1,k);
        return ans;
    }
    void reserve(ListNode* now,ListNode* pre,int num,int k){
        if(num==k){
            if(first){
                first = false;
                ans = now;
            }
            tmpHead = now;
        }else
            reserve(now->next,now,num+1,k);
        now->next = pre;
    }
    void dfs(ListNode* node,int num,int k){
        if(node==NULL){
            return;
        }
        if(num==k){
            
            reserve(start,node->next,1,k);
            if(tail!=NULL)
                tail->next = tmpHead;
            tail = start;
            start = start->next;
            dfs(start,1,k);
        }else{
            dfs(node->next,num+1,k);
        }
    }
private:
    ListNode* start;
    ListNode* tail;
    ListNode* tmpHead;
    bool first;
};

Lowest Common Ancestor of a Binary Search Tree
由于限定为二叉搜索树,所以不必真的去了解LCA算法看了之后是真几巴难
如果节点node是p,q的ancestor,那么p,q一定位于node的同一侧,也就是都在左子树或右子树。又因为二叉搜索树的性质,只需要比较node和p,q节点值的大小,就可以快速判断p,q是否在同一侧。

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        int rootVal = root->val;
        int qVal = q->val,pVal = p->val;
        if(rootVal > pVal && rootVal > qVal)
            return lowestCommonAncestor(root->left,p,q);
        if(rootVal < qVal && rootVal < pVal)
            return lowestCommonAncestor(root->right,p,q);
        return root;
    }
};

Shuffle an Array

生成原数组的随机数组,Knuth洗牌算法。
下面先给一个伪随机算法:将位置i的元素,等概率的与其他元素(包括自身)交换。

for(int i=0;i<len;i++){
	int pos = rand()%len;
	swap(nums[pos],nums[i]);
}

这种算法会生成lenlen种排列方式,但实际上长度为len的集和全排列只有len!种,而lenlen>len!,也就是说上述算法产生了一些重复排列。
在这里插入图片描述

假如每个排列都重复了t次,那么还是符合题意,使得每种排列等概率出现,但是lenlen和len!应该是不存在什么倍数关系。
所以我们需要删掉重复出现的排列,在这里可以模拟全排列的计算过程,控制位置i可以交换元素的范围,从而产生所有排列等概率出现的结果集。

参考文献:洗牌的正确姿势-Knuth shuffle算法

class Solution {
public:
    vector<int> copy;
    Solution(vector<int>& nums) {
        copy = vector<int>(nums);
    }
    
    /** Resets the array to its original configuration and return it. */
    vector<int> reset() {
        return copy;
    }
    
    /** Returns a random shuffling of the array. */
    vector<int> shuffle() {
        vector<int> ans(copy);
        int len = ans.size();
        for(int i=0;i<len-1;i++){
            int add = rand()%(len-i);
            swap(ans[i],ans[i+add]);
        }
        return ans;
    }
};

Push Dominoes
当前为’.‘时,不处理,跳过
当前为’L’时,查询是否有’R’未处理,如果有根据长度判断中间位置情况,没有推到头
当前为’R’,查询是否有’R’未处理,没有记录位置,有推到上一个’R’的位置

class Solution {
public:
    string pushDominoes(string str) {
        int len = str.size();
        string ans;
        for(int i=0;i<len;i++)
            ans += '.';
        
        int pos;
        int last = -1;
        for(int i=0;i<len;i++){
            switch(str[i]){
                case 'L':
                    if(last<0){
                        pos = i;
                        while(pos>=0 && ans[pos]=='.'){
                            ans[pos --] = 'L';
                        }
                    }else{
                        int range = (i-last+1)/2;
                        for(int _=0;_<range;_++){
                            ans[last+_] = 'R';
                            ans[i-_] = 'L';
                        }
                        last = -1;
                    }
                    break;
                case 'R':
                    if(last<0){
                        last = i;
                    }else{
                        pos = last;
                        while(pos<i){
                            ans[pos ++] = 'R'; 
                        }
                        last = i;
                    }
                    break;
                case '.':
                    break;
            }
        }
        
        if(last>=0)
            while(last<len)
                ans[last ++] = 'R';
        return ans;
    }
};

Partition Array into Disjoint Intervals
将给出的数组分为两部分,left最大值小于right最小值。
记left[i]为[0,i]区间内最大值,right[i]为[i,len)最小值,判断条件如题意。

class Solution {
public:
    int partitionDisjoint(vector<int>& nums) {
        int len = nums.size();
        
        vector<int> left(nums);
        vector<int> right(nums);
        
        for(int i=1;i<len;i++){
            left[i] = max(left[i],left[i-1]);
        }
        for(int i=len-2;i>=0;i--){
            right[i] = min(right[i],right[i+1]);
        }
        
        int ans = 1;
        for(;ans<len;ans++){
            if(left[ans-1]<=right[ans])
                break;
        }
        return ans;
    }
};

Binary Tree Pruning
如果当前节点node子树不存在值为1的节点,删除该子树。
利用dfs查找值为1的节点,如果存在返回该节点,不存在返回nullptr。父节点根据子树查询信息判断是否保留子树。
当且仅当左右子树为nullptr且当且节点node值不为1,表示以node为root的子树节点全为0,不符合题意,返回nullptr;否则表示以node为root的子树节点不全为0,应该返回该节点

class Solution {
public:
    TreeNode* pruneTree(TreeNode* node){
        if(node==nullptr)
            return nullptr;
        node->left = pruneTree(node->left);
        node->right = pruneTree(node->right);

        if(node->left==nullptr && node->right==nullptr && node->val == 0){
            return nullptr;
        }else{
            return node;
        }
    }
};
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值