题解:火柴棒数字
源题目地址:https://www.lanqiao.cn/problems/2239/learning/
问题描述
小蓝有300根火柴棒,要用它们拼出数字0-9,每个数字最多拼10次。每个数字需要的火柴棒数量如下:
数字 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|---|
火柴棒数 | 6 | 2 | 5 | 5 | 4 | 5 | 6 | 3 | 7 | 6 |
要求拼出的整数尽可能大,可以不使用完所有火柴棒。
解题思路
- 动态规划方法:使用dp数组记录每种火柴棒消耗下能组成的最大数字字符串
- 贪心策略:优先使用数值大的数字(9、8、7等)
- 限制处理:每个数字最多使用10次
- 字符串处理:确保数字字符串按降序排列以获得最大值
代码实现
import java.util.Arrays;
public class Main{
public static void main(String[] args) {
// 每个数字的火柴棒消耗,索引0~9分别对应数字0~9
int[] cost = { 6, 2, 5, 5, 4, 5, 6, 3, 7, 6 };
// 每个数字最多可用次数
int[] maxCount = new int[10];
Arrays.fill(maxCount, 10);
// 记录每个数字使用的次数
int[] used = new int[10];
int totalMatches = 300;
// 优先使用消耗小的数字,但要在总消耗不超过300的情况下,尽量多使用高数值的数字
// 按照消耗从小到大处理,但需要贪心选择高数值的可行组合
// 动态规划方式:dp[i]表示消耗i根火柴棒能得到的最大数字字符串
String[] dp = new String[totalMatches + 1];
Arrays.fill(dp, "");
for (int i = 0; i <= totalMatches; i++) {
for (int num = 9; num >= 0; num--) { // 优先考虑大数字
if (num == 0 && i == 0)
continue; // 避免前导零
int c = cost[num];
if (i >= c) {
for (int k = 1; k <= maxCount[num]; k++) {
if (i >= c * k) {
String prev = dp[i - c * k];
// 检查当前数字是否已经用了超过允许次数
int prevUsed = countDigits(prev, num);
if (prevUsed + k > maxCount[num])
continue;
String candidate = buildString(prev, num, k);
if (compare(candidate, dp[i]) > 0) {
dp[i] = candidate;
}
}
}
}
}
}
// 找出所有可能中最大的字符串
String maxStr = "";
for (int i = 0; i <= totalMatches; i++) {
if (compare(dp[i], maxStr) > 0) {
maxStr = dp[i];
}
}
System.out.println(maxStr);
}
// 比较两个数字字符串的大小
private static int compare(String a, String b) {
if (a.length() != b.length()) {
return a.length() - b.length();
}
return a.compareTo(b);
}
// 构建新的字符串,将数字num重复k次,并保持降序排列
private static String buildString(String prev, int num, int k) {
StringBuilder sb = new StringBuilder(prev);
for (int i = 0; i < k; i++) {
sb.append(num);
}
// 需要将字符排序为降序
char[] chars = sb.toString().toCharArray();
Arrays.sort(chars);
reverse(chars);
return new String(chars);
}
// 反转字符数组
private static void reverse(char[] chars) {
int i = 0, j = chars.length - 1;
while (i < j) {
char t = chars[i];
chars[i] = chars[j];
chars[j] = t;
i++;
j--;
}
}
// 统计字符串中某个数字的出现次数
private static int countDigits(String s, int num) {
int count = 0;
for (char c : s.toCharArray()) {
if (c - '0' == num) {
count++;
}
}
return count;
}
}
代码采用动态规划方法,主要步骤如下:
- 初始化:
- 定义每个数字的火柴棒消耗数组
cost
- 设置每个数字最多使用次数
maxCount
为10 - 初始化dp数组,
dp[i]
表示消耗i根火柴棒能得到的最大数字字符串
- 定义每个数字的火柴棒消耗数组
- 填充dp数组:
- 从数字9到0依次处理(优先考虑大数字)
- 对于每个数字,考虑使用1到10次的情况
- 检查是否超过最大使用次数限制
- 构建候选数字字符串并保持降序排列
- 结果提取:
- 遍历dp数组找出最大的数字字符串
关键函数说明
compare(String a, String b)
:比较两个数字字符串的大小buildString(String prev, int num, int k)
:构建新的字符串并保持降序排列reverse(char[] chars)
:反转字符数组countDigits(String s, int num)
:统计字符串中某个数字的出现次数
复杂度分析
- 时间复杂度:O(nmk),其中n为数字种类(10),m为总火柴棒数(300),k为最大使用次数(10)
- 空间复杂度:O(m),用于存储dp数组