Problem
You are provided with four possible operations that can be done on the editor(each operation requires 1 keyboard hit)- A
- Ctrl+A
- Ctrl+C
- Ctrl+V
Algorithm
The first intuition on seeing this problem is we should use dynamic programming, as this problem has a clear structure of sub-problems. A straightforward trial is to distinguish different cases by the last operation. Nevertheless, difficulty arises when the last operation is Ctrl+V and the second last operation is not Ctrl+C.In order to solve this problem, we have to further dissect the properties of an optimal edit sequence:
- Ctrl+A always immediately precedes Ctrl+C, otherwise it cannot be optimal.
- If a Ctrl+V follows an A, it is safe to push Ctrl+A backward to bypass A.
- If a combo Ctrl+A Ctrl+C precedes an A, it is better to push the combo forward to bypass A. That is the original edit sequence cannot be optimal.
As a consequence, if we denote the longest sequence of n operations by C(n), the recursion can be expressed as:
$$C(n) = max(1+ C(n-1), max_{i}((i+1)\times C(n-2-i)))$$
in which \(1+C(n-1)\) is for the case when the last operation is A, and the latter part is for the case when a continuous block of Ctrl+V is at the end of the optimal edit sequence. Since we don't know the optimal size of the block, we have to do a linear scan which amounts to an algorithm of \(O(n^2)\)
Code
static String[] OPERATIONS = {"A", "Ctrl+A", "Ctrl+C", "Ctrl+V"};
int longestText(int n) {
assert n > 0;
int[] length = new int[n + 1];
length[0] = 0;
length[1] = 1;
int[] optBlockSizes = new int[n+1]; // optimal size of Ctrl+V block ending at each position
optBlockSizes[1] = 0; // no Ctrl+V block, i.e., A
for (int i = 2; i < n+1; i++) { // for each length of edit sequence
int len = length[i-1] + 1;
optBlockSizes[i] = 0; // i.e., A
for (int j = 1; j < i-2; j++) { // for each size of Ctrl+V block
int newLen = (j+1)*length[i-2-j];
if (newLen > len) {
len = newLen;
optBlockSizes[i] = j;
}
}
}
// print the edit sequence
printEditSequence(optBlockSizes);
return length[n];
}
void printEditSequence(int[] pastBlocks) {
// the array is [0,n], with the first element useless
int n = pastBlocks.length - 1;
String[] editSequence = new String[n+1];
printEditSequence(editSequence, pastBlocks, n);
}
// last is the largest unprocessed element index
void printEditSequence(String[] sequence, int[] blocks, int last) {
if (last == 0) {// print sequence[1:n];
for (int i = 1; i < sequence.length-1; i++) {
System.out.print(sequence[i]+", ");
}
System.out.println(sequence[sequence.length-1]);
}
else {
if (blocks[last] == 0) {
sequence[last] = "A";
printEditSequence(sequence, blocks, last-1);
}
else {
int blockSize = blocks[last];
for (int i = last; i > last - blockSize; last--) {
sequence[i] = "Ctrl+V";
}
sequence[last-blockSize-1] = "Ctrl+A";
sequence[last-blockSize] = "Ctrl+C";
printEditSequence(sequence, blocks, last-blockSize-2);
}
}
}