4 Keys Keyboard
Last updated
Last updated
Translator: ucsk
Author: labuladong
The problem of 4 keys keyboard is very interesting and can broaden one's horizon. This problem can make you obviously feel that different definitions of dp arrays need completely different logic to think about, and this logic can produce completely different solutions.
We can't wait to solve this problem:
After reading the question, think about how to get the maximum number of characters 'A' after typing N
times of the keyboard? We are more familiar with trying questions using enumeration. Whenever we want to press the keyboard (and can press it), there are 4 buttons
for us to choose from, we can enumerate every possible operation It is obvious that this is a dynamic programming problem.
This kind of problem-solving idea is easy to understand, but the efficiency is not high. We follow the routine directly: for dynamic programming problems, we must first understand which are [ states ] and which is [ choices ].
Specific to this problem, what choices are obvious for each keystroke: four types are the 4 keys mentioned in the title, which are A
, Ctrl-A
, Ctrl-C
, and Ctrl-V
.
Next, let's think about what is the states of this problem? In other words, what information did we need to know to break for the original problem with smaller sub-problems?
Now you think about it, Is it correct for me to define the status of this problem as follows?
The first state is the remaining number of times the key can be pressed, we use n
to represent it.
The second state are the number of characters 'A' on the current screen, we use a_num
.
The third state are the number of characters 'A' still in the clipboard, represented by copy
.
By defining state in this way, we can know the base case: when the number of remaining n
is 0
, a_num
is the answer we want.
Combining the four choices just mentioned, we can express these kinds of choices of state transitions:
By describing this, we can see that the scale of the problem n
is constantly decreasing, and finally we can reach the base case of n == 0
. So this idea is correct: (Do you think so?)
This solution should be well understood because it is semantically explicit.
Below we continue to follow the routine and use memorized search to eliminate those overlapping sub-problems:
After we optimized our code in this way, although the sub-problem was repeatedly solved, the number of searches was still very large (if we submit to LeetCode it will definitely time out).
Now let's try to analyze the time complexity of the algorithm just now. The challenge is that this analysis is not easy. No matter what it is, now we write this dp function as a dp array:
We know that the maximum value of the variable n
are N
, but it is difficult to calculate the maximum number of a_num
and copy
. The lowest complexity is O(N^3). So the algorithm just now is not good, the complexity is too high, and it can no longer be optimized.
The more embarrassing thing is that this also shows that I used to define state as it is not very good. Let's change the idea of defining this dp problem.
Next, our thinking is a little more complicated, but it is very efficient.
Continue to follow our routine, choice has been defined before, or the 4
. But this time we only need to define a state, which is the remaining number of available keyboard presses n
.
This algorithm is based on the following fact. There must be only two cases of the key sequence corresponding to the optimal answer:
Either keeps pressing A: A
, A
, ... , A
(more when N
is smaller).
Either this is the form: A
, A
, ..., Ctrl-A
, Ctrl-C
, Ctrl-V
, Ctrl-V
, ..., Ctrl-V
(mostly when N
is larger). (Here you can find some mathematical rules, you can study if you are interested)
Because when the number of characters to be printed is relatively small (N
is small), "Ctrl-A
, Ctrl-C
, Ctrl-V
" consumes a relatively high number of operations, so we might as well keep pressing A
. When N
is relatively large, the gain of Ctrl-V
in the later period is definitely relatively large. In this case, the entire operation sequence is roughly like this: at the beginning, press several 'A's, then Ctrl-A
s, Ctrl-C
, and finally several Ctrl-V
s, and then continue Ctrl-A -> Ctrl-C -> Ctrl-V
Such a loop operation.
In other words, the last keystroke was either A
or Ctrl-V
. As long as we are clear about this, we can design the algorithm through these two situations:
Think about it. For the case of [pressing the A
key], it is actually a new 'A' printed on the screen of state i-1, so it is easy to get the result:
However, if we want to press Ctrl-V
, we also need to consider where we did Ctrl-A
and Ctrl-C
.
Earlier we said that the optimal sequence of operations must be Ctrl-A
, Ctrl-C
followed by several Ctrl-V
, so we use a variable j
as the starting point for these Ctrl-V
operations. Then the two operations before j
should be Ctrl-A
and Ctrl-C
:
The j
variable minus 2
is used to save the number of operations available to Ctrl-A
, Ctrl-C
. See the description picture to understand:
We have just completed this algorithm. The time complexity of the algorithm is O(N^2) and the space complexity is O(N), so this solution seems to be very efficient.
Dynamic programming is difficult to find the state transition. The different definitions we set will produce different state transition logic. Although we can all get the correct results in the end, the efficiency of the program may have amazed differences.
Let's review the method we tried for the first time. Although the overlapping sub-problem has been eliminated, the efficiency of the program is still low, but where is the low? Let's abstract the recursive framework to find out:
Let's analyze the logic of this exhaustive scheme. Obviously, it is possible to have such a sequence of operations Ctrl-A
, Ctrl+C
, Ctrl-A
, Ctrl-C
, ... , or Ctrl-V
, Ctrl-V
, ... . However, the result of the operation sequence produced by this method is not optimal, even if we have not figured out a way to circumvent these situations, thereby adding a lot of calculations of unnecessary sub-problems.
After we review the second solution, we only need to think a little bit before we can think that the operation sequence of the optimal answer should be this form: A
, A
, ..., Ctrl-A
, Ctrl-C
, Ctrl-V
, Ctrl-V
, ..., Ctrl-V
.
Based on the findings we found, we redefined state and re-searched for state transition, which logically reduced the number of invalid sub-problems, and ultimately optimized the program's operating efficiency.