2025-12-06:硬幣面值還原。用go語言,給出一個從 1 開始索引的整數數組 numWays,其中 numWays[i] 表示用若干種固定面額且每種可重複使用的硬幣,湊出金額 i 的方案數。所有面額都是正整數,且最大不會超過 numWays 的長度。目前具體的面額信息丟失了,你需要推斷出可能導致該 numWays 的硬幣面額集合。
輸出應為一個按升序排列的不重複面額列表(即所有可能出現的面額);如果不存在任何能生成該 numWays 的面額組合,則返回空數組。
輸入: numWays = [0,1,0,2,0,3,0,4,0,5]。
輸出: [2,4,6]。
解釋:
| 金額 | 方法數 | 解釋 |
|---|---|---|
| 1 | 無法用硬幣湊出總金額 1。 | |
| 2 | 1 | 唯一的方法是 [2]。 |
| 3 | 無法用硬幣湊出總金額 3。 | |
| 4 | 2 | 可以用 [2, 2] 或 [4]。 |
| 5 | 無法用硬幣湊出總金額 5。 | |
| 6 | 3 | 可以用 [2, 2, 2]、[2, 4] 或 [6]。 |
| 7 | 無法用硬幣湊出總金額 7。 | |
| 8 | 4 | 可以用 [2, 2, 2, 2]、[2, 2, 4]、[2, 6] 或 [4, 4]。 |
| 9 | 無法用硬幣湊出總金額 9。 | |
| 10 | 5 | 可以用 [2, 2, 2, 2, 2]、[2, 2, 2, 4]、[2, 4, 4]、[2, 2, 6] 或 [4, 6]。 |
題目來自力扣3592。
🔍 問題解決步驟詳解
-
初始化動態規劃數組
- 創建一個長度為
n+1的數組f(其中n是numWays的長度),用於記錄使用當前已推斷出的硬幣面額組合,湊出金額0到n的方案數。 - 將
f[0]初始化為1,表示湊出金額0的方案數為1(即不取任何硬幣)。對於i從1到n,f[i]的初始值均為0。
- 創建一個長度為
-
遍歷每個金額並檢查方案數
- 從金額
i = 1開始遍歷至i = n(對應numWays的索引為i-1):- 獲取
numWays[i-1]的值,即題目給定的湊出金額i的方案數,記為ways。 - 比較
ways與當前動態規劃數組計算出的方案數f[i]:- 情況一:
ways == f[i]- 説明當前已推斷出的硬幣面額組合已經能正確生成金額
i的方案數,無需添加新的面額。直接繼續處理下一個金額i+1。
- 説明當前已推斷出的硬幣面額組合已經能正確生成金額
- 情況二:
ways != f[i]- 此時需要嘗試添加新的面額以匹配給定的方案數。檢查條件
ways - 1 == f[i]是否成立:- 如果成立,則表示可以通過添加一個面額為
i的硬幣來彌補方案數的差距。將面額i加入結果集ans,並執行下一步的完全揹包更新。 - 如果不成立,則説明無法通過添加面額
i來滿足給定的方案數,立即返回空數組nil,表示不存在可行的硬幣面額組合。
- 如果成立,則表示可以通過添加一個面額為
- 此時需要嘗試添加新的面額以匹配給定的方案數。檢查條件
- 情況一:
- 獲取
- 從金額
-
更新動態規劃數組(完全揹包更新)
- 當添加了新的面額
i後,需要更新動態規劃數組f以反映新面額對後續金額方案數的影響。這個過程類似於解決完全揹包問題。 - 具體操作是:對於金額
j從i到n(從小到大),執行f[j] += f[j - i]。這表示,對於每個金額j,新增的方案數等於使用新面額i後,湊出金額j-i的方案數(因為加上一個面額i的硬幣即可湊出金額j)。 通過這樣的更新,f數組能動態地記錄當前所有已選面額組合下的方案數。
- 當添加了新的面額
-
輸出結果
- 如果成功遍歷完所有金額
1到n且沒有返回空數組,則收集到的結果集ans就是按升序排列的可能硬幣面額集合(因為i是遞增遍歷的)。
- 如果成功遍歷完所有金額
⏱ 複雜度分析
-
時間複雜度:
O(n^2)。- 需要遍歷每個金額
i(從1到n),共n次循環。 - 在最壞情況下,每次循環都可能需要更新動態規劃數組(內層循環從
i到n),內層循環的次數平均約為n/2。 - 因此,總體時間複雜度為平方級別
O(n^2)。
- 需要遍歷每個金額
-
空間複雜度:
O(n)。- 主要空間開銷來自動態規劃數組
f,其大小為n+1,因此額外空間複雜度為線性O(n)。
- 主要空間開銷來自動態規劃數組
💎 核心思路總結
這個算法的核心在於貪心策略和動態規劃的結合。它從小到大依次考慮每個金額,通過對比給定方案數與當前計算方案數的差異,動態地推斷出必須存在的硬幣面額,並利用完全揹包的思想更新方案數。其正確性依賴於硬幣面額是正整數且不超過 n 的條件,從而保證瞭解的唯一性(如果存在)。
Go完整代碼如下:
package main
import (
"fmt"
)
func findCoins(numWays []int) (ans []int) {
n := len(numWays)
f := make([]int, n+1)
f[0] = 1
for i := 1; i <= n; i++ {
ways := numWays[i-1]
if ways == f[i] {
continue
}
if ways-1 != f[i] {
return nil
}
ans = append(ans, i)
// 現在得到了一個大小為 i 的物品,用 i 計算完全揹包
for j := i; j <= n; j++ {
f[j] += f[j-i]
}
}
return
}
func main() {
numWays := []int{0, 1, 0, 2, 0, 3, 0, 4, 0, 5}
result := findCoins(numWays)
fmt.Println(result)
}
Python完整代碼如下:
# -*-coding:utf-8-*-
from typing import List
def findCoins(numWays: List[int]) -> List[int]:
n = len(numWays)
f = [0] * (n + 1)
f[0] = 1
ans = []
for i in range(1, n + 1):
ways = numWays[i - 1]
if ways == f[i]:
continue
if ways - 1 != f[i]:
return None
ans.append(i)
# 現在得到了一個大小為 i 的物品,用 i 計算完全揹包
for j in range(i, n + 1):
f[j] += f[j - i]
return ans
def main():
numWays = [0, 1, 0, 2, 0, 3, 0, 4, 0, 5]
result = findCoins(numWays)
print(result)
if __name__ == "__main__":
main()
C++完整代碼如下:
#include <iostream>
#include <vector>
using namespace std;
vector<int> findCoins(const vector<int>& numWays) {
int n = numWays.size();
vector<int> f(n + 1, 0);
vector<int> ans;
f[0] = 1;
for (int i = 1; i <= n; i++) {
int ways = numWays[i - 1];
if (ways == f[i]) {
continue;
}
if (ways - 1 != f[i]) {
return {}; // 返回空vector
}
ans.push_back(i);
// 現在得到了一個大小為 i 的物品,用 i 計算完全揹包
for (int j = i; j <= n; j++) {
f[j] += f[j - i];
}
}
return ans;
}
int main() {
vector<int> numWays = {0, 1, 0, 2, 0, 3, 0, 4, 0, 5};
vector<int> result = findCoins(numWays);
// 輸出結果
cout << "[";
for (size_t i = 0; i < result.size(); i++) {
cout << result[i];
if (i != result.size() - 1) {
cout << ", ";
}
}
cout << "]" << endl;
return 0;
}