题目描述
这道题在leetcode要会员,所以我去了lintcode做了。
https://www.lintcode.com/problem/867/
解法
第一次解法
第一次觉得挺好做的,于是写出来如下代码:
public class Solution {
/**
* @param N: an integer
* @return: return an integer
*/
public int maxA(int N) {
// write your code here
if(N<0) return -1;
if(N<=3) return N;
int []dp = new int[N+1];
dp[0]=0;
dp[1]=1;
dp[2]=2;
dp[3]=3;
int cache=0;//记录缓冲区的数据量大小
for(int i=4;i<=N;i++){
//要保证最大,
if(dp[i-3]>=3 && i-3>=3 && dp[i-3]>3*cache){//是否能按下c-A\c-c\c-v,同时判断按下3次c-V的收益大还是按下3次c-A\c-c\c-v的收益大
dp[i] = 2*dp[i-3];//全选、复制、黏贴
cache = dp[i-3];
// System.out.println(cache+" --");
}else if(cache!=0){
dp[i] = dp[i-1]+cache;//只剩下一次机会了
}else{
//只能一个一个按
dp[i] = dp[i-1]+1;
}
}
return dp[N];
}
}
实际上,这样是不对的,因为前一个状态的选择,与后面状态的选择不是依赖关系,而是一种最大选择关系。比如6次,则A,A,A,C-A,C-C,C-V,得到6个A,但是我们按7次的话,则第7次直接按下C-V就得到了9个A。
但是如果我们现在可以按100次,那可能按第10次的结果和我们只能按10次的结果不一样。
第二次,暴力破解——递归
既然直接思考不行,我们就需要先以递归的方法来思考,暴力破解法:
public class Solution {
/**
* @param N: an integer
* @return: return an integer
*/
public int maxA(int N) {
return findMaxA(N,0,0);
}
public int findMaxA(int times,int A_nums,int cacheNums){
if(times<=0) return A_nums;
return Math.max(
//按下A
findMaxA(times-1,A_nums+1,cacheNums),
Math.max(
//按下C-V
findMaxA(times-1,A_nums+cacheNums,cacheNums),
//按下C-A,C-C
findMaxA(times-2,A_nums,A_nums)//缓冲区数目直接变为A_nums
)
);
}
}
结果超时,因为子问题重叠的可能太多了。因为这个问题存在:
如果我们现在可以按100次,那可能按第10次的结果和我们只能按10次的结果不一样。
所以使用备忘录的话,要使用三元组避免复用。所以这里就不写了。
第三次做法——重新定义dp数组
我们可以知道:
要么一直按A:A,A,…A(当 N 比较小时)。
要么是这么一个形式:A,A,…C-A,C-C,C-V,C-V,…C-V(当 N 比较大时)。
那么我们思考动态递归的过程:
- 变量是什么?按键的次数、A的个数
- dp【i】表示按键i次的显示A的最大结果
思考选择:
-
我们最后一次的按键要么是按下A,要么是按下C-V进行直接拷贝或者C-A,C-C,C-V。
对于按下A的情况,我们知道就是上一次的状态上多加了一个A,即dp[i]=dp[i-1]+1; -
对于按下C-V的情况呢?我们知道它前面必然出现了C-A,C-C,才能将数据拷贝到缓冲区,我才能用C-V。但是它的位置不确定。那么我们就必须记录C-V的最早的起点,那么此时该起点前面的两次操作必然就是C-A,C-C。这个起点的情况只能在i之前,所以我们直接暴力!
然后我们两种选择里面,取最大的那个结果即得到了当前的dp【i】。
int[]dp = new int[N+1];
dp[0] = 0;
for(int i=1;i<=N;i++){
//选择按下A,
dp[i] = dp[i-1]+1;
//选择最后一次操作为按下C-V
for(int j=2;j<i;j++){//j必然大于2才可能按下c-v,记录C-V的最早的起点
//这里全选+复制,那么我们得到了 dp(j-2)缓冲区数目,然后连续按下复制键j-i+1次
dp[i] = Math.max(dp[i],dp[j-2]*(i-j+1));
//这里就是暴力遍历,假设每个j都是C-V的最早的起点,找到最大值
}
}