LeetCode 650 - 2 Keys Keyboard
The problem is asking for the minimum number of operations required to generate exactly n characters 'A' on a notepad starting with a single 'A'.
Difficulty: 🟡 Medium
Topics: Math, Dynamic Programming
Solution
Problem Understanding
The problem is asking for the minimum number of operations required to generate exactly n characters 'A' on a notepad starting with a single 'A'. We are allowed only two operations: "Copy All," which copies all characters currently on the screen, and "Paste," which pastes the previously copied characters. Each operation counts as one step.
The input is a single integer n, which represents the target number of 'A' characters. The output is also an integer, representing the minimum number of steps required to reach exactly n 'A'.
The constraints indicate that 1 <= n <= 1000, meaning n is moderate and an algorithm with a time complexity worse than O(n^2) may become slow.
Edge cases include:
n = 1, where no operations are needed since we already start with one'A'.- Prime numbers, because they cannot be factored into smaller numbers, which may require more operations.
- Large composite numbers, where factoring efficiently reduces the number of operations by allowing repeated pastes after a "Copy All."
Approaches
Brute Force
The brute-force approach would be to simulate every sequence of operations and track the number of 'A' characters at each step. At every step, you could choose either to copy or paste. While this guarantees correctness, it is extremely slow because the number of possible sequences grows exponentially. This is infeasible for n up to 1000.
Optimal Approach
The key observation is that the minimum number of steps corresponds to the sum of the prime factors of n. If you consider generating n 'A' as multiplying blocks of 'A' characters, each multiplication corresponds to a sequence of "Copy All" followed by multiple "Paste" operations.
For example, to generate 9 'A': we can factor it as 3 * 3. Start with one 'A', copy it, paste twice to get 3 'A', then copy these 3, and paste twice again to reach 9 'A'. The number of operations is the sum of factors: 3 + 3 = 6.
This insight leads to a dynamic programming approach: for every number i from 2 to n, we find a divisor j and compute dp[i] = dp[j] + (i // j). If i is prime, the only factor is 1 and i itself, which means i operations are required.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(2^n) | O(n) | Simulate all sequences of copy/paste, too slow |
| Optimal | O(n * sqrt(n)) | O(n) | Use factors and dynamic programming to find minimal steps |
Algorithm Walkthrough
- Initialize a DP array
dpof sizen + 1, wheredp[i]represents the minimum number of steps to geti'A'. Setdp[1] = 0because no steps are required for one'A'. - Iterate
ifrom 2 ton. For eachi, initializedp[i]withi(worst-case, all pastes). - For each potential divisor
jofi(from 1 up tosqrt(i)), check ifi % j == 0. - If
jis a factor, compute the corresponding number of steps asdp[j] + (i // j). Also, considerdp[i // j] + jfor the other factor. Updatedp[i]with the minimum of its current value and these computed values. - After the loop,
dp[n]contains the minimum number of steps required.
Why it works: This works because every number can be factored into smaller blocks. Copying once and pasting repeatedly is equivalent to multiplying blocks. By iterating over factors, we ensure we always choose the sequence that minimizes the total steps.
Python Solution
class Solution:
def minSteps(self, n: int) -> int:
if n == 1:
return 0
dp = [0] * (n + 1)
dp[1] = 0
for i in range(2, n + 1):
dp[i] = i # Worst case, paste 1 A i times
for j in range(1, int(i ** 0.5) + 1):
if i % j == 0:
dp[i] = min(dp[i], dp[j] + i // j)
dp[i] = min(dp[i], dp[i // j] + j)
return dp[n]
The DP array dp stores minimum steps for all numbers from 1 to n. The inner loop checks all possible factors to minimize operations. The solution uses the factorization insight to efficiently reduce computation.
Go Solution
func minSteps(n int) int {
if n == 1 {
return 0
}
dp := make([]int, n+1)
dp[1] = 0
for i := 2; i <= n; i++ {
dp[i] = i
for j := 1; j*j <= i; j++ {
if i % j == 0 {
dp[i] = min(dp[i], dp[j]+i/j)
dp[i] = min(dp[i], dp[i/j]+j)
}
}
}
return dp[n]
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
The Go implementation mirrors the Python solution. Arrays are allocated with make, and a helper min function simplifies comparisons.
Worked Examples
Example 1: n = 3
| Step | Operation | Screen | Copied |
|---|---|---|---|
| 1 | Copy All | A | A |
| 2 | Paste | AA | A |
| 3 | Paste | AAA | A |
dp[3] = 3
Example 2: n = 1
| Step | Operation | Screen | Copied |
|---|---|---|---|
| 0 | None | A | - |
dp[1] = 0
Example 3: n = 9
| Step | Operation | Screen | Copied |
|---|---|---|---|
| 1 | Copy All | A | A |
| 2 | Paste | AA | A |
| 3 | Paste | AAA | A |
| 4 | Copy All | AAA | AAA |
| 5 | Paste | 6A | AAA |
| 6 | Paste | 9A | AAA |
dp[9] = 6
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n * sqrt(n)) | Outer loop runs n times, inner loop up to sqrt(n) for factorization |
| Space | O(n) | DP array of size n+1 |
The time complexity arises from checking all divisors for each number up to n. Space is linear due to storing minimal steps for each intermediate number.
Test Cases
# Provided examples
assert Solution().minSteps(3) == 3 # 3 A's
assert Solution().minSteps(1) == 0 # Edge case, only 1 A
# Additional test cases
assert Solution().minSteps(2) == 2 # Copy + Paste
assert Solution().minSteps(4) == 4 # Copy 1 -> Paste -> Copy 2 -> Paste
assert Solution().minSteps(5) == 5 # Prime number, requires 5 steps
assert Solution().minSteps(6) == 5 # 2*3 factorization
assert Solution().minSteps(9) == 6 # 3*3 factorization
assert Solution().minSteps(12) == 7 # 3*4 or 2*6 optimal
assert Solution().minSteps(1000) == 21 # Stress test with upper bound
| Test | Why |
|---|---|
| n=3 | Simple small number, verifies basic DP logic |
| n=1 | Edge case, no operations needed |
| n=2 | Smallest number >1 |
| n=4 | Composite number, multiple factors |
| n=5 | Prime number, tests worst-case factorization |
| n=6 | Composite, non-prime factorization |
| n=9 | Perfect square, repeated factor |
| n=12 | Multiple factor options, test DP choice |
| n=1000 | Upper boundary stress test |
Edge Cases
The first edge case is n=1. This is trivial but critical, as the algorithm must correctly return 0 without performing any operations. The implementation handles it explicitly at the start.
The second edge case involves prime numbers, e.g., n=5. Since prime numbers cannot be factored into smaller integers greater than 1, the algorithm correctly identifies that the minimal steps equal n itself.
The third edge case is large composite numbers with multiple factor combinations, e.g., n=12. The algorithm must select the combination that minimizes the sum of operations. By checking all divisors up to sqrt(n) and considering both