输出给定数组中所有和为S的可能组合

本文介绍了一种寻找整数数组中所有和为目标数S的组合的方法,并提供了两种实现方式:一种利用Stack,另一种则不使用Stack。通过递归算法,文章详细展示了如何找到所有可能的组合。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

如果给定一个整数数组和一个目标数S,如何输出该数组中所有和为S的可能组合?

例如,

给定数组 如下:

int[] values = { 1, 3, 4, 5, 6, 15 };

那么和为15的可能组合有如下几种:

15 = 1+3+5+6
15 = 4+5+6
15 = 15

本文采用如下两种方法来实现:

  • 借助Stack实现
  • 不借助Stack实现

借助Stack实现

代码实现

import java.util.Arrays;
import java.util.Stack;

/**
 * 
 * @author wangmengjun
 *
 */
public class PrintAllByStack {

	/** 设定一个目标值 */
	public static final int TARGET_SUM = 20;

	/** 使用Stack存放已经使用的数值 */
	private Stack<Integer> stack = new Stack<Integer>();

	/** 存放当前Stack中元素的和 */
	private int sumInStack = 0;

	public void process(final int[] data, int target) {
		if (data == null || data.length == 0) {
			throw new IllegalArgumentException("data不能为空");
		}

		Arrays.sort(data);
		processRecursion(data, 0, data.length, target);
	}

	private void processRecursion(final int[] data, int fromIndex,
			int endIndex, int target) {

		if (sumInStack >= target) {
			if (sumInStack == target) {
				print(stack);
			}
			return;
		}

		for (int currentIndex = fromIndex; currentIndex < endIndex; currentIndex++) {
			if (sumInStack + data[currentIndex] <= target) {
				stack.push(data[currentIndex]);
				sumInStack += data[currentIndex];
				/**
				 * 从currentIndex +1开始继续递归
				 */
				processRecursion(data, currentIndex + 1, endIndex, target);
				sumInStack -= (Integer) stack.pop();
			}
		}
	}

	/**
	 * 打印符合条件的结果,如 15 = 4+6+5
	 */
	private void print(Stack<Integer> stack) {
		StringBuilder sb = new StringBuilder();
		sb.append(TARGET_SUM).append(" = ");
		for (Integer i : stack) {
			sb.append(i).append("+");
		}
		System.out.println(sb.deleteCharAt(sb.length() - 1).toString());
	}
}

测试及结果

/**
 * 
 * @author wangmengjun
 *
 */
public class Main {

	public static void main(String[] args) {
		PrintAllByStack example = new PrintAllByStack();
		int[] values = { 1, 4, 3, 6, 7, 9, 5, 8, 15, 16, 18, 17 };
		example.process(values, PrintAllByStack.TARGET_SUM);
	}

}

 

20 = 1+3+4+5+7
20 = 1+3+7+9
20 = 1+3+16
20 = 1+4+6+9
20 = 1+4+7+8
20 = 1+4+15
20 = 1+5+6+8
20 = 3+4+5+8
20 = 3+4+6+7
20 = 3+8+9
20 = 3+17
20 = 4+7+9
20 = 4+16
20 = 5+6+9
20 = 5+7+8
20 = 5+15

 

在上述代码中,Stack作为辅助,在递归方法外定义, 这样有点奇怪

接下来的方法,我们将Stack替换掉。

不借助Stack实现

代码

测试及结果

/**
 * @author wangmengjun
 *
 */
import java.util.Arrays;

public class PrintAllSubsets {

	/** 设定一个目标值 */
	public static final int TARGET_SUM = 20;

	public void process(final int[] data, final int target) {
		if (data == null || data.length == 0) {
			throw new IllegalArgumentException("data不能为空");
		}

		int len = data.length;
		/**
		 * 处理前先排序一下,这样有一个好处,如果一个数添加进去已经大于target了,
		 * 那么该数后面的数添加进去都将大于target
		 */
		Arrays.sort(data);
		this.processRecursion(data, 0, new int[len], 0, target);
	}

	private void processRecursion(final int[] data, int fromIndex,
			final int[] stack, final int stacklen, final int target) {
		if (target == 0) {
			/**
			 * 如果符合条件,则输出符合条件的情况
			 */
			printResult(Arrays.copyOf(stack, stacklen));
			return;
		}
		
		while (fromIndex < data.length && data[fromIndex] > target) {
			/**
			 * 借助数组已经排序的好处,后面更大的数值,只要增加索引即可。
			 */
			fromIndex++;
		}

		while (fromIndex < data.length && data[fromIndex] <= target) {
			/**
			 * 当数据用完,或者当数值大于剩余想获取的目标值,结束循环
			 */
			stack[stacklen] = data[fromIndex];
			processRecursion(data, fromIndex + 1, stack, stacklen + 1, target
					- data[fromIndex]);
			fromIndex++;
		}
	}

	/**
	 * 打印符合条件的结果,如 15 = 4+6+5
	 */
	private void printResult(int[] copyOf) {
		StringBuilder sb = new StringBuilder();
		sb.append(TARGET_SUM).append(" = ");
		for (Integer i : copyOf) {
			sb.append(i).append("+");
		}
		System.out.println(sb.deleteCharAt(sb.length() - 1).toString());
	}
}

测试及结果


/**
 * 
 * @author wangmengjun
 *
 */
public class Main {

	public static void main(String[] args) {
		PrintAllSubsets example = new PrintAllSubsets();
		int[] values = { 1, 4, 3, 6, 7, 9, 5, 8, 15, 16, 18, 17 };
		example.process(values, PrintAllSubsets.TARGET_SUM);
	}

}
20 = 1+3+4+5+7
20 = 1+3+7+9
20 = 1+3+16
20 = 1+4+6+9
20 = 1+4+7+8
20 = 1+4+15
20 = 1+5+6+8
20 = 3+4+5+8
20 = 3+4+6+7
20 = 3+8+9
20 = 3+17
20 = 4+7+9
20 = 4+16
20 = 5+6+9
20 = 5+7+8
20 = 5+15

 

转载于:https://my.oschina.net/wangmengjun/blog/775973

<think>好的,我现在需要帮用户解决用C++实现数字组合问题的递归解决方案。首先,我得理解用户的具体需求是什么。用户提到了数字组合问题,但没有具体说明是什么样的组合,比如是排列还是组合,或者有其他限制条件。根据提供的引用内容,用户可能是指类似全排列或者特定约束下的组合生成,比如不重复的组合,或者总固定的组合。 接下来,我需要查看用户提供的引用内容,看看是否有相关的例子可以借鉴。引用[1]中的代码是一个组合型枚举的DFS实现,输入nm,输出从n个数中选m个的所有组合。引用[2]中的代码处理的是递推问题,涉及三种格子的排列组合。引用[3][4]则提到了递归在排列组合中的应用,特别是引用[4]中的代码似乎处理的是将数字n分解为不同组合的方式数,有一定的约束条件。 用户的问题可能类似于这些引用中的情况,但需要具体明确。比如,用户可能想要生成所有可能的数字组合,这些组合满足某些条件,如为定值,或者不重复选择等。根据常见的数字组合问题,比如“给定一个数组一个目标数,找出所有唯一组合使数字等于目标”,或者“从1到n中选k个数的所有组合”。但用户没有具体说明,可能需要进一步假设。 考虑到引用[1]中的组合枚举代码,用户可能已经了解组合生成的递归方法,但需要其他类型的组合问题解决方案,比如允许重复或不同约束的情况。或者用户可能需要将数字组合成特定形式,比如分解成不同数的,如引用[4]中的例子。 为了确保正确,我需要先明确数字组合问题的定义。比如,假设问题是:给定一个整数n,找出所有可能组合,使得这些组合中的数之等于n,并且每个数都不重复,或者允许重复。或者,可能是从一组数字中选择若干个数,使得满足某种条件,如为定值。 假设用户的问题类似于组合,即给定一个目标数,找出所有可能组合使其为目标数,且数字来自给定集合,允许或不允许重复。例如,LeetCode上的组合问题。这种情况下,递归解决方案通常使用回溯法,通过递归尝试每个可能的数字,并在满足条件时记录结果。 但根据用户提供的引用,特别是引用[4]中的代码,可能用户的问题更接近将n分解为不同的数之,如整数划分问题。例如,将n分解为若干个正整数之,且后面的数不大于前面的数,以避免重复组合。这种情况下,递归函数需要考虑当前分解的数不能超过之前的值,从而避免重复排列。 例如,引用[4]中的代码,函数f(n, bound)计算将n分解为多个数之的方式数,其中每个数不超过bound,且分解后的数满足n-k >=1且n-k <=k。这可能是一个特定约束的整数划分问题。但用户可能需要更通用的解决方案,生成所有可能组合,而不仅仅是计算方式数。 因此,我需要结合用户提供的引用,特别是引用[1]的组合枚举代码,设计一个递归的C++解决方案,生成所有满足特定条件的数字组合。例如,生成所有从1到n中选k个数的组合,或者生成所有可能为n的组合,允许或不允许重复使用数字。 假设用户的问题是从1到n中选取m个数的所有组合,如引用[1]中的代码所示,但用户可能希望更一般的数字组合问题,比如允许重复或不同的约束。或者,用户可能需要解决类似整数划分的问题,将n分解为不同的数之,并生成所有可能组合。 我需要明确用户的具体需求,但根据提供的引用,可能用户需要的是生成所有可能组合,例如,给定一个数组,生成所有子集,或者生成所有排列,或者生成满足特定条件的组合。例如,引用[3]中提到的递归求解数字的排列组合。 因此,可能的解决方案需要根据具体问题来设计。假设用户的问题是需要生成所有可能的k个数的组合,类似组合型枚举,但使用递归方法。或者,生成所有排列,如全排列。 根据引用[1]中的代码,组合枚举的递归实现已经存在,用户可能需要类似的代码,或者更复杂的组合问题。比如,允许数字重复,或者不同的选择方式。 或者,用户的问题可能类似于整数划分,例如将数字n分解为若干正整数之,并列出所有可能的分解方式,这需要不同的递归结构,确保分解不重复,例如保持分解后的数按非递增顺序排列,以避免重复。 例如,对于分解n=4,可能的分解为4, 3+1, 2+2, 2+1+1, 1+1+1+1等,但根据不同的约束条件,可能有不同的结果。 在这种情况下,递归函数需要跟踪当前的分解路径,并且确保每一步选择的数不超过前一个数,以避免重复组合。 假设用户的问题属于这种情况,那么代码的结构可能如下: void dfs(int target, int start, vector<int>& path) { if (target == 0) { 输出或保存path; return; } for (int i = start; i <= target; ++i) { path.push_back(i); dfs(target - i, i, path); // 允许重复,例如分解为2+2 path.pop_back(); } } 这样,当调用dfs(n, 1, path)时,会生成所有可能的分解方式,其中每个数至少为start,且后面的数不小于前面的数,以避免重复。例如,分解4为1+1+1+1, 1+1+2, 1+3, 2+2, 4等。 但根据不同的需求,可能需要调整递归的条件。例如,如果不允许重复使用相同的数字,则需要调整循环中的起始值。 或者,如果问题是从给定数组中选择数字,使其为特定值,则需要不同的处理方式,比如回溯法,每次选择是否包含当前数字,并递归处理剩下的数字。 但根据用户提供的引用,尤其是引用[1]的组合枚举,可能用户的问题更接近生成所有k个数的组合,或者生成所有可能的分解方式。 现在,我需要参考用户提供的引用中的代码结构,比如引用[1]的代码使用DFS递归生成组合,参数包括当前的位置x起始值s,避免重复组合。 因此,可能的解决方案是类似的递归结构,根据具体问题进行调整。 例如,用户可能的问题是从n个数字中选择m个数的组合,那么引用[1]的代码已经解决了这个问题,但用户可能需要不同的约束,比如允许重复,或者数字之等于某个值。 或者,用户可能需要解决组合问题,即从给定数字中选取若干个数,使得它们的等于目标,允许重复或不重复。 此时,需要根据具体问题设计递归函数。例如,对于允许重复的组合问题,递归函数可以尝试每个数字,然后递归处理剩余的目标值,同时允许再次选择该数字。 例如: void backtrack(vector<int>& candidates, int target, int start, vector<int>& path, vector<vector<int>>& result) { if (target == 0) { result.push_back(path); return; } for (int i = start; i < candidates.size(); ++i) { if (candidates[i] > target) continue; path.push_back(candidates[i]); backtrack(candidates, target - candidates[i], i, path, result); // 允许重复,传入i而不是i+1 path.pop_back(); } } 但用户的问题是否属于这种情况还不清楚。需要进一步分析。 另外,用户提到的“数字组合问题”可能是指生成所有可能的排列或组合,例如排列型枚举,如引用[2]中的全排列。 不过,引用[1]中的代码是组合型枚举,而引用[2]中的代码是递推求解方案数,而非生成具体的组合。 因此,用户可能需要生成所有可能的数字组合,满足某种条件,比如为定值,或者排列组合。 结合用户提供的引用,可能用户需要的是类似组合型或排列型枚举的递归解决方案,但具体问题可能不同。 例如,用户可能的问题类似于:给定数字nk,生成所有可能的k个数的组合,使得它们的等于n,每个数可以重复使用或不可重复使用。 或者,用户的问题可能更简单,如生成所有可能的子集,或者生成排列。 但根据用户给出的引用,特别是引用[1]的组合枚举代码引用[4]的递归分解代码,可能用户需要的是类似组合枚举或整数分解的问题的递归解决方案。 为了确保正确,我需要假设用户的问题类似于组合枚举,但可能允许不同的约束,例如允许重复选择,或者总条件。 假设用户的问题是从1到n中选择若干个数,使得它们的等于s,并且每个数只能选一次,那么递归的解决方案需要遍历所有可能组合,检查它们的是否等于s。 或者,用户的问题可能不需要总条件,只是生成所有可能组合,如从n个数中选m个的组合,这已经由引用[1]的代码解决,但用户可能希望得到不同的实现方式。 因此,我需要进一步明确问题。但根据用户的问题描述:“数字组合问题”,可能指的是生成所有可能组合,而不是排列。例如,组合不考虑顺序,而排列考虑顺序。 例如,组合问题:从n个元素中选k个,不考虑顺序,如引用[1]中的代码;排列问题:考虑顺序,如全排列。 根据引用[3]中的描述,用户可能想用递归来求解排列组合的问题,所以可能用户需要的是排列或组合的生成。 现在,我需要根据用户提供的引用中的代码,特别是引用[1]的组合枚举,来构造一个递归解决方案。 例如,用户可能希望解决类似的问题,但可能条件不同,比如允许重复,或者数字来自特定集合。 或者,用户可能需要解决类似整数划分的问题,如将n分解为若干数之的所有可能方式。 例如,对于n=4,可能组合包括:4, 3+1, 2+2, 2+1+1, 1+1+1+1等,这种情况下,递归函数需要确保组合中的数按非递增顺序排列,以避免重复。 这种情况下,递归函数的参数可能包括当前剩余值、当前组合中的最大值(避免选择更大的数导致重复),以及当前的路径。 例如,函数定义可能如下: void dfs(int remaining, int max_num, vector<int>& path, vector<vector<int>>& result) { if (remaining == 0) { result.push_back(path); return; } for (int i = max_num; i >= 1; --i) { if (i > remaining) continue; path.push_back(i); dfs(remaining - i, i, path, result); path.pop_back(); } } 这样,调用dfs(n, n, path, result)将生成所有可能的分解方式,按非递增顺序排列,避免重复。 例如,n=4时,生成4, 3+1, 2+2, 2+1+1, 1+1+1+1。 但用户的问题是否属于这种情况还不确定。可能需要更多的信息,但根据用户提供的引用,特别是引用[4]中的代码,处理的是分解问题,可能用户需要类似的解决方案。 因此,我需要结合这些信息,给出一个递归的C++解决方案,可能针对整数划分或组合生成问题。 假设用户的问题是将数字n分解为若干正整数之,并生成所有可能组合,每个组合中的数按非递增顺序排列,以避免重复。例如,n=4的输出包括4, 3+1, 2+2, 2+1+1, 1+1+1+1。 这种情况下,递归函数的实现可能如下: #include <iostream> #include <vector> using namespace std; void findCombinations(int remaining, int maxAllowed, vector<int>& current, vector<vector<int>>& result) { if (remaining == 0) { result.push_back(current); return; } for (int i = maxAllowed; i >= 1; i--) { if (i > remaining) continue; current.push_back(i); findCombinations(remaining - i, i, current, result); current.pop_back(); } } int main() { int n; cout << "请输入目标数n: "; cin >> n; vector<vector<int>> result; vector<int> current; findCombinations(n, n, current, result); cout << "所有可能组合为:" << endl; for (auto& combination : result) { for (size_t j = 0; j < combination.size(); j++) { cout << combination[j]; if (j != combination.size() - 1) cout << "+"; } cout << endl; } return 0; } 这段代码将生成所有可能的分解方式,每个组合中的数按非递增顺序排列,避免重复。 但用户可能需要不同的约束条件,例如不允许重复使用相同的数字,或者允许排列顺序不同视为不同的组合。需要进一步确认。 假设用户的问题是从一组数字中选择k个数的组合,比如引用[1]中的问题,输入nm,输出所有m个数的组合。这种情况下,用户提供的引用[1]中的代码已经解决了这个问题,但用户可能需要不同的实现方式或其他变种。 或者,用户的问题可能类似于生成所有子集,比如给定一个数组,生成所有可能的子集,这种情况下,递归方法需要遍历每个元素,选择是否包含它。 例如: void generateSubsets(vector<int>& nums, int index, vector<int>& current, vector<vector<int>>& result) { if (index == nums.size()) { result.push_back(current); return; } // 不包含当前元素 generateSubsets(nums, index + 1, current, result); // 包含当前元素 current.push_back(nums[index]); generateSubsets(nums, index + 1, current, result); current.pop_back(); } 但根据用户提供的引用,可能更接近组合枚举或整数分解问题。 因此,我需要根据常见的数字组合问题,结合用户提供的引用,给出一个递归解决方案。例如,组合枚举(从n个数中选m个)的递归实现,或者整数划分的组合生成。 考虑到用户提供的引用[1]中的代码是组合枚举,可能用户需要的是类似的解决方案,但可能需要不同的解释或不同的条件。 因此,总结用户的可能需求,给出一个递归的C++解决方案,例如组合枚举,并解释其工作原理。例如,输入nm,输出所有m个数的组合,每个组合中的元素按升序排列,不重复。 用户提供的引用[1]中的代码已经实现了这一点,但用户可能需要解释或不同的实现。或者,用户可能希望允许重复选择元素,例如组合中的元素可以重复出现,但顺序不计。 例如,从n个元素中选m个,允许重复,但组合不考虑顺序。比如,n=3, m=2,可能组合是(1,1), (1,2), (1,3), (2,2), (2,3), (3,3)。 这种情况下,递归函数需要调整起始条件,允许当前元素被重复选择,但确保组合中的元素非递减排列,避免重复。 例如,递归函数的参数包括当前位置、起始值,每次递归从当前元素开始,而不是下一个元素。 例如: void dfs(int x, int start) { if (x > m) { 输出组合; return; } for (int i = start; i <= n; i++) { st[x] = i; dfs(x + 1, i); // 允许重复,起始值为i } } 这样,当调用dfs(1, 1)时,每个位置可以选择当前起始值或更大的值,允许重复。 例如,n=3, m=2,将生成(1,1), (1,2), (1,3), (2,2), (2,3), (3,3)。 因此,用户可能需要这样的代码,根据不同的组合条件调整递归参数。 结合用户的问题描述提供的引用,我认为用户需要的是递归解决组合型问题的C++代码,可能类似于组合枚举或整数分解。因此,我需要提供相应的代码解释,并确保符合用户提供的引用中的结构。 例如,如果用户的问题是从n个数中选m个的组合,允许重复,那么递归实现如下: #include <iostream> #include <vector> using namespace std; int n, m; vector<int> combination; void dfs(int start, int depth) { if (depth == m) { for (int num : combination) { cout << num << " "; } cout << endl; return; } for (int i = start; i <= n; ++i) { combination.push_back(i); dfs(i, depth + 1); // 允许重复,起始值保持为i combination.pop_back(); } } int main() { cin >> n >> m; dfs(1, 0); return 0; } 这样,输入n=3,m=2,输出允许重复的组合。 但用户的具体需求可能不同,可能需要不同的约束条件。例如,不允许重复,此时起始值为i+1,如引用[1]中的代码。 如果用户的问题是不允许重复的组合,即从n个不同的数中选择m个,不允许重复,那么引用[1]中的代码已经解决了这个问题。用户可能需要确认这一点。 因此,用户可能需要的是允许重复的组合生成,或者整数分解的组合生成。 根据用户提供的引用[4],其中处理的是分解问题,可能用户需要的是将数字n分解为若干数之的递归解决方案,例如: #include <iostream> #include <vector> using namespace std; void findCombinations(int target, int current, vector<int>& path, int start) { if (current == target) { for (int num : path) { cout << num << " "; } cout << endl; return; } for (int i = start; i <= target - current; ++i) { path.push_back(i); findCombinations(target, current + i, path, i); // 允许重复,起始值为i path.pop_back(); } } int main() { int n; cin >> n; vector<int> path; findCombinations(n, 0, path, 1); return 0; } 这段代码将生成所有可能组合,其中数的等于n,允许重复,并且组合中的数按非递减顺序排列,以避免重复。例如,n=3时,输出1 1 1;1 2;2 1(但由于起始值控制,实际输出可能不同,需要调整递归参数确保顺序)。 不过,可能更准确的实现需要确保组合中的数按非递减或非递增顺序排列,以避免重复的组合。例如,每次递归调用时,只允许选择大于等于当前数的数,这样组合将按非递减顺序排列,避免重复。 但用户的问题可能不需要总等于某个值,而是生成所有可能的子集,或者排列。需要更明确的问题定义。 总结来说,用户需要的是用C++递归解决数字组合问题的方案,可能涉及组合枚举、排列生成、整数分解等。根据提供的引用,用户提供的引用[1]是组合枚举,引用[4]是整数分解的方案数计算,但用户需要生成具体的组合。 因此,结合这些信息,我将提供一个递归解决方案,用于生成所有可能的数字组合,满足特定条件,例如为n,或者选择m个数允许重复等,并解释其工作原理,同时参考用户提供的引用中的代码结构。</think>以下是使用C++实现数字组合问题的递归解决方案,以**生成所有允许重复元素的组合**为例: ### 问题定义 给定数字范围$1$到$n$,从中选择$m$个数(允许重复选择同一个数字),生成所有可能组合。例如,当$n=3$且$m=2$时,组合为: $$(1,1),\ (1,2),\ (1,3),\ (2,2),\ (2,3),\ (3,3)$$ ### 递归实现代码 ```cpp #include <iostream> #include <vector> using namespace std; int n, m; vector<int> combination; void dfs(int start, int depth) { if (depth == m) { // 达到组合长度要求 for (int num : combination) { cout << num << " "; } cout << endl; return; } for (int i = start; i <= n; ++i) { combination.push_back(i); // 选择当前数字 dfs(i, depth + 1); // 允许重复,下一层从i开始 combination.pop_back(); // 回溯 } } int main() { cin >> n >> m; dfs(1, 0); return 0; } ``` ### 关键逻辑说明 1. **递归终止条件**:当组合长度达到$m$时输出结果。 2. **循环控制**:`for (int i = start; i <= n; ++i)`确保组合按非递减顺序排列,避免重复(如防止同时出现$(1,2)$$(2,1)$)。 3. **允许重复**:通过传递`i`而非`i+1`到下一层递归,允许重复选择当前数字[^1]。 4. **回溯操作**:通过`push_back``pop_back`维护组合状态。 --- ### 变体问题:不允许重复的组合 若需生成**不重复**的组合(如从$n$个数字中选$m$个),只需修改递归调用为`dfs(i+1, depth+1)`,确保下一层从下一个数字开始选择: ```cpp void dfs(int start, int depth) { // ... 其他代码相同 dfs(i+1, depth + 1); // 禁止重复 } ``` --- ### 性能优化建议 1. **剪枝**:若组合过程中已无法满足最终条件(如剩余数字不足以填满组合),提前终止递归。 2. **记忆化**:若问题涉及大量重复计算(如组合),可通过缓存中间结果优化效率[^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值