LeetCode——四键键盘
Q:假设你有一个特殊的键盘包含下面的按键:
Key 1: (A):在屏幕上打印一个 'A'。
Key 2: (Ctrl-A):选中整个屏幕。
Key 3: (Ctrl-C):复制选中区域到缓冲区。
Key 4: (Ctrl-V):将缓冲区内容输出到上次输入的结束位置,并显示在屏幕上。
现在,你只可以按键 N 次(使用上述四种按键),请问屏幕上最多可以显示几个 'A'呢?
样例 1:
输入: N = 3
输出: 3
解释:
我们最多可以在屏幕上显示三个'A'通过如下顺序按键:
A, A, A
样例 2:
输入: N = 7
输出: 9
解释:
我们最多可以在屏幕上显示九个'A'通过如下顺序按键:
A, A, A, Ctrl A, Ctrl C, Ctrl V, Ctrl V
注释:
1 <= N <= 50
结果不会超过 32 位有符号整数范围。
A:
状态转移:
写出代码:
public int maxA(int N) {
if (N <=1)
return N;
int a_num = 0;
int copy = 0;
return dp(N, a_num, copy);
}
private int dp(int n, int a_num, int copy) {
if (n <= 0)//注意这里一定是<=0
return a_num;
return max(dp(n - 1, a_num + 1, copy), dp(n - 1, a_num + copy, copy), dp(n - 2, a_num, a_num));
}
private int max(int a, int b, int c) {
return Math.max(Math.max(a, b), c);
}
但这样会超时。我们用memo记录一下:
Map<int[], Integer> memo = new HashMap<>();
public int maxA(int N) {
if (N <= 1)
return N;
int a_num = 0;
int copy = 0;
return dp(N, a_num, copy);
}
private int dp(int n, int a_num, int copy) {
if (n <= 0)
return a_num;
int[] array = new int[]{n, a_num, copy};
if (memo.containsKey(array))
return map.get(array);
int max = max(dp(n - 1, a_num + 1, copy), dp(n - 1, a_num + copy, copy), dp(n - 2, a_num, a_num));
memo.put(array, max);
return max;
}
private int max(int a, int b, int c) {
return Math.max(Math.max(a, b), c);
}
还是超时了。
考虑第二种写法:
这个算法基于这样⼀个事实,最优按键序列⼀定只有两种情况:要么⼀直按 A :A,A,...A(当 N ⽐较⼩时)。要么是这么⼀个形式:A,A,...C-A,C-C,C-V,C-V,...C-V(当 N ⽐较⼤时)。因为字符数量少(N ⽐较⼩)时, C-A C-C C-V 这⼀套操作的代价相对⽐较⾼,可能不如⼀个个按 A ;⽽当 N ⽐较⼤时,后期 C-V 的收获肯定很⼤。这种情况下整个操作序列⼤致是:开头连按⼏个 A ,然后 C-A C-C组合再接若⼲ C-V ,然后再 C-A C-C 接着若⼲ C-V ,循环下去。换句话说,最后⼀次按键要么是 A 要么是 C-V 。
由于最优的操作序列⼀定是 C-A C-C 接着若⼲ C-V ,所以我们⽤⼀个变量 j 作为若⼲ C-V 的起点。那么 j 之前的 2 个操作就应该是 C-A C-C 了:
public int maxA(int N) {
if (N <= 1)
return N;
int[] dp = new int[N + 1];
dp[0] = 0;
for (int i = 1; i <= N; i++) {
dp[i] = dp[i - 1] + 1;//按 A 键
//全选 & 复制 dp[j-2],连续粘贴 i - j 次
for (int j = 2; j < i; j++) {
//屏幕上共 dp[j - 2] * (i - j + 1) 个 A
dp[i] = Math.max(dp[i], dp[j - 2] * (i - j + 1));
}
}
return dp[N];
}