Imagine you have a special keyboard with the following keys:
- Key 1: Prints 'A' on screen
- Key 2: (Ctrl-A): Select screen
- Key 3: (Ctrl-C): Copy selection to buffer
- Key 4: (Ctrl-V): Print buffer on screen appending it after what has already been printed.
Find maximum numbers of A's that can be produced by pressing keys on the special keyboard N times.
Examples:
Input: n = 3
Output: 3
Explanation: Press key 1 three times.
Input: n = 7
Output: 9
Explanation: The best key sequence is key 1, key 1, key 1, key 2, key 3, key4, key 4.
Below are few important points to note.
a) For n < 7, the output is n itself.
b) Ctrl V can be used multiple times to print current buffer. The idea is to compute the optimal string length for n keystrokes by using a simple insight. The sequence of n keystrokes which produces an optimal string length will end with a suffix of Ctrl-A, a Ctrl-C, followed by only Ctrl-V's . (For n > 6)
Table of Content
[Naive Approach] Using Recursion - O(2 ^ n) Time and O(n) Auxiliary Space
First we need to keep pressing A to build some text, but after a point it becomes important to stop typing and start using copy-paste. Copy-paste works like multiplication—if you already have some A’s on the screen, pasting adds the same block again and again, growing much faster than typing one by one. So the goal is to decide the best moment to stop typing (the breakpoint), then do Ctrl-A, Ctrl-C, and use the remaining steps for Ctrl-V to multiply your existing A’s as much as possible.
The idea is to try all such possible stopping points using recursion and pick the one that gives the maximum result.
- If we loop from n-3 to 1 and choose each of these values for the break-point, and compute that optimal string they would produce maximum As.
- Once the loop ends, we will have the maximum of the optimal lengths for various breakpoints, thereby giving us the optimal length for n keystrokes.
#include <iostream>
using namespace std;
int optimalKeys(int n) {
// The optimal string length is n when n is smaller than 7
if (n <= 6)
return n;
// Initialize result
int max = 0;
// Try all possible break-points 'b' after which we
// will have Ctrl-A, Ctrl-C and then only Ctrl-V all the way.
for (int b = n - 3; b >= 1; b--) {
// If the breakpoint is b at b'th keystroke then the optimal
// string would have length (n-b-1) * optimalKeys(b);
int curr = (n - b - 1) * optimalKeys(b);
if (curr > max)
max = curr;
}
return max;
}
int main() {
int n = 7;
cout << optimalKeys(n) << endl;
}
class GfG {
static int optimalKeys(int n) {
// The optimal string length is n when n is smaller than 7
if (n <= 6)
return n;
// Initialize result
int max = 0;
// Try all possible break-points 'b' after which we
// will have Ctrl-A, Ctrl-C and then only Ctrl-V all the way.
for (int b = n - 3; b >= 1; b--) {
// If the breakpoint is b at b'th keystroke then the optimal
// string would have length (n - b - 1) * optimalKeys(b);
int curr = (n - b - 1) * optimalKeys(b);
if (curr > max)
max = curr;
}
return max;
}
public static void main(String[] args) {
int n = 7;
System.out.println(optimalKeys(n));
}
}
def optimalKeys(n):
# The optimal string length is n when n is smaller than 7
if n <= 6:
return n
# Initialize result
max_val = 0
# Try all possible break-points 'b' after which we
# will have Ctrl-A, Ctrl-C and then only Ctrl-V all the way.
for b in range(n - 3, 0, -1):
# If the breakpoint is b at b'th keystroke then the optimal
# string would have length (n - b - 1) * optimalKeys(b);
curr = (n - b - 1) * optimalKeys(b)
if curr > max_val:
max_val = curr
return max_val
# Driver code
if __name__ == "__main__":
n = 7
print(optimalKeys(n))
using System;
class GfG {
static int optimalKeys(int n) {
// The optimal string length is n when n is smaller than 7
if (n <= 6)
return n;
// Initialize result
int max = 0;
// Try all possible break-points 'b' after which we
// will have Ctrl-A, Ctrl-C and then only Ctrl-V all the way.
for (int b = n - 3; b >= 1; b--) {
// If the breakpoint is b at b'th keystroke then the
// optimal string would have length (n - b - 1) * optimalKeys(b);
int curr = (n - b - 1) * optimalKeys(b);
if (curr > max)
max = curr;
}
return max;
}
static void Main() {
int n = 7;
Console.WriteLine(optimalKeys(n));
}
}
function optimalKeys(n) {
// The optimal string length is n
// when n is smaller than 7
if (n <= 6)
return n;
// Initialize result
let max = 0;
// Try all possible break-points 'b' after which we
// will have Ctrl-A, Ctrl-C and then only Ctrl-V all the way.
for(let b = n - 3; b >= 1; b--) {
// If the breakpoint is b at b'th keystroke then the optimal
// string would have length (n - b - 1) * optimalKeys(b);
let curr = (n - b - 1) * optimalKeys(b);
if (curr > max)
max = curr;
}
return max;
}
// Driver code
let n = 7;
console.log(optimalKeys(n));
Output
9
In the below diagram, ok() refers to optimalKeys(n)

[Better Approach] Using Memoization (DP) - O(n^2) Time and O(n) Space
In the recursive solution, there are many overlapping subproblems as we can see from the above diagram. These values get recomputed many times, which increases time complexity. So instead of solving them repeatedly, we store the answer the first time we compute it and reuse it whenever needed. This is called Memoization (Dynamic Programming). By doing this, we avoid computing unnecessary repeating states and this improves time complexity from exponential to polynomial.
#include <iostream>
using namespace std;
int optimalKeys(int n) {
// The optimal string length is n when n is
// smaller than 7
if (n <= 6)
return n;
int screen[n];
// Initializing the optimal lengths array
// for until 6 input strokes
for (int i = 1; i <= 6; i++)
screen[i - 1] = i;
// Solve all subproblems in bottom manner
for (int i = 7; i <= n; i++) {
screen[i - 1] = 0;
// For any keystroke n, we need to loop from n-3 keystrokes
// back to 1 keystroke to find a breakpoint 'b' after which we
// will have ctrl-a, ctrl-c and then only ctrl-v all the way.
for (int b = i - 3; b >= 1; b--) {
// If the breakpoint is at b'th keystroke then
// the optimal string would have length
// (i - b - 1) * screen[b - 1];
int curr = (i - b - 1) * screen[b - 1];
if (curr > screen[i - 1])
screen[i - 1] = curr;
}
}
return screen[n - 1];
}
int main() {
int n = 7;
cout << optimalKeys(n) << endl;
return 0;
}
import java.io.*;
class GfG {
static int optimalKeys(int n) {
// The optimal string length is n
// when n is smaller than 7
if (n <= 6)
return n;
int screen[] = new int[n];
// Initializing the optimal lengths
// array for until 6 input strokes
for (int i = 1; i <= 6; i++)
screen[i - 1] = i;
// Solve all subproblems in bottom manner
for (int i = 7; i <= n; i++) {
// Initialize length of optimal
// string for n keystrokes
screen[i - 1] = 0;
// For any keystroke n, we need
// to loop from n-3 keystrokes
// back to 1 keystroke to find
// a breakpoint 'b' after which we
// will have ctrl-a, ctrl-c and
// then only ctrl-v all the way.
for (int b = i - 3; b >= 1; b--) {
// if the breakpoint is at b'th
// keystroke then the optimal string
// would have length (i-b-1)*screen[b-1];
int curr = (i - b - 1) * screen[b - 1];
if (curr > screen[i - 1])
screen[i - 1] = curr;
}
}
return screen[n - 1];
}
public static void main(String[] args) {
int n = 7;
System.out.println(optimalKeys(n));
}
}
def optimalKeys(n):
# The optimal string length is
# n when n is smaller than 7
if (n <= 6):
return n
screen = [0] * n
# Initializing the optimal lengths
# array for until 6 input strokes.
for i in range(1, 7):
screen[i - 1] = i
# Solve all subproblems in bottom manner
for i in range(7, n + 1):
# Initialize length of optimal
# string for n keystrokes
screen[i - 1] = 0
# For any keystroke n, we need to loop from
# n-3 keystrokes back to 1 keystroke to find a
# breakpoint 'b' after which we will have ctrl-a,
# ctrl-c and then only ctrl-v all the way.
for b in range(i - 3, 0, -1):
# if the breakpoint is at b'th keystroke then
# the optimal string would have length
# (i-b-1)*screen[b-1];
curr = (i - b - 1) * screen[b - 1]
if (curr > screen[i - 1]):
screen[i - 1] = curr
return screen[n - 1]
# Driver code
if __name__ == "__main__":
n = 7
print(optimalKeys(n))
using System;
class GfG {
static int optimalKeys(int n) {
// The optimal string length is n when n is smaller
// than 7
if (n <= 6)
return n;
int[] screen = new int[n];
// Initialize the optimal lengths array for up to 6
// keystrokes
for (int i = 1; i <= 6; i++)
screen[i - 1] = i;
// Solve all subproblems in a bottom-up manner
for (int i = 7; i <= n; i++) {
// Initialize length of the optimal string for n
// keystrokes
screen[i - 1] = 0;
// For any keystroke n, loop from n-3 keystrokes
// back to 1 keystroke to find a breakpoint 'b'
// after which we will have Ctrl-A, Ctrl-C, and
// then only Ctrl-V all the way.
for (int b = i - 3; b >= 1; b--) {
// If the breakpoint is at b'th keystroke,
// the optimal string would have length (i -
// b - 1) * screen[b - 1];
int curr = (i - b - 1) * screen[b - 1];
if (curr > screen[i - 1])
screen[i - 1] = curr;
}
}
return screen[n - 1];
}
static void Main(string[] args) {
int n = 7;
Console.WriteLine(optimalKeys(n));
}
}
function optimalKeys(n) {
// The optimal string length is n when n is smaller than 7
if (n <= 6)
return n;
let screen = new Array(n);
for (let i = 0; i < n; i++) {
screen[i] = 0;
}
// Initializing the optimal lengths
// array for until 6 input strokes
for (let i = 1; i <= 6; i++)
screen[i - 1] = i;
// Solve all subproblems in bottom manner
for (let i = 7; i <= n; i++) {
// Initialize length of optimal string for n keystrokes
screen[i - 1] = 0;
// For any keystroke n, we need to loop from
// n-3 keystrokes back to 1 keystroke to find
// a breakpoint 'b' after which we will have
// ctrl-a, ctrl-c and then only ctrl-v all the way.
for (let b = i - 3; b >= 1; b--) {
// If the breakpoint is at b'th keystroke then
// the optimal string would have length
// (i-b-1)*screen[b-1];
let curr = (i - b - 1) * screen[b - 1];
if (curr > screen[i - 1])
screen[i - 1] = curr;
}
}
return screen[n - 1];
}
// Driver code
let n = 7;
console.log(optimalKeys(n));
Output
9
[Expected Approach] Using Bottom Up (DP) - O(n) Time and O(n) Space
After building some A’s, we use Ctrl-A and Ctrl-C once, then Ctrl-V to multiply the result. However, doing too many pastes is not optimal—it's often better to restart the copy process for a bigger gain. Hence, we only consider the most effective cases: 1, 2, or 3 pastes. Taking the maximum of these captures the optimal solution efficiently without checking all breakpoints.
Consider the following dry run : n = 7
For i = 1 --> screen[0] = 1
For i = 2 --> screen[1] = 2
For i = 3 --> screen[2] = 3
For i = 4 --> screen[3] = 4
For i = 5 --> screen[4] = 5
For i = 6 --> screen[5] = 6
For i = 7 --> screen[6] = max(2 × screen[3], 3 × screen[2], 4 × screen[1]) = max(2 × 4, 3 × 3, 4 × 2) = max(8, 9, 8) = 9
Finally, screen = [1, 2, 3, 4, 5, 6, 9]
Final answer : 9
#include <iostream>
using namespace std;
int optimalKeys(int n) {
// The optimal string length is n when n is smaller than 7
if (n <= 6)
return n;
// An array to store result of subproblems
int screen[n];
// Initialize the optimal lengths array for up to 6 keystrokes
for (int i = 1; i <= 6; i++)
screen[i - 1] = i;
// Solve all subproblems in a bottom-up manner
for (int i = 7; i <= n; i++) {
// For any keystroke n, calculate the maximum of:
// 1. Pressing Ctrl-V once after copying the A's
// obtained by (n-3) keystrokes.
// 2. Pressing Ctrl-V twice after copying the A's
// obtained by (n-4) keystrokes.
// 3. Pressing Ctrl-V thrice after copying the A's
// obtained by (n-5) keystrokes.
screen[i - 1] = max(2 * screen[i - 4],
max(3 * screen[i - 5],
4 * screen[i - 6]));
}
return screen[n - 1];
}
int main() {
int n = 7;
cout << optimalKeys(n) << endl;
return 0;
}
class GfG {
static int optimalKeys(int n) {
// The optimal string length is n when n is smaller than 7
if (n <= 6)
return n;
// An array to store result of subproblems
int[] screen = new int[n];
// Initializing the optimal lengths array for
// until 6 input strokes
for (int i = 1; i <= 6; i++)
screen[i - 1] = i;
// Solve all subproblems in bottom-up manner
for (int i = 7; i <= n; i++) {
// for any keystroke n, we will need to choose between:
// 1. pressing Ctrl-V once after copying the
// A's obtained by n-3 keystrokes.
// 2. pressing Ctrl-V twice after copying the A's
// obtained by n-4 keystrokes.
// 3. pressing Ctrl-V thrice after copying the A's
// obtained by n-5 keystrokes.
screen[i - 1] = Math.max(2 * screen[i - 4],
Math.max(3 * screen[i - 5],
4 * screen[i - 6]));
}
return screen[n - 1];
}
public static void main(String[] args) {
int n = 7;
System.out.println(optimalKeys(n));
}
}
def optimalKeys(n):
# The optimal string length is n when n is smaller than 7
if n <= 6:
return n
# An array to store result of subproblems
screen = [0] * n
# Initializing the optimal lengths array for until 6 input strokes
for i in range(1, 7):
screen[i - 1] = i
# Solve all subproblems in bottom manner
for i in range(7, n + 1):
# for any keystroke n, we will need to choose between:
# 1. pressing Ctrl-V once after copying the A's
# obtained by n-3 keystrokes.
# 2. pressing Ctrl-V twice after copying the A's
# obtained by n-4 keystrokes.
# 3. pressing Ctrl-V thrice after copying the A's
# obtained by n-5 keystrokes.
screen[i - 1] = max(2 * screen[i - 4],
max(3 * screen[i - 5],
4 * screen[i - 6]))
return screen[n - 1]
# Driver code
if __name__ == "__main__":
n = 7
print(optimalKeys(n))
using System;
class GfG {
static int optimalKeys(int n) {
// The optimal string length is n when n is smaller than 7
if (n <= 6)
return n;
// An array to store result of subproblems
int[] screen = new int[n];
// Initializing the optimal lengths array for until 6 input strokes
for (int i = 1; i <= 6; i++)
screen[i - 1] = i;
// Solve all subproblems in a bottom-up manner
for (int i = 7; i <= n; i++) {
// for any keystroke n, we will need to choose between:
// 1. pressing Ctrl-V once after copying the A's
// obtained by n-3 keystrokes.
// 2. pressing Ctrl-V twice after copying the A's
// obtained by n-4 keystrokes.
// 3. pressing Ctrl-V thrice after copying the A's
// obtained by n-5 keystrokes.
screen[i - 1] = Math.Max(2 * screen[i - 4],
Math.Max(3 * screen[i - 5],
4 * screen[i - 6]));
}
return screen[n - 1];
}
static void Main(String[] args) {
int n = 7;
Console.WriteLine(optimalKeys(n));
}
}
function optimalKeys(n) {
// The optimal string length is n when n is smaller than 7
if (n <= 6)
return n;
// An array to store result of subproblems
let screen = [];
// Initializing the optimal lengths array for until 6 input strokes
for (let i = 1; i <= 6; i++)
screen[i - 1] = i;
// Solve all subproblems in bottom-up manner
for (let i = 7; i <= n; i++) {
// for any keystroke n, we will need to choose between:
// 1. pressing Ctrl-V once after copying the A's
// obtained by n-3 keystrokes.
// 2. pressing Ctrl-V twice after copying the A's
// obtained by n-4 keystrokes.
// 3. pressing Ctrl-V thrice after copying the A's
// obtained by n-5 keystrokes.
screen[i - 1] = Math.max(2 * screen[i - 4],
Math.max(3 * screen[i - 5],
4 * screen[i - 6]));
}
return screen[n - 1];
}
// Driver code
let n = 7
console.log(optimalKeys(n));
Output
9