int minCostClimbingStairs(vector<int>& cost) {
if (cost.size()<2){
return 0;
}
int cache[cost.size()+1];
cache[0]=0;
cache[1]=0;
for (int i = 2; i <= cost.size(); ++i) {
cache[i]= min(cache[i-2]+cost[i-2],cache[i-1]+cost[i-1]);
}
return cache[cost.size()];
}
定义 f [ i ] [ j ] f[i][j] f[i][j]为使用 a r r [ i ] arr[i] arr[i] 为斐波那契数列的最后一位,使用 a r r [ j ] arr[j] arr[j] 为倒数第二位(即 a r r [ i ] arr[i] arr[i] 的前一位)时的最长数列长度。
不失一般性考虑 f [ i ] [ j ] f[i][j] f[i][j]该如何计算,首先根据斐波那契数列的定义,我们可以直接算得 a r r [ j ] arr[j] arr[j] 前一位的值为 a r r [ i ] − a r r [ j ] arr[i]−arr[j] arr[i]−arr[j],而快速得知 a r r [ i ] − a r r [ j ] arr[i]−arr[j] arr[i]−arr[j]值的坐标 t t t,可以利用 arr 的严格单调递增性质,使用「哈希表」对坐标进行转存,若坐标 t t t存在,并且符合 t < j t
f [ i ] [ j ] = m a x ( 3 , f [ j ] [ t ] + 1 ) f [ i ] [ j ] = max ( 3 , f [ j ] [ t ] + 1 ) f[i][j]=max(3,f[j][t]+1)f[i][j] = \max(3, f[j][t] + 1) f[i][j]=max(3,f[j][t]+1)f[i][j]=max(3,f[j][t]+1)
同时,当我们「从小到大」枚举 iii,并且「从大到小」枚举 j j j 时,我们可以进行如下的剪枝操作:
public int lenLongestFibSubseq(int[] arr) {
int ans=0;
HashMap<Integer,Integer> finder=new HashMap<>();
for (int i = 0; i < arr.length; i++) {
finder.put(arr[i],i);
}
int[][] cache=new int[arr.length][arr.length];
for (int i = 2; i < arr.length; i++) {
for (int j = i-1; j >= 1; j--) {
int residual=arr[i]-arr[j];
if (residual>=arr[j]){
break;
}
if (finder.containsKey(arr[i]-arr[j])){
cache[i][j]=Math.max(3,cache[j][finder.get(residual)]+1);
ans=Math.max(ans,cache[i][j]);
}
}
}
return ans;
}
事实上,这还是一道很经典的博弈论问题,也是最简单的一类博弈论问题。
为了方便,我们称「石子序列」为石子在原排序中的编号,下标从 1
开始。
由于石子的堆数为偶数,且只能从两端取石子。因此先手后手所能选择的石子序列,完全取决于先手每一次决定。
证明如下:
由于石子的堆数为偶数,对于先手而言:每一次的决策局面,都能「自由地」
选择奇数还是偶数的序列,从而限制后手下一次「只能」
奇数还是偶数石子。
具体的,对于本题,由于石子堆数为偶数,因此先手的最开始局面必然是[奇数, 偶数]
,即必然是「奇偶性不同的局面」
;当先手决策完之后,交到给后手的要么是[奇数,奇数]
或者 [偶数,偶数]
,即必然是「奇偶性相同的局面」;后手决策完后,又恢复「奇偶性不同的局面」
交回到先手 …
不难归纳推理,这个边界是可以应用到每一个回合。
因此先手只需要在进行第一次操作前计算原序列中「奇数总和」和「偶数总和」哪个大,然后每一次决策都「限制」对方只能选择「最优奇偶性序列」的对立面即可。
同时又由于所有石子总和为奇数,堆数为偶数,即没有平局,所以先手必胜。
bool stoneGame(vector<int>& piles) {
return true;
}
定义 f[l][r]
为考虑区间 [l,r]
,在双方都做最好选择的情况下,先手与后手的最大得分差值为多少。
那么 f[1][n]
为考虑所有石子,先手与后手的得分差值:
不失一般性的考虑 f [ l ] [ r ] f[l][r] f[l][r]如何转移。根据题意,只能从两端取石子(令 piles
下标从 1
开始),共两种情况:
piles[l - 1]
;取完石子后,原来的后手变为先手,从 [l+1,r]
区间做最优决策,所得价值为 f[l+1][r]
。因此本次先手从左端点取石子的话,双方差值为:piles[r−1]
;取完石子后,原来的后手变为先手,从[l,r−1]
区间做最优决策,所得价值为 f[l][r−1]
。因此本次先手从右端点取石子的话,双方差值为:双方都想赢,都会做最优决策(即使自己与对方分差最大)。因此 f [ l ] [ r ] f[l][r] f[l][r] 为上述两种情况中的最大值。
根据动态规划的状态转移方程,计算 dp [ i ] [ j ] \textit{dp}[i][j] dp[i][j] 需要使用 dp[i+1][j]
和 dp[i][j−1]
的值,即区间 [i+1,j]
和 [i,j−1]
的状态值需要在区间[i,j]
的状态值之前计算,因此计算 dp[i][j]
的顺序可以是以下两种。
从小到大遍历每个区间长度,对于每个区间长度依次计算每个区间的状态值。
从大到小遍历每个区间开始下标 i i i,对于每个区间开始下标 i i i 从小到大遍历每个区间结束下标 jjj,依次计算每个区间 [i, j]
的状态值。
计算得到 dp[0][n−1]
即为 Alice 与 Bob 的石子数量之差最大值。如果 d p [ 0 ] [ n − 1 ] > 0 dp[0][n−1]>0 dp[0][n−1]>0,则 Alice 赢得游戏,返回true,否则 Bob 赢得游戏,返回 false。
class Solution {
public boolean stoneGame(int[] ps) {
int n = ps.length;
int[][] f = new int[n + 2][n + 2];
for (int len = 1; len <= n; len++) { // 枚举区间长度
for (int l = 1; l + len - 1 <= n; l++) { // 枚举左端点
int r = l + len - 1; // 计算右端点
int a = ps[l - 1] - f[l + 1][r];
int b = ps[r - 1] - f[l][r - 1];
f[l][r] = Math.max(a, b);
}
}
return f[1][n] > 0;
}
}
根据题意,我们知道本质是求分割点,使得分割点的「左边的子数组的最大值」小于等于「右边的子数组的最小值」。
我们可以先通过一次遍历(从后往前)统计出所有后缀的最小值 min,其中 min[i] = x 含义为下标范围在
[ i , n − 1 ] [i,n−1] [i,n−1] 的 n u m s [ i ] nums[i] nums[i]的最小值为 x,然后再通过第二次遍历(从前往后)统计每个前缀的最大值(使用单变量进行维护),找到第一个符合条件的分割点即是答案。
public int partitionDisjoint(int[] nums) {
int[] min=new int[nums.length];
int[] max=new int[nums.length];
min[nums.length-1]=nums[nums.length-1];
for (int i = nums.length-2; i >=0 ; i--) {
min[i]=Math.min(min[i+1],nums[i]);
}
max[0]=nums[0];
for (int i = 1; i < nums.length; i++) {
max[i]=Math.max(nums[i],max[i-1]);
}
int ans=0;
for (int i = nums.length-2; i >=0; i--) {
if (max[i]<=min[i+1]){
ans=i;
}
}
return ans+1;
}
根据题意可知,字符有0和1两种状态,所以我们维护一个二维的cache数组来记录每个字符的状况。
cache[i][0]表示第i个字符是0的变换次数,cache[i][1]表示第i个字符是1的变换次数。
根据单调性: 若 s [ i − 1 ] = = 0 s[i-1] == 0 s[i−1]==0,s[i]是0或者1都可以保持单调性。
若 s [ i − 1 ] = = 1 s[i-1] == 1 s[i−1]==1,s[i]则必须为1才可以保持单调性(必须满足i-1是1)。
所以
cache[i][0] = cache[i-1][0] + (s[i] == '1' ? 1 : 0);//(自己是0,则前边都是0)
cache[i][1] = Math.min(cache[i-1][0],cache[i-1][1]) + (s[i] == '0' ? 1 : 0);//(自己是1,前边0或者1都可以)
最后result = Math.min(cache[i][0],cache[i][1])
public int minFlipsMonoIncr(String s) {
int cache[][]=new int[s.length()][2];
char[] arr=s.toCharArray();
cache[0][0]=arr[0]=='0'?0:1;
cache[0][1]=1-cache[0][0];
for (int i = 1; i < arr.length; i++) {
cache[i][0]=cache[i-1][0]+(arr[i]=='0'?0:1);
cache[i][1]=Math.min(cache[i-1][0],cache[i-1][1])+(arr[i]=='1'?0:1);
}
return Math.min(cache[s.length()-1][0],cache[s.length()-1][1]);
}