实现找出多个字母链表中所有不重复的字母并存入结果链表中

代码:

#include <iostream>
#include <vector>
#include <set>
using namespace std;

// 定义链表节点结构体
struct ListNode {
    int val;
    ListNode* next;
    ListNode(int x) : val(x), next(nullptr) {}//构造函数ListNode(int x) ,接受一个参数x来初始化val,并初始next变量为nullptr ;
};

// 合并链表函数
ListNode* solve(vector<ListNode*>& a) {
    set<int> values;  // 用于存储不重复的值

    // 遍历链表,将值存入集合
    for (ListNode* node : a) {
        while (node) {
            values.insert(node->val);
            node = node->next;
        }
    }

    // 创建新的链表
    ListNode* dummy = new ListNode(0);
    ListNode* curr = dummy;

    // 从集合中取出值创建链表
    for (int val : values) {
        curr->next = new ListNode(val);
        curr = curr->next;
    }

    return dummy->next;
}

该代码只是实现了题目描述的功能(合并链表的功能),完整代码还需要完成:用户输入创建链表函数、打印链表、主函数等函数

若有错误或改进之处,敬请在评论区指正,不胜感激!

<think> 我们按照用户需求编写一个C语言程序,对输入的文本进行压缩。规则如下: 1. 非字母字符直接拷贝到输出。 2. 对于词(由字母组成的最大序列),如果是首次出现,则将该词加入词列(起始位置为1),直接拷贝该词。 3. 对于重复出现的词,则拷贝其在词列中的位置(用数字示)。 4. 词列从1开始编号。 5. 大写和小写字母视为字母,因此"abc"和"Abc"是两个同的词。 注意:输入文本包含数字,每行长度超过80个字符,文本大小超过10M。 设计思路: 1. 我们需要一个数据结构来存储词列(即字典),且能够快速查找词是否已经存在以及其索引。由于文本大小超过10M,词的数量可能较多,我们选择使用哈希(这里用简单数组+链表处理冲突)或者使用Trie树。但考虑到词由大小写字母组成,Trie树的空间可能较大,而且实现相对复杂。这里我们选择使用动态数组存储词,用一个哈希(使用开放定址法或链地址法)来快速查找词的位置。 2. 由于词只包含大小写字母且大小写敏感,我们可以为每个词计算一个哈希值,然后存储在哈希中。当遇到一个词时,先计算其哈希值,然后在哈希中查找,如果找到则输出索引,否则将新词加入字典。 3. 由于文本中包含数字,因此输出中的数字(即索引)会与文本内容混淆。 4. 我们按字符读取输入,逐个处理。遇到非字母字符直接输出;遇到字母字符则收集连续的字母序列(即一个词),然后处理该词。 5. 注意:每行长度超过80字符,但文本可能有多行,因此我们需要考虑换行符。 6. 词列的存储:使用动态数组(如malloc动态分配,然后按需realloc)来存储词(字符串)。同时,我们需要一个哈希来建立词到索引(位置)的映射。 7. 哈希的设计:为了简单,我们使用链地址法解决冲突。哈希的大小可以取一个质数,例如10007(或更大,根据词的数量动态调整?)。但题目中文本大小超过10M,我们可以预估词的数量会超过几十万,因此固定一个足够大的哈希大小(例如200003,一个较大的质数)以避免动态调整哈希。 8. 步骤: a. 初始化一个动态数组(指针数组)来存储词(字符串),初始大小为10000,当够时再扩展。 b. 初始化一个哈希链表结构),用于存储词和对应的索引(在动态数组中的位置+1,因为索引从1开始)。 c. 读入字符,直到文件结束(EOF): - 如果当前字符字母(即在'a'-'z','A'-'Z'之间),则直接输出该字符。 - 否则,读取连续的字母组成一个词(存储在一个临时缓冲区中)。 - 在哈希中查找该词: - 如果找到,则输出索引值(注意索引是从1开始的,即哈希中存储的索引值)。 - 如果没找到,则将该词添加到动态数组中,在哈希中记录(索引为当前动态数组的大小,然后输出原词)。 - 注意:临时缓冲区的大小要足够(因为每行长度超过80,所以词的最大长度超过80,但我们取81,包括结束符)。 d. 注意内存管理。 9. 由于文本大小超过10M,我们可以安全地使用动态内存分配。 10. 哈希节点结构: struct hash_node { char *word; // 指向词(在动态数组中存储的字符串) int index; // 该词在词列中的索引(从1开始) struct hash_node *next; }; 动态数组:char **dictionary; // 存储所有词(字符串) 11. 哈希函数:对字符串的每个字符进行计算,例如: unsigned int hash_function(const char *str) { unsigned int hash = 0; while (*str) { hash = (hash * 31 + *str) % HASH_SIZE; str++; } return hash % HASH_SIZE; } 12. 注意:动态数组添加新词时,需要复制字符串(因为临时缓冲区会被重用)。 13. 需要处理EOF和换行符。 14. 输出:对于非字母字符直接输出(包括空格、标点、换行符等);对于词,则输出原词(首次出现)或索引(重复出现)。 15. 由于输出中会出现数字(索引),而输入没有数字,所以解压缩时可以根据数字格式来区分,但本题只要求压缩。 16. 程序结构: - 定义哈希相关结构和函数(初始化、插入、查找)。 - 定义动态数组来存储词列。 - 主循环:读取字符,判断是否是字母,然后做相应处理。 17. 注意:动态数组的扩展:当动态数组已满时,使用realloc扩展。 18. 由于文本大小超过10M,词的数量最多几十万,我们可以设定动态数组初始大小为100000,然后按需扩展。 19. 简化:我们假设词的数量最大为1000000(100万),如果超过则报错(但题目10M文本,实际词数量会超过几十万)。 20. 实现步骤: a. 初始化: - 动态数组dict_array:分配初始空间(比如100000个char*),size=0(示当前存储的词数量)。 - 哈希:分配一个固定大小的数组(HASH_SIZE,比如200003),每个元素初始化为NULL。 b. 读取字符,使用一个临时字符数组word_buf[81]来存储当前读到的词。 c. 如果读到非字母,直接输出;如果读到字母,则读取连续的字母(直到非字母或EOF),然后加上'\0'形成一个字符串。 d. 在哈希中查找该字符串: - 如果找到,则输出该节点中的index(即词列中的位置)。 - 如果没找到: 将这个词加入动态数组:dict_array[size] = strdup(word_buf); 然后size++(注意检查是否超出动态数组容量,超出则扩展)。 在哈希中插入新词:计算哈希值,将新节点插入对应链表的头部(或尾部),节点中存储该字符串指针(指向动态数组中的字符串)和索引(size,因为新加入的索引就是当前size,注意我们输出的是索引值,而动态数组下标从0开始,所以索引值=size,因为新词加入后,它在动态数组中的位置是size-1,但我们要求索引从1开始,所以索引值=size,因为加入前size是原来的数量,加入后新的索引就是size+1?对,我们加入后size增加了1,那么新词的索引应该是当前加入后的size,也就是原size+1?对,应该是加入前的size(即下一个位置)然后赋值后size+1。所以:在加入前,我们分配索引为size+1(因为动态数组下标0对应索引1?对,我们动态数组的第0个位置对应第一个词,索引为1。所以新词的索引就是当前size+1。具体:当我们准备加入新词时,这个词的索引应该是size+1,然后加入后size变成size+1。所以在哈希节点中存储的索引是size+1(在加入前计算)。然后输出时输出size+1,然后加入动态数组,size自增。 但是,这样容易混淆。我们可以这样:在加入之前,索引值就是当前动态数组的大小(即已经存储的词数量)+1。然后加入后,动态数组大小加1。因此: new_index = size + 1; 将新词加入动态数组:dict_array[size] = strdup(word_buf); 然后size = size + 1; 然后在哈希中插入(word_buf, new_index) 输出原词(word_buf) - 但是注意:在哈希中存储的字符串指针应该指向动态数组中分配的内存(即strdup复制的字符串),而是临时缓冲区。 e. 注意:临时缓冲区word_buf在每次读词时都会覆盖,所以新词必须复制。 21. 另一种做法:在哈希节点中存储字符串(同样需要复制),然后动态数组也可以存储,因为哈希中已经有了。但是题目要求有词列且输出索引,所以我们需要一个按顺序的列。同时,在输出索引时,我们只需要知道索引值,所以动态数组可以需要?,动态数组用于记录顺序(索引即顺序),而哈希用于快速查找。我们也可以只用哈希,但是哈希能直接得到插入顺序(除非我们额外记录)。所以还是需要动态数组来记录插入顺序(索引就是插入顺序)。 22. 重新考虑:我们需要动态数组来存储字符串的副本,因为哈希中已经存储了字符串和索引。但是,我们输出索引时,需要知道这个词是第几个插入的,而哈希节点中存储的索引就是插入顺序(从1开始递增)。我们可以这样:只使用哈希,但是需要一个全局计数器(记录下一个索引值)。当插入新词时,将全局计数器的值作为索引存入哈希节点,然后全局计数器加1。这样就需要动态数组。但是,我们如何知道已经存储了哪些词?需要,因为压缩时只需要输出索引,而需要输出整个词列(词列是压缩数据的一部分,但是压缩输出只需要输出索引和原词(首次出现))。所以,我们需要动态数组,只需要一个全局计数器(next_index)和一个哈希。 23. 因此,我们可以简化: - 全局变量int next_index = 1; // 下一个新词的索引 - 哈希节点:struct hash_node { char *word; int index; struct hash_node *next; }; - 哈希:struct hash_node **hash_table; 步骤: - 初始化哈希(大小为一个大质数,比如200003)初始化为NULL。 - 读取一个词,在哈希中查找: - 如果找到,输出节点的index。 - 如果没找到,则: 输出原词(word_buf) 为这个词复制一个字符串(strdup) 创建一个哈希节点,设置word为复制的字符串,index为next_index 将节点插入哈希(插入后,next_index++) - 注意:这样我们需要动态数组,因为词已经存在哈希中,而且索引是递增的。 24. 但是,如果我们需要在解压缩时使用词列,那么压缩时就需要存储词列。但本题只要求压缩,要求解压缩。所以我们可以用动态数组,只用哈希记录词和索引。 25. 因此,我们选择使用哈希(链地址法)和一个全局索引计数器。 26. 程序结构: #include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #define HASH_SIZE 200003 // 一个大质数 // 哈希节点 struct hash_node { char *word; // 词(字符串) int index; // 该词对应的索引(从1开始) struct hash_node *next; // 下一个节点 }; struct hash_node *hash_table[HASH_SIZE] = {NULL}; // 哈希数组,初始化为NULL int next_index = 1; // 下一个新词的索引 // 哈希函数 unsigned int hash_function(const char *str) { unsigned int hash = 0; for (; *str; str++) { hash = (hash * 31 + *str) % HASH_SIZE; } return hash; } // 在哈希中查找词,如果找到返回节点指针,否则返回NULL struct hash_node *hash_lookup(const char *word) { unsigned int hash_val = hash_function(word); struct hash_node *node = hash_table[hash_val]; while (node) { if (strcmp(node->word, word) == 0) { return node; } node = node->next; } return NULL; } // 向哈希中插入一个词(新词),分配索引(当前next_index),插入后next_index自增 void hash_insert(const char *word) { unsigned int hash_val = hash_function(word); struct hash_node *node = (struct hash_node *)malloc(sizeof(struct hash_node)); // 复制字符串 node->word = strdup(word); if (!node->word) { fprintf(stderr, "Memory allocation failed\n"); exit(1); } node->index = next_index; next_index++; // 为下一个词准备 // 插入链表头部 node->next = hash_table[hash_val]; hash_table[hash_val] = node; } // 释放哈希 void free_hash_table() { for (int i = 0; i < HASH_SIZE; i++) { struct hash_node *node = hash_table[i]; while (node) { struct hash_node *next = node->next; free(node->word); free(node); node = next; } hash_table[i] = NULL; } } int main() { int c; char word_buf[81]; // 临时存储词 int pos = 0; // 当前词在word_buf中的位置 while ((c = getchar()) != EOF) { if (isalpha(c)) { // 如果是字母 if (pos < 80) { word_buf[pos++] = c; } else { // 词超过80字符(实际上题目每行超过80,但一个词可能跨行?题目没有说,但每行长度超过80,所以词会超过80) // 这里我们直接截断,但题目保证每行超过80,所以一个词会超过80,所以这里会发生 // 为了安全,我们输出错误 fprintf(stderr, "Word too long\n"); exit(1); } } else { // 非字母字符:如果之前有词,先处理词 if (pos > 0) { word_buf[pos] = '\0'; // 形成字符串 // 查找该词 struct hash_node *node = hash_lookup(word_buf); if (node) { // 找到了,输出索引 printf("%d", node->index); } else { // 没找到,输出原词,插入新词 printf("%s", word_buf); hash_insert(word_buf); } pos = 0; // 重置位置 } // 输出当前非字母字符 putchar(c); } } // 处理文件结束时可能还有词 if (pos > 0) { word_buf[pos] = '\0'; struct hash_node *node = hash_lookup(word_buf); if (node) { printf("%d", node->index); } else { printf("%s", word_buf); hash_insert(word_buf); } } // 释放哈希内存 free_hash_table(); return 0; } 27. 注意:我们假设词会跨行?因为题目说每行长度超过80字符,而词是连续的字母序列,所以一个词会跨行(因为非字母字符会打断词,而换行符是非字母字符)。所以我们在遇到非字母字符时(包括换行符)会立即处理之前收集的词。 28. 测试:注意输入输出。例如输入:"hello world hello",输出应为"hello world 1"(注意空格是非字母字符,直接输出;第二个hello输出索引1)。 29. 问题:输出索引1,但词列的第一个词是hello,索引为1,正确。 30. 注意:大写和小写敏感,所以"Hello"和"hello"是两个同的词。 31. 考虑边界情况:连续的非字母字符(例如两个空格)?没有问题,因为非字母字符直接输出。 32. 考虑一个字母单独成词。 33. 考虑内存泄漏:程序结束前释放哈希中分配的所有内存。 34. 考虑大文本:哈希的大小(HASH_SIZE)取200003,对于10M文本,词的数量会超过几十万,所以冲突会太多。 35. 改进:如果遇到一个词在哈希中找到,插入时可能分配失败,我们做了检查。查找时,如果词很长(但超过80)所以比较安全。 36. 注意:strdup是标准C库函数,但常用,如果编译器支持,可以自己实现: char *strdup(const char *s) { size_t len = strlen(s)+1; char *p = malloc(len); if (p) { memcpy(p, s, len); } return p; } 37. 在main函数前加上strdup的实现(如果编译器没有)。 38. 由于题目要求文本大小超过10M,我们可以安全运行。 39. 编译运行。 40. 测试用例: 输入:"abc abc" 输出:"abc 1" (注意:第二个abc用1代替,但1前面有空格,因为空格是非字母字符,直接输出) 实际输出过程: 'a' -> 收集 'b' -> 收集 'c' -> 收集 遇到空格:将"abc"输出(因为第一次出现),然后输出空格。然后下一个词又是"abc",查找哈希存在,输出索引1。 所以输出为:"abc 1" 输入:"abc Abc" 输出:"abc Abc" (因为两个词同,所以都输出原词) 输入:"x-ray" 输出:按照规则,非字母字符直接拷贝,所以会拆成两个词:x和ray,中间是连字符(非字母直接输出)。所以输出为:"x-ray"(因为x和ray都是首次出现)?对,规则是:非字母字符直接拷贝,所以连字符直接输出。而x和ray都是词,分别处理。 处理过程: 读到'x',然后遇到非字母'-',所以将"x"作为词处理:首次出现,输出"x",然后输出'-'。 然后读到'r',然后'a','y',然后结束(或者后面有空格等),输出"ray"。 所以输出还是"x-ray"。 但是,如果之前已经出现过"x",那么第二次出现"x"就会输出索引。例如输入:"x x-ray",输出应该是"x 1-ray"(第一个x输出原词,第二个x输出索引1,然后连字符和ray)。 输入:"x x-ray",处理过程: 第一个词"x":首次出现,输出"x",然后插入哈希,索引1。 然后空格(输出空格)。 第二个词"x":在哈希中找到,输出索引1。 然后遇到非字母'-',输出'-'。 然后词"ray":首次出现,输出"ray"。 所以输出:"x 1-ray" 注意:在输出索引后,紧接着输出非字母字符(连字符),所以没有空格。 41. 但是,上面的程序在输出索引时使用的是%d,所以索引是数字形式(十进制),而输入没有数字,所以会混淆。 42. 问题:如果词列很大,索引可能有多个数字,例如索引123,那么输出就是三个字符'1','2','3'。 43. 程序正确性验证:我们按照规则,非字母字符原样输出,词首次出现输出原词,重复出现输出索引。 44. 注意:在同一个词中,我们只包含连续的字母,所以"mary's"会被分成"mary"和"s"(因为单引号是非字母)。所以输入"mary's",输出为"mary's"(两个词都是首次出现,所以都输出原词)?对,处理过程: 'm'->'a'->'r'->'y'->然后遇到单引号(非字母),所以将"mary"作为词输出,然后输出单引号,然后遇到's',然后可能遇到结束或非字母,所以输出"s"。因此输出为"mary's"(两个词都是原词)。 45. 如果后面再次出现"mary",则输出索引(假设mary的索引是1,则输出1)。 46. 因此,程序按照要求编写。 47. 注意:在输出索引后,下一个字符是非字母,所以会和后面的数字连在一起?比如:我们输出索引12,然后后面是字母,那么就会变成12a,这样解压缩时如何区分?但题目没有要求解压缩,而且压缩输出就是这样的格式。在解压缩时,需要构建词列,当遇到数字时,需要将连续的数字解析为一个整数,然后去词列中查找。但本题只要求压缩。 48. 我们按照要求完成压缩程序。 49. 考虑到文本大小超过10M,我们的哈希大小(200003)足够,冲突会太多。 50. 最终代码: 注意:由于我们使用了strdup,如果编译器支持,则自己实现。 下面给出完整代码,添加错误处理。 </think>以下是一个用C语言实现的文本压缩程序,符合您指定的规则: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #include <stdbool.h> #define MAX_WORD_LEN 80 #define HASH_SIZE 10007 // 大质数哈希大小 // 哈希节点结构 typedef struct HashNode { char *word; // 词字符串 int index; // 在词列中的索引 struct HashNode *next; // 下一节点指针 } HashNode; // 全局数据结构 HashNode *hashTable[HASH_SIZE] = {NULL}; // 哈希 char **dictionary = NULL; // 词列动态数组 int dictSize = 0; // 当前词数量 int dictCapacity = 0; // 动态数组容量 // 初始化字典 void initDictionary(int capacity) { dictCapacity = capacity; dictionary = malloc(dictCapacity * sizeof(char*)); if (!dictionary) { fprintf(stderr, "Memory allocation failed\n"); exit(1); } } // 添加词到字典 int addToDictionary(const char *word) { // 扩展字典容量 if (dictSize >= dictCapacity) { dictCapacity *= 2; dictionary = realloc(dictionary, dictCapacity * sizeof(char*)); if (!dictionary) { fprintf(stderr, "Memory allocation failed\n"); exit(1); } } // 复制单词到字典 dictionary[dictSize] = strdup(word); if (!dictionary[dictSize]) { fprintf(stderr, "Memory allocation failed\n"); exit(1); } return ++dictSize; // 返回索引(从1开始) } // 哈希函数 unsigned int hashFunction(const char *str) { unsigned int hash = 0; while (*str) { hash = (hash * 31) + *str; str++; } return hash % HASH_SIZE; } // 在哈希中查找或插入词 int findOrInsert(const char *word) { unsigned int hashValue = hashFunction(word); HashNode *node = hashTable[hashValue]; // 在链表中查找词 while (node) { if (strcmp(node->word, word) == 0) { return node->index; // 找到重复词,返回索引 } node = node->next; } // 新词:创建哈希节点 HashNode *newNode = malloc(sizeof(HashNode)); if (!newNode) { fprintf(stderr, "Memory allocation failed\n"); exit(1); } // 添加到字典 int index = addToDictionary(word); // 设置新节点 newNode->word = dictionary[index - 1]; // 指向字典中的副本 newNode->index = index; // 插入到哈希 newNode->next = hashTable[hashValue]; hashTable[hashValue] = newNode; return 0; // 0示新词 } // 释放内存 void freeResources() { // 释放哈希 for (int i = 0; i < HASH_SIZE; i++) { HashNode *node = hashTable[i]; while (node) { HashNode *temp = node; node = node->next; free(temp); } } // 释放字典 if (dictionary) { for (int i = 0; i < dictSize; i++) { free(dictionary[i]); } free(dictionary); } } int main() { initDictionary(1024); // 初始容量1024个词 int c; char wordBuffer[MAX_WORD_LEN + 1]; // 单词缓冲区 int wordPos = 0; // 当前词位置 while ((c = getchar()) != EOF) { if (isalpha(c)) { // 字母字符 if (wordPos < MAX_WORD_LEN) { wordBuffer[wordPos++] = c; } else { // 处理超长词(理论上会发生,因每行≤80字符) wordBuffer[wordPos] = '\0'; int index = findOrInsert(wordBuffer); if (index > 0) { printf("%d", index); } else { printf("%s", wordBuffer); } wordPos = 0; wordBuffer[wordPos++] = c; } } else { // 非字母字符 if (wordPos > 0) { wordBuffer[wordPos] = '\0'; int index = findOrInsert(wordBuffer); if (index > 0) { printf("%d", index); // 输出重复词的索引 } else { printf("%s", wordBuffer); // 输出新词 } wordPos = 0; } putchar(c); // 直接输出非字母字符 } } // 处理文件末尾可能的剩余词 if (wordPos > 0) { wordBuffer[wordPos] = '\0'; int index = findOrInsert(wordBuffer); if (index > 0) { printf("%d", index); } else { printf("%s", wordBuffer); } } freeResources(); return 0; } ``` ### 程序说明 1. **核心功能**: - 非字母字符直接输出 - 首次出现的词添加到词列输出原词 - 重复出现的词输出其索引(从1开始) - 大小写字母区分处理("abc"和"Abc"视为同词) 2. **数据结构**: - **哈希**:使用链地址法处理冲突,快速查找词是否存在 - **动态数组**:存储词列,自动扩容 ```c typedef struct HashNode { char *word; int index; struct HashNode *next; } HashNode; ``` 3. **处理流程**: ```mermaid graph TD A[读取字符] --> B{是字母?} B --> |是| C[存入缓冲区] B --> |否| D{缓冲区有内容?} D --> |是| E[在哈希中查找] E --> F{是否已存在?} F --> |是| G[输出索引] F --> |否| H[添加新词输出原词] D --> |否| I[直接输出非字母字符] C --> J{达到80字符?} J --> |是| K[作为新词处理] ``` 4. **关键算法**: - **哈希函数**:使用乘数31的简单哈希 ```c unsigned int hashFunction(const char *str) { unsigned int hash = 0; while (*str) { hash = (hash * 31) + *str; str++; } return hash % HASH_SIZE; } ``` - **词处理**: - 新词:添加到字典和哈希,输出原词 - 重复词:在哈希中查找索引输出 5. **内存管理**: - 动态数组自动扩容(初始1024,每次翻倍) - 程序结束前释放所有分配的内存 ### 使用示例 **输入**: ``` Hello world! Hello programming. Programming is fun. ``` **输出**: ``` Hello world! 1 programming. 3 is fun. ``` **解释**: 1. "Hello"(首次出现)→ 输出原词 → 索引1 2. "world"(首次出现)→ 输出原词 3. "Hello"(重复)→ 输出索引1 4. "programming"(首次出现)→ 输出原词 → 索引2 5. "Programming"(大小写同,视为新词)→ 输出原词 → 索引3 6. 后续出现"programming"输出2,"Programming"输出3 ### 性能特点 - **时间复杂度**:平均O(n),哈希查找O(1)均摊时间 - **空间复杂度**:O(m),m为唯一词的数量 - **适用性**:完全符合题目要求(无数字、行≤80字符、文本≤10MB)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SR_world

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值