硬币面值还原
硬币面值还原
第455场周赛2025-06-22
题目
给你一个 从 1 开始计数 的整数数组 numWays,其中 numWays[i] 表示使用某些 固定 面值的硬币(每种面值可以使用无限次)凑出总金额 i 的方法数。每种面值都是一个 正整数 ,并且其值 最多 为 numWays.length。
然而,具体的硬币面值已经 丢失 。你的任务是还原出可能生成这个 numWays 数组的面值集合。
返回一个按从小到大顺序排列的数组,其中包含所有可能的 唯一 整数面值。
如果不存在这样的集合,返回一个 空 数组。
示例 1:
输入: numWays = [0,1,0,2,0,3,0,4,0,5]
输出: [2,4,6]
解释:
金额 方法数 解释
1 0 无法用硬币凑出总金额 1。
2 1 唯一的方法是 [2]。
3 0 无法用硬币凑出总金额 3。
4 2 可以用 [2, 2] 或 [4]。
5 0 无法用硬币凑出总金额 5。
6 3 可以用 [2, 2, 2]、[2, 4] 或 [6]。
7 0 无法用硬币凑出总金额 7。
8 4 可以用 [2, 2, 2, 2]、[2, 2, 4]、[2, 6] 或 [4, 4]。
9 0 无法用硬币凑出总金额 9。
10 5 可以用 [2, 2, 2, 2, 2]、[2, 2, 2, 4]、[2, 4, 4]、[2, 2, 6] 或 [4, 6]。
示例 2:
输入: numWays = [1,2,2,3,4]
输出: [1,2,5]
解释:
金额 方法数 解释
1 1 唯一的方法是 [1]。
2 2 可以用 [1, 1] 或 [2]。
3 2 可以用 [1, 1, 1] 或 [1, 2]。
4 3 可以用 [1, 1, 1, 1]、[1, 1, 2] 或 [2, 2]。
5 4 可以用 [1, 1, 1, 1, 1]、[1, 1, 1, 2]、[1, 2, 2] 或 [5]。
示例 3:
输入: numWays = [1,2,3,4,15]
输出: []
解释:
没有任何面值集合可以生成该数组。
提示:
1 <= numWays.length <= 100
0 <= numWays[i] <= 2 * 108©leetcode
题解
这道题没做出来,用了复杂度比较高的方法,列举了每个可能的具体数字组合。这是考虑到如果直接利用已有的count,会有重复的组合,比如对于例子1中的数值6,当一端值为2的时候,另一段4对应的是2,2;4,当一端值为4的时候,另一段对应的2,这时候的2,4和4,2时重复的。
public class Solution {
public IList<int> FindCoins(int[] numWays) {
int n=numWays.Length;
IList<int> res=new List<int>();
IList<IList<int>>[] ways=new IList<IList<int>>[n];
for(int i=0;i<n;i++){
int v=i+1;
if(numWays[i]==0){
// todo : ensure no way
IList<IList<int>> w=findWays(v,res,ways);
int c=w.Count;
if(c>numWays[i]){
Console.WriteLine("v"+v+" "+c+">"+numWays[i]);
return [];
}
}else{
if(res.Count==0){ // min val in res
if(numWays[i]!=1){
return [];
}
res.Add(v);
IList<int> one=new List<int>();
one.Add(v);
IList<IList<int>> tmp=new List<IList<int>>();
tmp.Add(one);
ways[i]=tmp;
continue;
}
// count: ways to combine to v using existing res
IList<IList<int>> w=findWays(v,res,ways);
ways[i]=w;
int c=w.Count;
// Console.WriteLine("v"+v+" "+c+" - "+numWays[i]);
if(c>numWays[i]){
Console.WriteLine("v"+v+" "+c+">"+numWays[i]);
return [];
}else if(c==numWays[i]){
}else{
if(c==numWays[i]-1){
res.Add(v);
IList<int> one=new List<int>();
one.Add(v);
ways[i].Add(one);
}else{
return [];
}
}
}
}
return res;
}
// cur: asc, < target
private IList<IList<int>> findWays(int target,IList<int> curr, IList<IList<int>>[] ways){
// Console.WriteLine(target+","+" curr:"+string.Join(',',curr));
IList<IList<int>> res=new List<IList<int>>();
foreach(int v in curr){// use v as largest to combine
int another=target-v;
int idx=another-1;
// Console.WriteLine("another:"+another+" idx"+idx);
IList<IList<int>> cb=ways[idx];
if(cb==null){
continue;
}
//largest value in each cb should <=v
foreach(IList<int> lst in cb){
if(lst[lst.Count-1]>v){
continue;
}
IList<int> aRes=new List<int>();
foreach(int aV in lst){
aRes.Add(aV);
}
aRes.Add(v);
// Console.WriteLine("t "+target+" c "+string.Join(',',aRes));
res.Add(aRes);
}
}
return res;
}
}©leetcode
大佬解法
自己求解当前总值i对应的所有组合数为dp[i-1],和上一道题(质数)类似,这道题也是用的反过来的思想(用当前值取设置后面的值,而不是用之前的值来求当前的值)
如果大于题目给的a[i-1],则返回不可能
如果等于,则不用把当前面值加上去
否则,需要把当前面值加上去,并更新i到最后n对应的dp值,一端是当前面值i,另一端上总面值j-i。---- 这种解法不需要考虑重复组合的情况,因为i作为组合可能中的最大值,比如对于例1,
i=2时,dp[2]=0<c, so dp[2]+=dp[0](1), dp[4]+=dp[4-2](1),dp[6]+=dp[4]=0
i=4时,dp[4]<c, so dp[4]+=dp[0](1) ==>2,dp[6]+=dp[6-4](1)
import java.util.ArrayList;
import java.util.List;
class Solution {
public List<Integer> findCoins(int[] a) {
int n = a.length;
List<Integer> list = new ArrayList<>();//所有面值
long[] dp = new long[n + 1];
dp[0] = 1;
for (int i = 1; i <= n; i++) {
int c = a[i - 1];//题目给出的对总值i的组合数
if (dp[i] > c) return new ArrayList<>();
else if (dp[i] < c) {
list.add(i);
for (int j = i; j <= n; j++) {
dp[j] += dp[j - i];
}
if (dp[i] != c) return new ArrayList<>();
}
}
return list;
}
}