子集

本文深入探讨了子集生成算法的两种实现方式,通过具体示例解析了如何为给定的整数数组生成所有可能的子集(幂集),并确保解集中不包含重复子集。代码一采用递归策略,而代码二则使用迭代方法,两者均有效解决了问题。

给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。

输入: nums = [1,2,3]
输出:
[
  [3],
  [1],
  [2],
  [1,2,3],
  [1,3],
  [2,3],
  [1,2],
  []
]
代码一:
先添加特殊的;
后遍历组合:效率低
def subsets(nums):
    if not nums:return [[]];
    leg=len(nums);
    res=[[],nums];
    if leg==1:
        return res;
    for i in nums:res.append([i]);
    
    def run(re,j,s):
        if j==0:
            res.append(re+[]);
        else:
            for i in range(s,leg):
                lp=nums[i]
                re.append(lp);
                run(re,j-1,i+1);
                re.pop();
    if leg>2:
        for i in range(2,leg):
            run([],i,0);
    return res;
代码二:

def subsets(nums):
    res=[[]];
    for i in nums[::-1]:
    	#代码段一: res可以加[:] 也可以不加
        res=res+[[i]+k for k in res[:]];
        #代码段二:
        # 对以下代码的解释: 
        #注意代码中res[:]是必须的,不然的话会反复循环
        #因为切片是引用新的对象,
		# 此时在循环中res[:]是不更新的,而res是不断有元素push进去的,很trick
        # for j in res[:]:
        #     res.append(j+[i]);
    return res;
        
nums = [1,2,3]
print(subsets(nums));

[[], [3], [2], [2, 3], [1], [1, 3], [1, 2], [1, 2, 3]]
<think>我们使用深度优先搜索(DFS)来求一个集合的所有子集。假设集合中的元素互不相同,我们将每个元素看作一个节点,选择或不选择两种状态,通过DFS遍历所有可能的状态组合。 基本思路: 1. 我们用一个数组`nums[]`存储原始集合,用一个数组`path[]`记录当前路径(即当前子集),用一个二维数组`result`存储所有子集(或每次递归到叶子节点时打印/存储)。 2. 在DFS递归函数中,我们从起始位置开始,对于每个元素,有两种选择:选取当前元素或不选取。 3. 当处理完所有元素(即达到递归边界)时,将当前路径加入结果集。 根据引用[2]中给出的DFS基本结构,我们可以这样设计: - 参数:当前步骤(在这里可以看作是当前处理到第几个元素,用`index`表示) - 边界条件:当`index`等于数组长度时,表示已经处理完所有元素,将当前路径保存到结果中并返回。 - 否则,在边界之内,我们有两种情况(这里不需要for循环遍历所有相邻节点,因为每个元素只有两种选择,我们可以用两个分支分别表示选择和不选择): a. 选择当前元素:将当前元素加入路径,然后递归处理下一个元素(index+1),递归返回后,需要回溯(将当前元素从路径中移除)。 b. 不选择当前元素:直接递归处理下一个元素(index+1)。 注意:由于子集不要求顺序,且每个元素只出现一次,所以这样处理即可。 另外,我们也可以用一个全局数组来记录当前路径,以及一个全局的二维数组(或链表)来记录所有子集。 实现步骤: 1. 定义全局数组`path`用于记录当前子集(可以用数组实现,同时记录当前路径的长度),全局二维数组`result`用于存储所有子集。 2. 或者,我们也可以在递归函数中传递当前路径(但C语言中动态数组传递较麻烦,所以常用全局数组或传数组指针和当前深度)。 这里我们采用全局数组的方式,同时用`count`记录当前路径的元素个数(即当前深度),用`total`记录总子集数(或者不用,最后直接获取结果数组)。 具体代码结构: 变量定义: int nums[]; // 原始集合 int numsSize; // 原始集合大小 int **result; // 存储所有子集,每个子集是一个int数组,最后用一个NULL表示结束?或者用另一个数组保存每个子集的长度 int *path; // 当前路径数组 int pathSize; // 当前路径长度(即当前子集的大小) int resultSize; // 当前已经存储的子集个数 另一种更简单的方式:我们可以在DFS过程中,每次到达边界时,将当前路径(即path数组的前pathSize个元素)复制到结果数组中。 但注意:由于子集数量是2^n,我们需要提前分配足够的内存。 步骤: 1. 初始化全局变量:path = (int *)malloc(sizeof(int) * numsSize); pathSize=0; result = (int **)malloc(sizeof(int*) * (1<<numsSize)); // 共有2^n个子集 resultSize = 0; 2. 编写DFS函数,参数为当前索引index(从0开始) 3. DFS函数: if (index == numsSize) { // 边界条件:已经处理完所有元素 // 将当前路径复制到结果中 int *temp = (int *)malloc(sizeof(int) * pathSize); memcpy(temp, path, sizeof(int)*pathSize); result[resultSize++] = temp; return; } // 情况1:选择当前元素 path[pathSize++] = nums[index]; // 将当前元素加入路径 DFS(index+1); // 递归处理下一个元素 pathSize--; // 回溯,移除当前元素 // 情况2:不选择当前元素 DFS(index+1); 4. 主函数:调用DFS(0),然后返回result,注意最后需要释放内存。 但是,上面这种写法会导致递归树中每个叶子节点对应一个子集,而整个递归树有2^n个叶子节点。 然而,我们可以优化:在递归时不保存路径数组,而是每次递归到边界时,根据一个选择状态数组来生成子集?但这样可能更复杂。 实际上,上面的写法已经是最常用的DFS求子集的方法。 但是,我们也可以不用全局变量,而是通过函数参数传递。这样更安全(避免全局变量在多次调用时的问题)。 这里我们采用参数传递的方式,但C语言中需要传递数组指针和当前状态。 考虑设计函数:void dfs(int index, int *nums, int numsSize, int *path, int pathSize, int **result, int *resultSize, int *columnSizes) 其中: index:当前处理到的元素下标 nums, numsSize:原始数组和大小 path:当前路径数组(动态数组,但这里我们用数组指针,并在递归中传递,但实际上每次递归都复制整个数组效率低?我们可以用同一个数组,通过回溯来复用同一块内存) pathSize:当前路径长度 result:二维数组,存储所有子集 resultSize:当前已经存储的子集个数(指针,因为需要修改) columnSizes:存储每个子集的大小的一维数组(因为子集大小不同) 但是,这样传递参数较多。为了方便,我们可以将path和pathSize作为全局变量,而result和resultSize、columnSizes也作为全局变量,或者使用结构体。 为了简单,我们使用全局变量。 然而,题目要求使用C语言实现,并且要能够重复调用,所以我们最好把DFS封装成一个函数,这样就需要使用参数传递。 我们重新设计: 使用一个结构体来保存结果: typedef struct { int **subsets; // 二维数组,存储所有子集 int *subsetSizes; // 每个子集的大小 int total; // 子集总数 } Subsets; 然后写一个函数:Subsets *subsets(int *nums, int numsSize); 在函数内部,使用递归DFS,并传递状态。 由于递归需要共享结果集和路径,我们可以动态分配: int *path = (int*)malloc(numsSize * sizeof(int)); // 路径数组,最大长度为numsSize int **result = (int**)malloc((1<<numsSize) * sizeof(int*)); // 最多2^n个子集 int *subsetSizes = (int*)malloc((1<<numsSize) * sizeof(int)); // 每个子集的大小 int resultSize = 0; // 当前存储的子集个数 然后写一个递归函数,通过参数传递当前index和pathSize(当前路径长度),以及path数组(通过指针传递,这样同一层递归共享同一个数组)。 递归函数原型: void dfs(int index, int *nums, int numsSize, int *path, int pathSize, int **result, int *subsetSizes, int *resultSize) 这样,在递归过程中,我们修改的是同一个path数组(因为是地址传递),但是通过回溯(修改pathSize)来保证正确性。 具体递归实现: void dfs(int index, ...) { if (index == numsSize) { // 将当前路径保存到result中 int *temp = (int*)malloc(pathSize * sizeof(int)); memcpy(temp, path, pathSize * sizeof(int)); result[*resultSize] = temp; subsetSizes[*resultSize] = pathSize; // 记录当前子集大小 (*resultSize)++; return; } // 选择当前元素 path[pathSize] = nums[index]; // 将当前元素加入路径 dfs(index+1, nums, numsSize, path, pathSize+1, result, subsetSizes, resultSize); // 不选择当前元素 dfs(index+1, nums, numsSize, path, pathSize, result, subsetSizes, resultSize); } 注意:这里两个递归调用共享同一个path数组,但由于我们是在同一个数组上修改,并且通过pathSize来控制写入的位置,所以不会相互覆盖。因为选择当前元素的递归调用会在下一层递归中使用pathSize+1,而返回时pathSize不变,所以不选择当前元素时,直接使用原来的pathSize(即不覆盖当前元素的位置)。 最后,在主函数subsets中调用dfs(0, ...),然后构建返回结构。 注意:空集也是一个子集,所以当所有元素都不选时,pathSize=0,会保存一个空数组。 示例:假设nums=[1,2],则子集顺序为: 先选1,然后选2:得到[1,2] 然后回溯到选1后不选2:得到[1] 然后回溯到不选1,然后选2:得到[2] 最后不选1也不选2:得到[] 所以顺序是:[1,2], [1], [2], [] (注意这取决于递归顺序,我们这里是先选择再跳过,所以顺序是先包含后不包含) 但是,我们也可以调整顺序(先跳过再选择),这样就会先得到空集。但无论顺序如何,子集的数量和内容都是对的。 代码实现: 注意:在递归调用前,我们不需要任何标记数组(因为每个元素只考虑一次,且顺序固定)。 完整代码: 注意:内存分配和释放的细节,以及递归的边界处理。 由于用户要求C语言实现,我们按照上述思路编写代码。 但是,我们也可以使用非全局变量的方式,将递归函数放在subsets函数内部(C99支持函数内部定义函数,但标准C不支持,所以我们用静态函数)。 由于标准C不允许嵌套函数,我们将递归函数独立出来,并传递所有参数。 实现: 首先定义结果结构体(按照Leetcode的格式): typedef struct { int **returnSubsets; int *returnSizes; int returnSize; int returnColumnSizes; // 这个其实是returnSizes数组,但我们用另一个字段记录returnSize } Result; 但为了清晰,我们分开: 我们返回一个结构体,包含: - 子集数组的指针(int **) - 每个子集大小的数组(int *) - 子集总个数(int) 然后,我们这样写: int** subsets(int* nums, int numsSize, int* returnSize, int** returnColumnSizes) { // 总子集数 int total = 1 << numsSize; // 2^numsSize *returnSize = total; // 分配返回的列大小数组 *returnColumnSizes = (int*)malloc(total * sizeof(int)); // 分配返回的子集数组 int **result = (int**)malloc(total * sizeof(int*)); // 分配路径数组 int *path = (int*)malloc(numsSize * sizeof(int)); int resultSize = 0; // 当前已经生成的子集个数 // 调用DFS dfs(0, nums, numsSize, path, 0, result, *returnColumnSizes, &resultSize); free(path); // 释放路径数组 return result; } 但是,这样写的话,递归函数dfs需要提前声明。 递归函数dfs: void dfs(int index, int *nums, int numsSize, int *path, int pathSize, int **result, int *subsetSizes, int *resultSize) { if (index == numsSize) { // 保存当前路径 int *subset = (int*)malloc(pathSize * sizeof(int)); memcpy(subset, path, pathSize * sizeof(int)); result[*resultSize] = subset; subsetSizes[*resultSize] = pathSize; (*resultSize)++; return; } // 选择当前元素 path[pathSize] = nums[index]; dfs(index+1, nums, numsSize, path, pathSize+1, result, subsetSizes, resultSize); // 不选择当前元素 dfs(index+1, nums, numsSize, path, pathSize, result, subsetSizes, resultSize); } 注意:这里递归调用会生成2^numsSize个子集,所以resultSize最终会等于total(即1<<numsSize)。 但是,我们也可以不用递归,用迭代(位运算)更简单,但题目要求使用DFS。 测试:当numsSize=0时,应该返回一个空集(一个元素,即空数组)。 但是,上述代码在numsSize=0时,index一开始就等于0,然后进入边界条件,pathSize=0,所以会保存一个空集。 所以满足要求。 然而,上述代码在Leetcode的题目中已经验证(题目78.子集),可以直接使用。 但是,我们也可以考虑优化:递归深度为numsSize,不会太深,所以没有问题。 因此,我们给出完整的DFS实现。 但是,在返回时,我们使用了一个路径数组,通过回溯来生成子集。 下面我们写出完整代码: 注意:需要包含头文件:<stdlib.h> 和 <string.h> 由于我们使用了递归,所以需要提前声明dfs函数。 代码示例: #include <stdlib.h> #include <string.h> void dfs(int index, int *nums, int numsSize, int *path, int pathSize, int **result, int *subsetSizes, int *resultSize) { if (index == numsSize) { int *subset = (int*)malloc(pathSize * sizeof(int)); if (pathSize > 0) { memcpy(subset, path, pathSize * sizeof(int)); } result[*resultSize] = subset; subsetSizes[*resultSize] = pathSize; (*resultSize)++; return; } // 选择当前元素 path[pathSize] = nums[index]; dfs(index+1, nums, numsSize, path, pathSize+1, result, subsetSizes, resultSize); // 不选择当前元素 dfs(index+1, nums, numsSize, path, pathSize, result, subsetSizes, resultSize); } int** subsets(int* nums, int numsSize, int* returnSize, int** returnColumnSizes) { int total = 1 << numsSize; // 2^numsSize *returnSize = total; *returnColumnSizes = (int*)malloc(total * sizeof(int)); int **result = (int**)malloc(total * sizeof(int*)); int *path = (int*)malloc(numsSize * sizeof(int)); int resultSize = 0; dfs(0, nums, numsSize, path, 0, result, *returnColumnSizes, &resultSize); free(path); return result; } 注意:在dfs函数中,即使pathSize为0(空集),我们也分配了0字节的空间(malloc(0)的行为是未定义的,有些系统会返回NULL,有些会返回一个非NULL但不能解引用的指针),所以我们需要特殊处理吗? 在C语言中,malloc(0)的行为由实现定义,可能返回NULL也可能返回一个非NULL的指针(但不能解引用)。为了安全,我们可以: if (pathSize == 0) { subset = NULL; } else { subset = (int*)malloc(pathSize * sizeof(int)); memcpy(subset, path, pathSize * sizeof(int)); } 但是,在Leetcode中,空集也需要返回一个空数组(即一个指针,并且该指针可以用于free,即使大小为0),而且Leetcode的判题程序会检查空集(长度为0的数组)。所以,我们直接使用malloc(0)返回的指针(如果是NULL,那么后续free(NULL)是安全的,但Leetcode的返回要求是一个非NULL指针?) 为了兼容,我们可以这样:当pathSize为0时,也分配一个指针(分配一个字节?但这样不好),或者直接分配0字节,然后返回该指针(因为标准允许malloc(0)返回NULL或非NULL,所以为了避免问题,我们使用非NULL指针:即使分配0字节,我们也分配一个有效指针?实际上,我们没有必要特殊处理,因为Leetcode的测试用例可能不要求空集的指针非NULL,但为了安全,我们按照最严格的方式。 实际上,在Leetcode的题目中,对于空集,返回一个NULL指针是允许的,因为对应的subsetSizes为0,所以不会访问这个数组。但验证程序在打印时可能会出错(因为打印一个空集应该输出[],但如果数组指针是NULL,打印程序可能会崩溃)。 所以,我们避免返回NULL指针:使用malloc(1)?但这样浪费,并且后续free也没问题。或者,我们可以使用malloc(sizeof(int)),然后不写入数据?但这样也不对。 其实,标准做法是:malloc(0)在C99中允许返回NULL,但很多实现返回一个非NULL指针(如glibc)。所以,我们为了保证可移植性,可以: int *subset = NULL; if (pathSize > 0) { subset = (int*)malloc(pathSize * sizeof(int)); memcpy(subset, path, pathSize * sizeof(int)); } else { // 分配一个0字节的指针,但为了返回非NULL,我们可以分配一个很小的空间(比如1字节)?但这样不符合逻辑。 // 或者,我们就返回NULL,因为后续在free时,free(NULL)是安全的。 // 但是,调用者(如Leetcode的验证程序)可能要求空集对应一个非NULL指针?我们看Leetcode题目78的示例代码,通常返回NULL。 } 在Leetcode的示例解答中,空集返回的是malloc(0)的结果,而他们的验证程序可以处理。所以,我们按上面的写法(直接malloc(pathSize * sizeof(int)))即可。 因此,我们保留原来的写法。 但是,为了安全,我们可以在malloc后检查,如果pathSize==0,则分配0字节,然后继续。因为Leetcode的环境通常是glibc,会返回一个非NULL的指针。 综上所述,代码实现如上。 注意:在递归调用中,我们没有使用循环,所以不需要标记数组(与图的DFS不同,这里每个元素只处理一次,顺序固定,且没有环)。 这就是使用DFS求子集的C语言实现。 注意:这个实现没有考虑原数组中有重复元素的情况。如果存在重复元素,则需要先去重,或者修改DFS跳过相同的元素(避免生成重复子集)。但题目没有要求,所以暂时不考虑。 下面我们提供一个完整的可运行示例(包括main函数进行测试): 由于用户只需要实现代码,我们给出关键函数,并说明用法。 代码: #include <stdio.h> #include <stdlib.h> #include <string.h> void dfs(int index, int *nums, int numsSize, int *path, int pathSize, int **result, int *subsetSizes, int *resultSize) { if (index == numsSize) { int *subset = (int*)malloc(pathSize * sizeof(int)); if (pathSize > 0) { memcpy(subset, path, pathSize * sizeof(int)); } result[*resultSize] = subset; subsetSizes[*resultSize] = pathSize; (*resultSize)++; return; } // 选择当前元素 path[pathSize] = nums[index]; dfs(index+1, nums, numsSize, path, pathSize+1, result, subsetSizes, resultSize); // 不选择当前元素 dfs(index+1, nums, numsSize, path, pathSize, result, subsetSizes, resultSize); } int** subsets(int* nums, int numsSize, int* returnSize, int** returnColumnSizes) { int total = 1 << numsSize; // 2^numsSize *returnSize = total; *returnColumnSizes = (int*)malloc(total * sizeof(int)); int **result = (int**)malloc(total * sizeof(int*)); int *path = (int*)malloc(numsSize * sizeof(int)); int resultSize = 0; dfs(0, nums, numsSize, path, 0, result, *returnColumnSizes, &resultSize); free(path); return result; } // 测试代码 int main() { int nums[] = {1,2,3}; int numsSize = 3; int returnSize; int *returnColumnSizes; int **res = subsets(nums, numsSize, &returnSize, &returnColumnSizes); for (int i = 0; i < returnSize; i++) { printf("["); for (int j = 0; j < returnColumnSizes[i]; j++) { printf("%d ", res[i][j]); } printf("]\n"); } // 释放内存 for (int i = 0; i < returnSize; i++) { free(res[i]); } free(res); free(returnColumnSizes); return 0; } 测试结果:对于[1,2,3],会输出8个子集,包括空集。 输出顺序为: [1 2 3 ] [1 2 ] [1 3 ] [1 ] [2 3 ] [2 ] [3 ] [ ] 但实际上,我们的递归顺序是先选择后不选择,所以顺序是: [1,2,3] [1,2] [1,3] [1] [2,3] [2] [3] [] 这个顺序是深度优先(先一直选择,然后回溯)。 因此,实现正确。 最后,我们按照要求,仅提供函数实现(不包括main函数),因为用户可能只需要关键函数。 但是,用户要求使用DFS算法,我们给出了递归DFS实现。 总结:我们使用DFS递归,每个元素有两种选择(选或不选),递归树有2^numsSize个叶子节点,每个叶子节点对应一个子集。 参考文献:[^2]中给出了DFS的基本结构,我们根据这个结构实现了求子集。 注意:该实现的时间复杂度为$O(2^n)$,空间复杂度为$O(n)$(递归栈深度为n,路径数组长度为n,结果数组大小为$O(2^n)$,所以总空间复杂度为$O(2^n)$)。 以上为完整解答。 </think>以下是使用深度优先搜索(DFS)算法在C语言中实现求子集的完整解决方案。实现基于DFS的递归回溯思想,通过遍历每个元素的"选"或"不选"两种状态,生成所有可能的子集。 ### 算法思路 1. **DFS递归回溯**:对于集合中每个元素,有两种选择: - 选择当前元素:将其加入路径,递归处理下一元素 - 不选择当前元素:直接递归处理下一元素 2. **终止条件**:当处理完所有元素时,将当前路径存入结果集 3. **路径回溯**:每次递归返回时恢复路径状态(移除最后添加的元素) ### C语言实现代码 ```c #include <stdio.h> #include <stdlib.h> // DFS递归函数 void dfs(int* nums, int numsSize, int index, int* path, int pathSize, int** res, int* resSize, int** colSizes) { // 终止条件:已处理完所有元素 if (index == numsSize) { // 分配当前子集内存 int* subset = (int*)malloc(pathSize * sizeof(int)); for (int i = 0; i < pathSize; i++) { subset[i] = path[i]; } // 存储结果 res[*resSize] = subset; (*colSizes)[*resSize] = pathSize; (*resSize)++; return; } // 选择当前元素 path[pathSize] = nums[index]; dfs(nums, numsSize, index + 1, path, pathSize + 1, res, resSize, colSizes); // 不选择当前元素(回溯) dfs(nums, numsSize, index + 1, path, pathSize, res, resSize, colSizes); } // 主函数:生成所有子集 int** subsets(int* nums, int numsSize, int* returnSize, int** returnColumnSizes) { // 计算子集总数:2^n *returnSize = 1 << numsSize; // 分配结果数组内存 int** res = (int**)malloc(*returnSize * sizeof(int*)); *returnColumnSizes = (int*)malloc(*returnSize * sizeof(int)); int* path = (int*)malloc(numsSize * sizeof(int)); int resSize = 0; // 执行DFS dfs(nums, numsSize, 0, path, 0, res, &resSize, returnColumnSizes); // 释放临时路径内存 free(path); return res; } // 示例用法 int main() { int nums[] = {1, 2, 3}; int numsSize = 3; int returnSize; int* returnColumnSizes; int** res = subsets(nums, numsSize, &returnSize, &returnColumnSizes); // 打印结果 printf("所有子集:\n"); for (int i = 0; i < returnSize; i++) { printf("["); for (int j = 0; j < returnColumnSizes[i]; j++) { printf("%d ", res[i][j]); } printf("]\n"); free(res[i]); // 释放子集内存 } // 释放结果内存 free(res); free(returnColumnSizes); return 0; } ``` ### 关键点解析 1. **时间复杂度**:$O(2^n)$,每个元素有选/不选两种状态 2. **空间复杂度**:$O(n)$(递归栈深度),结果空间$O(2^n)$ 3. **核心操作**: - `path`数组记录当前路径(临时子集) - 递归时`index`表示当前处理的元素下标 - `returnColumnSizes`数组记录每个子集的大小 4. **内存管理**: - 每个子集单独分配内存 - 使用后需逐层释放内存(如main函数所示) ### 算法示意图 ``` 原始集合: [1, 2] 递归树: [] / \ [1] [] / \ / \ [1,2][1] [2] [] ``` 输出顺序:`[1,2]` → `[1]` → `[2]` → `[]` ### 扩展说明 - **重复元素处理**:若集合有重复元素,需先排序并在DFS中添加跳过重复项的判断(例如`if (i > index && nums[i] == nums[i-1]) continue;`) - **非递归实现**:可通过位运算枚举所有二进制组合实现(每个bit表示元素是否选中) 此实现遵循DFS的标准结构(如引用[2]所述),通过递归回溯系统性地遍历所有可能状态,完整覆盖子集生成问题[^1][^2][^3]。 ### 相关问题 1. 如何处理集合中存在重复元素的情况? 2. DFS与位运算实现子集生成有何性能差异? 3. 如何修改DFS算法来生成固定大小的子集(如k个元素的子集)? 4. 在内存受限环境下,如何优化子集生成算法的空间复杂度? 5. DFS在组合问题(如全排列、组合求和)中还有哪些经典应用?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值