From 4dc6a163601bd048272e6ba371bda4885f727a27 Mon Sep 17 00:00:00 2001 From: tom4649 Date: Fri, 29 May 2026 06:15:26 +0900 Subject: [PATCH] 650. 2 Keys Keyboard --- 0650.2-Keys-Keyboard/memo.md | 94 ++++++++++++++++++++++++++++ 0650.2-Keys-Keyboard/step1_dp.py | 16 +++++ 0650.2-Keys-Keyboard/step1_linear.py | 14 +++++ 0650.2-Keys-Keyboard/step2.py | 18 ++++++ 0650.2-Keys-Keyboard/step3.py | 17 +++++ 5 files changed, 159 insertions(+) create mode 100644 0650.2-Keys-Keyboard/memo.md create mode 100644 0650.2-Keys-Keyboard/step1_dp.py create mode 100644 0650.2-Keys-Keyboard/step1_linear.py create mode 100644 0650.2-Keys-Keyboard/step2.py create mode 100644 0650.2-Keys-Keyboard/step3.py diff --git a/0650.2-Keys-Keyboard/memo.md b/0650.2-Keys-Keyboard/memo.md new file mode 100644 index 0000000..bc48e71 --- /dev/null +++ b/0650.2-Keys-Keyboard/memo.md @@ -0,0 +1,94 @@ +# 650. 2 Keys Keyboard + +ナップザックDPの問題から選んだ。 + +まず基本的なナップザック問題の復習から: + +> 容量 $W$ のナップサックがあります。 $n$ 個のアイテムがあり、各アイテム $i$ には重さ $weight[i]$ と価値 $value[i]$ が設定されています。 ナップサックの総重量が $W$ を超えないようにアイテムを選んだとき、得られる価値の最大値を求めてください。 + +dp[i][w] = 「最初の $i$ 個のアイテムだけを使い、制限重量が $w$ であるときの価値の最大値」 + +として解く。 + +```python +def knapsack_01_2d(W: int, weights: list[int], values: list[int]) -> int: + n = len(weights) + # dp[i][w] のテーブルを初期化 (行: アイテム数+1, 列: 容量+1) + dp = [[0] * (W + 1) for _ in range(n + 1)] + + for i in range(1, n + 1): + w_item = weights[i - 1] + v_item = values[i - 1] + for w in range(W + 1): + if w >= w_item: + # 「選ばない」と「選ぶ」の大きい方を取る + dp[i][w] = max(dp[i - 1][w], dp[i - 1][w - w_item] + v_item) + else: + # 重すぎて入れられない場合は、前の状態を引き継ぐ + dp[i][w] = dp[i - 1][w] + + return dp[n][W] + +``` + +空間計算量の削減 + +```python +def knapsack_01_1d(W: int, weights: list[int], values: list[int]) -> int: + n = len(weights) + dp = [0] * (W + 1) + + for i in range(n): + w_item = weights[i] + v_item = values[i] + # 逆順に回すことで、同じアイテムを2回選ぶのを防ぐ + for w in range(W, w_item - 1, -1): + dp[w] = max(dp[w], dp[w - w_item] + v_item) + + return dp[W] +``` + +考え方 + +1. 「アイテム(選択の対象)」は何か? +- 問題文に登場する「要素のリスト」を見つけます。 +- 例:硬貨(Coin Change)、文字列(Word Break)、仕事のタスク、配列の数字。 + +2. 「ナップサックの容量(制限リソース)」は何か? +- 「これを超えてはいけない」「ぴったりこの値にしなければならない」という足かせを見つけます。これがDP配列の長さ(列数)になります。 +- 例:目標金額、合計重量、文字列の長さ、予算。 + +3. 「価値(最適化したい目標)」は何か? +- 「最大化」または「最小化」したい数値を特定します。これがDPの各マスに保存される値です。 +- 例:総価値の最大化、使う枚数の最小化、組み合わせの総数。 + +応用 + +完全ナップサック問題 (Unbounded Knapsack) +- 特徴: 同じアイテムを何回でも選んでよい。 + +部分和問題 (Subset Sum / Partition Equal Subset Sum) +- 特徴: 「価値」という概念がなく、「特定の合計値を作れるか?」というYES/NOを判定する。 + +## step1 + +この問題もCopyかPasteかを選択する点がナップザックDPと似ている + +ナップザックDPの考え方で解いてみる。が、かなり遅い。O(n^2)なので改善できそう。 + +コピーしている個数が多いほどpasteの回数は少なくなるに決まっているのでこれを利用できる。 + +またコピーするのは得たい文字数の約数に限られる。 + + +## step2 + +https://leetcode.com/problems/2-keys-keyboard/solutions/8290642/2-keys-keyboard-prime-factorization-by-s-6zj7/?envType=problem-list-v2&envId=50vif4uc + +O(\sqrt{n})まで落とせるのか。思いつかなかった。 + +文字数をX倍しようとするとき、X回の操作(copy1回 + pasetX-1回)が必要となる。 + +すると、最小回数は素因数の和に等しくなる、ということか。 + +この素因数分解の方法自体も覚えておこう。 diff --git a/0650.2-Keys-Keyboard/step1_dp.py b/0650.2-Keys-Keyboard/step1_dp.py new file mode 100644 index 0000000..8f68eea --- /dev/null +++ b/0650.2-Keys-Keyboard/step1_dp.py @@ -0,0 +1,16 @@ +import math + + +class Solution: + def minSteps(self, n: int) -> int: + dp = [[math.inf] * (n + 1) for _ in range(n + 1)] + dp[1][0] = 0 + for i in range(1, n + 1): + min_i = math.inf if i != 1 else 0 + for j in range(1, i + 1): + if 2 * j <= i: + dp[i][j] = dp[i - j][j] + 1 + min_i = min(min_i, dp[i][j]) + dp[i][i] = min_i + 1 + + return min(dp[n]) diff --git a/0650.2-Keys-Keyboard/step1_linear.py b/0650.2-Keys-Keyboard/step1_linear.py new file mode 100644 index 0000000..a23a912 --- /dev/null +++ b/0650.2-Keys-Keyboard/step1_linear.py @@ -0,0 +1,14 @@ +class Solution: + def minSteps(self, n: int) -> int: + dp = [-1] * (n + 1) + dp[1] = 0 + + for num_to_make in range(2, n + 1): + dp[num_to_make] = num_to_make + + for num_to_paste in range(num_to_make // 2, 1, -1): + if num_to_make % num_to_paste == 0: + dp[num_to_make] = dp[num_to_paste] + (num_to_make // num_to_paste) + break + + return dp[n] diff --git a/0650.2-Keys-Keyboard/step2.py b/0650.2-Keys-Keyboard/step2.py new file mode 100644 index 0000000..4deeadb --- /dev/null +++ b/0650.2-Keys-Keyboard/step2.py @@ -0,0 +1,18 @@ +class Solution: + def minSteps(self, n: int) -> int: + if n == 1: + return 0 + + steps = 0 + divisor = 2 + + while divisor * divisor <= n: + while n % divisor == 0: + steps += divisor + n //= divisor + divisor += 1 + + if n != 1: + steps += n + + return steps diff --git a/0650.2-Keys-Keyboard/step3.py b/0650.2-Keys-Keyboard/step3.py new file mode 100644 index 0000000..bc96fce --- /dev/null +++ b/0650.2-Keys-Keyboard/step3.py @@ -0,0 +1,17 @@ +class Solution: + def minSteps(self, n: int) -> int: + if n == 1: + return 0 + + steps = 0 + divisor = 2 + while divisor * divisor <= n: + while n % divisor == 0: + steps += divisor + n //= divisor + divisor += 1 + + if n != 1: + steps += n + + return steps