A dynamic programming solution for maximizing the number of A characters printed with a limited number of keyboard operations.
Problem Restatement
We have a special keyboard with four operations:
| Key | Operation |
|---|---|
A | Print one A on the screen |
Ctrl-A | Select all text on the screen |
Ctrl-C | Copy the selected text into a buffer |
Ctrl-V | Paste the buffer after the current text |
Given an integer n, we can press keys at most n times.
Return the maximum number of A characters we can print on the screen.
The problem is asking for the best sequence of operations, not the sequence itself.
Input and Output
| Item | Meaning |
|---|---|
| Input | An integer n |
| Output | The maximum number of A characters possible |
| Constraint | We may use at most n key presses |
| Operation limit | Only A, Ctrl-A, Ctrl-C, and Ctrl-V are allowed |
Example function shape:
def maxA(n: int) -> int:
...Examples
For n = 3, the best choice is to press A three times:
A, A, AThe screen has:
AAASo the answer is 3.
For n = 7, one optimal sequence is:
A, A, A, Ctrl-A, Ctrl-C, Ctrl-V, Ctrl-VAfter the first three operations, the screen has AAA.
Then we copy all three A characters.
Each paste adds three more A characters.
After two pastes, the screen has:
AAAAAAAAASo the answer is 9.
First Thought: Brute Force
A brute force solution would try every possible sequence of n key presses.
At every step, we have up to four choices:
A
Ctrl-A
Ctrl-C
Ctrl-VSo the number of sequences grows roughly like:
4^nThis is too large.
Many sequences are also useless. For example, pressing Ctrl-C before selecting anything does not help. Pressing Ctrl-V before copying anything does not help either.
We need to reason about the structure of an optimal sequence.
Key Insight
There are two useful ways to finish a sequence.
The first way is simple: press A.
If we only press A, then after i key presses we get i characters.
The second way is copy and paste.
Any useful copy-paste block has this shape:
Ctrl-A, Ctrl-C, Ctrl-V, Ctrl-V, ...This means we first build some number of A characters, select all of them, copy them, and then paste several times.
Suppose we already have the best result after j key presses.
Then we spend:
Ctrl-A
Ctrl-CThat costs two more key presses.
Every remaining key press can be Ctrl-V.
If the total number of key presses is i, then the number of paste operations is:
i - j - 2The final screen contains the original text plus each pasted copy.
So the multiplier is:
1 + (i - j - 2) = i - j - 1Therefore, one candidate answer is:
dp[j] * (i - j - 1)Dynamic Programming Definition
Let:
dp[i]mean:
the maximum number of A characters we can print using exactly i key pressesThe base idea is:
dp[i] = ibecause we can always press A exactly i times.
Then we try every possible earlier point j where we stop building text normally and start a copy-paste block.
Algorithm
Initialize dp so that:
dp[i] = iThis represents pressing A every time.
Then for each total number of key presses i:
- Try every breakpoint
j. - Treat
dp[j]as the number ofAcharacters already on the screen. - Use the remaining operations for
Ctrl-A,Ctrl-C, and one or moreCtrl-V. - Update
dp[i]with the best result.
The transition is:
dp[i] = max(dp[i], dp[j] * (i - j - 1))Here:
| Part | Meaning |
|---|---|
dp[j] | Best number of As after j key presses |
Ctrl-A, Ctrl-C | Two required operations |
i - j - 2 | Number of paste operations |
i - j - 1 | Total multiplier after keeping the original plus pastes |
Correctness
The dynamic programming state dp[i] stores the best possible result after i key presses.
For each i, there are two cases.
First, the optimal sequence may end by typing A repeatedly. This case is covered by the initialization dp[i] = i.
Second, the optimal sequence may end with one or more paste operations. In that case, before the final paste block begins, there must be some earlier key press count j where the screen already contains some text. The sequence then performs Ctrl-A, Ctrl-C, and several Ctrl-V operations.
The best possible amount of text before that block is dp[j]. The copy operation copies all of that text. Each paste adds another copy of the same text. With i - j - 2 paste operations, the final multiplier is i - j - 1.
The algorithm checks every valid breakpoint j, so it checks every possible final copy-paste block. Since every optimal solution either uses only direct typing or has some final copy-paste block, the algorithm finds the optimal value.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(n^2) | For each i, we try possible breakpoints j |
| Space | O(n) | We store one DP value for each key count |
Implementation
class Solution:
def maxA(self, n: int) -> int:
dp = list(range(n + 1))
for i in range(1, n + 1):
for j in range(1, i - 2):
dp[i] = max(dp[i], dp[j] * (i - j - 1))
return dp[n]Code Explanation
We start with:
dp = list(range(n + 1))This means:
dp[0] = 0
dp[1] = 1
dp[2] = 2
dp[3] = 3
...This represents the strategy of pressing A every time.
Then we compute every dp[i].
for i in range(1, n + 1):For each total number of key presses, we try all possible places where the final copy-paste block could begin.
for j in range(1, i - 2):The value j means we already used j key presses to build some text.
Then we spend two operations:
Ctrl-A, Ctrl-CThe rest are paste operations.
dp[i] = max(dp[i], dp[j] * (i - j - 1))This chooses the better result between the current best and the result created by copying dp[j] and pasting it repeatedly.
Finally:
return dp[n]returns the maximum number of A characters possible with n key presses.
Testing
def run_tests():
s = Solution()
assert s.maxA(1) == 1
assert s.maxA(2) == 2
assert s.maxA(3) == 3
assert s.maxA(4) == 4
assert s.maxA(5) == 5
assert s.maxA(6) == 6
assert s.maxA(7) == 9
assert s.maxA(8) == 12
assert s.maxA(9) == 16
assert s.maxA(10) == 20
print("all tests passed")
run_tests()Test meaning:
| Test | Why |
|---|---|
n = 1 | Smallest useful input |
n = 2, 3, 4, 5, 6 | Direct typing is still optimal |
n = 7 | First case where copy-paste beats direct typing |
n = 8 | Confirms multiple paste operations |
n = 9 | Confirms a larger multiplier |
n = 10 | Checks a later DP state |