-
Notifications
You must be signed in to change notification settings - Fork 0
322. Coin Change #40
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 139_word_break
Are you sure you want to change the base?
322. Coin Change #40
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| #include <queue> | ||
| #include <vector> | ||
| class Solution { | ||
| public: | ||
| int coinChange(std::add_cv_t<int>& coins, int amount) { | ||
| if (amount == 0) { | ||
| return 0; | ||
| } | ||
|
|
||
| std::vector<bool> visited(amount + 1, false); | ||
| visited[0] = true; | ||
|
|
||
| std::queue<int> sum; | ||
| sum.push(0); | ||
|
|
||
| int min_num_coins = 0; | ||
|
Comment on lines
+10
to
+16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 好みだとは思いますが,私ならここは「初期化」の一塊として捉え,空行を入れることはしないと思いました. |
||
|
|
||
| while (!sum.empty()) { | ||
| int size = sum.size(); | ||
| min_num_coins++; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Google Style Guideでは後置する必要性がなければ前置するとのことです. |
||
| for (int i = 0; i < size; i++) { | ||
| int current_amount = sum.front(); | ||
| sum.pop(); | ||
|
|
||
| for (auto coin : coins) { | ||
| int next_sum = current_amount + coin; | ||
|
|
||
| if (next_sum == amount) { | ||
| return min_num_coins; | ||
| } | ||
|
|
||
| if (next_sum < amount && !visited[next_sum]) { | ||
| visited[next_sum] = true; | ||
| sum.push(next_sum); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| return -1; | ||
| } | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| #include <queue> | ||
| #include <vector> | ||
| class Solution { | ||
| public: | ||
| int coinChange(std::vector<int>& coins, int target_amount) { | ||
| if (target_amount == 0) { | ||
| return 0; | ||
| } | ||
|
|
||
| int NOT_FOUND = -1; | ||
|
|
||
| std::vector<bool> visited_amount(target_amount + 1, false); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. こちらのコメントをご参照ください。 |
||
| visited_amount[0] = true; | ||
|
|
||
| std::queue<int> amounts; | ||
| amounts.push(0); | ||
|
|
||
| int min_num_coins = 0; | ||
|
|
||
| while (amounts.empty()) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: |
||
| int size = amounts.size(); | ||
| min_num_coins++; | ||
|
|
||
| for (int level = 0; level < size; level++) { | ||
| int amount = amounts.front(); | ||
| amounts.pop(); | ||
|
|
||
| for (auto coin : coins) { | ||
| int next_amount = amount + coin; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| if (next_amount == target_amount) { | ||
| return min_num_coins; | ||
| } | ||
| if (next_amount < target_amount && !visited_amount[next_amount]) { | ||
| visited_amount[next_amount] = true; | ||
| amounts.push(next_amount); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return NOT_FOUND; | ||
| } | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| #include <stack> | ||
| #include <vector> | ||
| class Solution { | ||
| public: | ||
| int coinChange(std::vector<int>& coins, int amount) { | ||
| if (amount < 0) { | ||
| return -1; | ||
| } | ||
| if (amount == 0) { | ||
| return 0; | ||
| } | ||
|
|
||
| std::vector<int> min_coins(amount + 1, std::numeric_limits<int>::max()); | ||
|
|
||
| std::vector<int> sorted_coins; | ||
| for (auto coin : coins) { | ||
| if (coin > amount) { | ||
| continue; | ||
| } | ||
| sorted_coins.push_back(coin); | ||
| } | ||
| sort(sorted_coins.begin(), sorted_coins.end()); | ||
|
|
||
| std::stack<std::pair<int, int>> coin_and_sum; | ||
| coin_and_sum.push({0, 0}); | ||
|
|
||
| while (!coin_and_sum.empty()) { | ||
| auto [num_coins, sum] = coin_and_sum.top(); | ||
| coin_and_sum.pop(); | ||
|
|
||
| if (sum > amount || num_coins >= min_coins[sum]) { | ||
| continue; | ||
| } | ||
|
|
||
| min_coins[sum] = num_coins; | ||
|
|
||
| for (auto coin : sorted_coins) { | ||
| coin_and_sum.push({num_coins + 1, coin + sum}); | ||
| } | ||
| } | ||
|
|
||
| return min_coins.back() == std::numeric_limits<int>::max()? -1 : min_coins.back(); | ||
| } | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| # 322. Coin Change | ||
|
|
||
| ## Step 1 | ||
|
|
||
| 合計額を大きいコインから順番に使って埋めていくアプローチを考えた。 | ||
|
|
||
| - DP table を作って、それぞれのコインを使った回数を記録する。 | ||
| - 大きい金額のコインから優先的に使っていきたいところだが、コインの金額がソートされていない。`std::set` を使って降順にソートされたリストにするか。 | ||
| - うまくいかなさそう。例えば `coins = [1, 3, 4], amount = 6` だと厳しい。ここで答えを見る。 | ||
|
|
||
| Bottom-up dp で解ける。`amount` を埋める最小のコイン数を求める問題は、より小さい合計値を埋めるコイン数を求めるという部分問題の集合として考えられる。`step1.cpp` に書いた。 | ||
|
|
||
| - コインの枚数を $N$、合計額を $M$ として、時間計算量 $O(N \times M)$、空間計算量 $O(M)$。 | ||
|
|
||
| ## Step 2 | ||
|
|
||
| Step 1 を修正したものを `step2.cpp` に書いた。 | ||
|
|
||
| ### 勉強 | ||
|
|
||
| [参照](https://github.com/potrue/leetcode/pull/40/changes#r2264799064):`NOT_FOUND` に何を入れるかは意見が分かれている。 | ||
|
|
||
| - めちゃくちゃ大きい値を入れておく派の人がいる。 | ||
| - [オーバーフロー回避](https://github.com/5103246/LeetCode_Arai60/pull/38/changes#r2658821437)? | ||
|
|
||
| [参照](https://github.com/potrue/leetcode/pull/40/changes#r2264798356):`i` は確かにわかりづらい。 | ||
|
|
||
| [参照](https://github.com/seal-azarashi/leetcode/pull/37/changes#r1830469401):BFS / DFS による実装。 | ||
|
|
||
| - グラフ探索のイメージになる。ノードを現在の `sum`、エッジをコインの金額(ないしはその加算)とみなすと、`amount` というノードまでの最短距離を求める問題として見ることができる。 | ||
| - DFSは `dfs.cpp` に書いた。 | ||
| - 最初 `std::set` を使って書こうとしたが `amount` が大きすぎるケースの枝刈りに失敗したので、`sorted_coins` を作るタイミングで値の判定を入れることにした。 | ||
| - 最短距離の問題なので無駄に掘ってしまうのはよくない。 | ||
| - BFSは `bfs.cpp` に書いた。 | ||
| - | ||
| - Queue を使う / 使わない、使う場合には何を入れておくか、アプローチは色々あるっぽい。あとは引き算でやるとか。 | ||
|
|
||
| ## Step 3 | ||
|
|
||
| Bottom-up dp を `step3.cpp` に書いた。メモの名付けがわかりづらい気がしたので `fewest_coins` とした。あと BFS を練習して、わかりづらいところを変えたものを `bfs_revised.cpp` に書いた。 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| #include <vector> | ||
| class Solution { | ||
| public: | ||
| int coinChange(std::vector<int>& coins, int amount) { | ||
| int NOT_FOUND = amount + 1; | ||
| std::vector<int> memo(amount + 1, NOT_FOUND); | ||
| memo[0] = 0; | ||
|
|
||
| for (int i = 1; i <= amount; i++) { | ||
| for (auto coin : coins) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
とのことなので,ここは |
||
| if (i >= coin) { | ||
| memo[i] = std::min(memo[i], memo[i - coin] + 1); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return memo[amount] == NOT_FOUND? -1 : memo[amount]; | ||
| } | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| #include <vector> | ||
| class Solution { | ||
| public: | ||
| int coinChange(std::vector<int>& coins, int target_amount) { | ||
| int NOT_FOUND = std::numeric_limits<int>::max() / 2; | ||
| std::vector<int> memo(target_amount + 1, NOT_FOUND); | ||
| memo[0] = 0; | ||
|
|
||
| for (int amount = 1; amount <= target_amount; amount++) { | ||
| for (auto coin : coins) { | ||
| if (amount >= coin) { | ||
| memo[amount] = std::min(memo[amount], memo[amount - coin] + 1); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return memo[target_amount] == NOT_FOUND? -1 : memo[target_amount]; | ||
| } | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| #include <vector> | ||
| class Solution { | ||
| public: | ||
| int coinChange(std::vector<int>& coins, int target_amount) { | ||
| int NOT_FOUND = std::numeric_limits<int>::max() / 2; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 自分は std::numeric_limits::max() / 2 でもそれほど意味が分かりづらいと感じませんでした。また、 std::numeric_limits::max() / 2 にしておくと、 std::numeric_limits::max() / 2 を 2 回足してもオーバーフローしないという性質があり、バグが回避できる場合があると思います。趣味の範囲かもしれません。 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. この文脈で「見つからない」という変数名は少し浮いているように思ったので、特別良い案は思い浮かびませんが、UNREACHABLE や UNACHIEVABLE などの方が、達成不可能なコイン枚数だという意味が伝えやすいかなと思いました。 |
||
|
|
||
| std::vector<int> fewest_coins(target_amount + 1, NOT_FOUND); | ||
| fewest_coins[0] = 0; | ||
|
|
||
| for (int amount = 1; amount <= target_amount; amount++) { | ||
| for (auto coin : coins) { | ||
| if (amount >= coin) { | ||
| fewest_coins[amount] = std::min(fewest_coins[amount], fewest_coins[amount - coin] + 1); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return fewest_coins[target_amount] == NOT_FOUND? -1 : fewest_coins[target_amount]; | ||
| } | ||
| }; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
std::add_cv_t<int>&ではなくstd::vector<int>&でしょうか.(寡聞にして前者を存じ上げませんでしたが,調べた限りconst volatile int&に対応することになるので,全く別物かなと思いました)