jump game: 棋盘,格子 f[i]表明从起点走到i坐标
f[i]表明前i个元素总和,i=0表示不取任何元素
f[i] = max(f[i-1], f[i-2] + A[i
]);
转换为java
f[i%2] = max(f[(i-1)%2]和 f[(i-2)%2])
You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed, the only constraint stopping you from robbing each of them is that adjacent houses have security system connected and it will automatically contact the police if two adjacent houses were broken into on the same night.算法
Given a list of non-negative integers representing the amount of money of each house, determine the maximum amount of money you can rob tonight without alerting the police.数组
Given [3, 8, 4], return 8.ide
这是一道典型的序列型的动态规划。
f[i]表明路过完第i家的时候, 最多能有多少钱。
通常求什么,f[i]就是什么。
对于i=n的时候,咱们有两个选择, 就是抢或者不抢:wordpress
抢的话,最后是i=n-2的钱数加上第n家的钱数
不抢的话, 钱数等于i=n-1的钱数优化
说着感受没啥问题,可是总以为隐隐约约哪里不对劲, 漏了什么状况, 因而乎我看了个博客http://blog.csdn.net/zsy112371/article/details/52541925上面用此段代码作解释this
带码引用 https://discuss.leetcode.com/topic/11082/java-o-n-solution-space-o-1:spa
public int rob(int[] num) { int[][] dp = new int[num.length + 1][2]; for (int i = 1; i <= num.length; i++) { dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1]); dp[i][1] = num[i - 1] + dp[i - 1][0]; } return Math.max(dp[num.length][0], dp[num.length][1]); }
解释引用自博客http://blog.csdn.net/zsy112371/article/details/52541925.net
稍微解释一下这个代码,dp这个二维数组表明盗窃前i栋房子带来的最大收益,其中第二维一共有两个选择分别是0和1。0表明不盗窃第i栋房子,1表明盗窃第i栋房子。换句话就是说,dp[i][0]表明盗窃前i栋房子的最大收益,可是不包括第i栋房子的(由于没有盗窃第i栋房子),而dp[i][0]表明盗窃前i栋房子的最大收益,其中包括了第i栋房子的(由于第i栋房子被盗窃了)。
其实对一栋房子来讲,结果无非是两种,被盗窃和没被窃。因此说,才会有以前分0和1两种状况进行讨论。若是第i栋房子没被盗窃的话,那么dp[i][0] = dp[i-1][0]和dp[i-1][1]中的最大值。这个比较好理解,若是第i栋房子没被窃,那么最大总收益dp[i][0]必定和dp[i-1][0],dp[i-1][1]这两个之中最大的相同。而倘若第i栋房子被窃,那么dp[i][1]必定等于第num[i-1](注意,这里的i是从1开始的,因此i-1正好对应num中的第i项,由于num中的index是从0开始的)+dp[i-1][0],由于第i栋房子已经被窃了,第i-1栋房子确定不能被窃,不然会触发警报,因此咱们只能取dp[i-1][0]即第i-1栋房子没被窃状况的最大值。循环结束,最后返回dp[num.length][0]和dp[nums.length][1]较大的一项。
结束来自http://blog.csdn.net/zsy112371/article/details/52541925 的引用。3d
咱们走几个小栗子:
数组[80, 7, 9, 90, 87]按上面的作法获得每步的结果, 选i和不选i的结果
80 7 9 90 87 不选i 0 (80) 80 89 80+90 选i (80) 7 (89) (80+90) (87+89)
换点数字和顺序:
80 9 7 3 60 不选i 0 (80) 80 87 87 选i (80) 9 (87) (80+3) (87+60)
咱们发现, 对于每个i, 影响后面能用到的, 只有选i和不选i两个决策结果中比较大的那个, 因此咱们能够只存一个, f[i]表明路过完第i家的时候, 最多能有多少钱. 就是如下算法:
public class Solution { /** * @param A: An array of non-negative integers. * return: The maximum amount of money you can rob tonight */ //---方法一--- public long houseRobber(int[] A) { // write your code here int n = A.length; if(n == 0) return 0; long []res = new long[n+1]; res[0] = 0; res[1] = A[0]; for(int i = 2; i <= n; i++) { res[i] = Math.max(res[i-1], res[i-2] + A[i-1]); } return res[n]; }
以后呢, 咱们发现, 对于某个状态c,假设它是奇数位的状态, 它前面有奇状态a和偶状态b:
... a b c ... ... 奇1 偶1 奇2 ...
它的状态只和它前面的一个奇状态和一个偶状态有关, 因此咱们能够只存三个变量:
奇1 和 偶1 推出 奇2
但是因为在奇2状态结果一出现的时候, 奇1结果就没啥用了, 能够被当即替换, 因此咱们就能够只存一对奇偶的状态, 即两个变量, 以下:
public long houseRobber(int[] A) { // write your code here int n = A.length; if(n == 0) return 0; long []res = new long[2]; res[0] = 0; res[1] = A[0]; for(int i = 2; i <= n; i++) { res[i%2] = Math.max(res[(i-1)%2], res[(i-2)%2] + A[i-1]); } return res[n%2]; } }
这就是滚动数组, 或者叫作滚动指针的空间优化.
After robbing those houses on that street, the thief has found himself a new place for his thievery so that he will not get too much attention. This time, all houses at this place are arranged in a circle. That means the first house is the neighbor of the last one. Meanwhile, the security system for these houses remain the same as for those in the previous street.
Given a list of non-negative integers representing the amount of money of each house, determine the maximum amount of money you can rob tonight without alerting the police.
Example
nums = [3,6,4], return 6
如今呢, 咱们若是选3了的话, 4很差搞。
若是选4了的话呢, 3很差搞。
这就变成了一个循环数组问题, 循环数组问题有三种方法能够解:
这里咱们用分裂的方法, 把数组
[3, 6, 4]
分红, 选3的:
[3, 6]
和不选3的:
[6, 4]
而后把这两个非循环数组分别用上面的方法求解.
我猜这多是双序列动规吧…
public class Solution { public int houseRobber2(int[] nums) { if (nums.length == 0) { return 0; } if (nums.length == 1) { return nums[0]; } return Math.max(robber1(nums, 0, nums.length - 2), robber1(nums, 1, nums.length - 1)); } public int robber1(int[] nums, int st, int ed) { int []res = new int[2]; if(st == ed) return nums[ed]; if(st+1 == ed) return Math.max(nums[st], nums[ed]); res[st%2] = nums[st]; res[(st+1)%2] = Math.max(nums[st], nums[st+1]); for(int i = st+2; i <= ed; i++) { res[i%2] = Math.max(res[(i-1)%2], res[(i-2)%2] + nums[i]); } return res[ed%2]; } }
Given a 2D binary matrix filled with 0’s and 1’s, find the largest square containing all 1’s and return its area.
Example
For example, given the following matrix:
1 0 1 0 0 1 0 1 1 1 1 1 1 1 1 1 0 0 1 0
Return 4.
长方形每每选左上和右下角的角点开始, 正方形通常选右下角.
这道题比较直白的作法是这样的:
for x = 0 ~ n for y = 0 ~ m for a = 1 ~ min(m,n) for (x = x ~ x+a) for (y = y ~ y+1)
时间复杂度是O( m * n * min(m,n) * m * n)
这个时间复杂度, 真是不小. 接着咱们想一想, 发现呢, (2,3)这个点为右下角点的square的大小, 其实和(1,2)有关, 它最大是(1,2)的squre的边长再加一
那么呢, 咱们能够把正方形分红pic5.1图中的几个部分:
对于一个点A, 咱们须要看看它左上的点最大的正方形边长, 而后验证它左边连续是1的点的长度和上面连续是1的点的长度, 即
1. for向左都是1的长度 2. for向上都是1的长度 3. for左上点的最大正方形的边长
1, 2和3的值中的最小值+1就是结果
其中1和2能够用预处理过得矩阵left[][]和up[][]优化.
这是状态方程是:
if matrix[i][j] == 1 f[i][j] = min(LEFT[i][j-1], UP[i-1][j], f[i-1][j-1]) + 1; if matrix[i][j] == 0 f[i][j] = 0
接着呢, 咱们发现, 其实left和up矩阵是不须要的. 如图pic5.2:
咱们发现, A为右下角点的铅笔画大正方形A全为1须要的条件是:
绿色的B, 黑色的C和粉色的D全都为1
加上
而且A本身的最右下角点为1
因此咱们只须要在左点, 上点和左上点各自最大正方形的边长取最小值, 再加1就好了. 状态方程变成:
if matrix[i][j] == 1 f[i][j] = min(f[i - 1][j], f[i][j-1], f[i-1][j-1]) + 1; if matrix[i][j] == 0 f[i][j] = 0
public class Solution { /** * @param matrix: a matrix of 0 and 1 * @return: an integer */ public int maxSquare(int[][] matrix) { // write your code here int ans = 0; int n = matrix.length; int m; if(n > 0) m = matrix[0].length; else return ans; int [][]res = new int [n][m]; for(int i = 0; i < n; i++){ res[i][0] = matrix[i][0]; ans = Math.max(res[i][0] , ans); for(int j = 1; j < m; j++) { if(i > 0) { if(matrix[i][j] > 0) { res[i][j] = Math.min(res[i - 1][j],Math.min(res[i][j-1], res[i-1][j-1])) + 1; } else { res[i][j] = 0; } } else { res[i][j] = matrix[i][j]; } ans = Math.max(res[i][j], ans); } } return ans*ans; } }
动态方程不能求的要初始化
须要多少个状态, 就存多少个.
求第i个状态, 若是只与i-1有关, 那就只须要两个数组. 用previous数组, 推now数组.
因此上一个解法中, j能够都模2.
那i为何不能模呢?
由于若是i模了的话, 下一行左边开始的时候, 存的仍是上一行最右边末尾的值, 不行哒~ 状态方程变为:
if matrix[i][j] == 1 f[i%2][j] = min(f[(i - 1)%2][j], f[i%2][j-1], f[(i-1)%2][j-1]) + 1; if matrix[i][j] == 0 f[i%2][j] = 0
其中要注意初始化和答案也要记得模:
初始化 Intialization:
f[i%2][0] = matrix[i][0]; f[0][j] = matrix[0][j];
答案 Answer
max{f[i%2][j]}
Given a 2D binary matrix filled with 0’s and 1’s, find the largest square which diagonal is all 1 and others is 0.
Notice
Only consider the main diagonal situation.
For example, given the following matrix:
1 0 1 0 0
1 0 0 1 0
1 1 0 0 1
1 0 0 1 0
Return 9
这时候up和left数组就不能省啦.
public class Solution { /** * @param matrix a matrix of 0 and 1 * @return an integer */ public int maxSquare2(int[][] matrix) { // write your code here int n = matrix.length; if (n == 0) return 0; int m = matrix[0].length; if (m == 0) return 0; int[][] f = new int[n][m]; int[][] u = new int[n][m]; int[][] l = new int[n][m]; int length = 0; for (int i = 0; i < n; ++i) for (int j = 0; j < m; ++j) { if (matrix[i][j] == 0) { f[i][j] = 0; u[i][j] = l[i][j] = 1; if (i > 0) u[i][j] = u[i - 1][j] + 1; if (j > 0) l[i][j] = l[i][j - 1] + 1; } else { u[i][j] = l[i][j] = 0; if (i > 0 && j > 0) f[i][j] = Math.min(f[i - 1][j - 1], Math.min(u[i - 1][j], l[i][j - 1])) + 1; else f[i][j] = 1; } length = Math.max(length, f[i][j]); } return length * length; } }
一维的叫滚动数组, 二位的叫滚动矩阵, 二维动态规划空间优化的特色是:
f[i%2][j] = 由f[(i-1)%2]行来决定状态
Give an integer array,find the longest increasing continuous subsequence in this array.
An increasing continuous subsequence:
Can be from right to left or from left to right.
Indices of the integers in the subsequence should be continuous.
Notice
O(n) time and O(1) extra space.
Example
For [5, 4, 2, 1, 3], the LICS is [5, 4, 2, 1], return 4.
For [5, 1, 2, 3, 4], the LICS is [1, 2, 3, 4], return 4.
这道题:
若是a[i] > a[i-1]时,f[i] = f[i-1] + 1 若是a[i] < a[i-1]呢,f[i] = 1
Give you an integer matrix (with row size n, column size m),find the longest increasing continuous subsequence in this matrix. (The definition of the longest increasing continuous subsequence here can start at any row or column and go up/down/right/left any direction).
Example
Given a matrix:
[
[1 ,2 ,3 ,4 ,5],
[16,17,24,23,6],
[15,18,25,22,7],
[14,19,20,21,8],
[13,12,11,10,9]
]
return 25
相似与滑雪问题
这道题很容易想到二维dp, 记录f[i][j] 是以i,j为结尾的LIS。可是因为咱们走的方向不只仅是从左往右和从上往下, 还可能从右往左和从下往上, 因此
1. 转化方程式非顺序性的(顺序指的是从左往右,从上往下) 2. 初始化的状态在哪儿是肯定的
这样的问题, 咱们只能dfs加记忆化搜索.
每一个位置最长的LIS记录在一个矩阵dp[][] 里面, 同时用一个flag[][] 矩阵记录下遍历过没有. dp和flag矩阵能够写在一块儿, 可是最好不要, 由于dp表明当前最长长度, flag表明遍历过没有, 意义不一样.
这道题的时间复杂度是O(n*n), 由于有flag, 每一个点最多遍历一次.
public class Solution { /** * @param A an integer matrix * @return an integer */ int [][]dp; int [][]flag ; int n ,m; public int longestIncreasingContinuousSubsequenceII(int[][] A) { // Write your code here if(A.length == 0) return 0; n = A.length; m = A[0].length; int ans= 0; dp = new int[n][m]; flag = new int[n][m]; for(int i = 0; i < n; i++) { for(int j = 0; j < m; j++) { dp[i][j] = search(i, j, A); ans = Math.max(ans, dp[i][j]); } } return ans; } int []dx = {1,-1,0,0}; int []dy = {0,0,1,-1}; int search(int x, int y, int[][] A) { if(flag[x][y] != 0) return dp[x][y]; int ans = 1; int nx , ny; for(int i = 0; i < 4; i++) { nx = x + dx[i]; ny = y + dy[i]; if(0<= nx && nx < n && 0<= ny && ny < m ) { if( A[x][y] > A[nx][ny]) { ans = Math.max(ans, search( nx, ny, A) + 1); } } } flag[x][y] = 1; dp[x][y] = ans; return ans; } }
这时候咱们用万能的dfs加memorization
dp[x][y] = dp[nx][ny] + 1 (if a[x][y] > a[nx][ny])
There are n coins in a line. Two players take turns to take one or two coins from right side until there are no more coins left. The player who take the last coin wins.
Could you please decide the first play will win or lose?
Example
n = 1, return true.
n = 2, return true.
n = 3, return false.
n = 4, return true.
n = 5, return true.
棋盘和玩游戏的问题必定是博弈类问题.
这个问题我直接就想到除以三看余数, 可是老师说这个方法很差想…而且没有通用性. 我以为我可能思惟诡异吧, 好多我本身能绕一个小时把本身都绕晕了的题, 老师说这个你们都应该能想到吧…
来来来, 不能用贪心咱们就走个小栗子看看,o表明一个硬币
ooooo 先手拿 1 / \ 2 oooo ooo 后手拿 1/ \2 1/ \2 ooo oo oo o 先手拿 先手输 先手赢 先手赢 先手赢
其中剩两个石头的状况有两次,咱们能够判断一次而后存下来以备后用。
首先, 咱们知道轮到先手, 剩4个,2个或1个石头的时候,是赢的. 而3个石头的时候, 是输的。因此对于5个石头的状况, 咱们要保证先手不管在对方拿了1个剩四个仍是拿了2个剩3个的状况下,都有机会赢。
能够概括为如下方法:
此外还有另外一个方法:
public class Solution { /** * @param n: an integer * @return: a boolean which equals to true if the first player will win */ public boolean firstWillWin(int n) { // write your code here int []dp = new int[n+1]; return MemorySearch(n, dp); } boolean MemorySearch(int n, int []dp) { // 0 is empty, 1 is false, 2 is true if(dp[n] != 0) { if(dp[n] == 1) return false; else return true; } if(n <= 0) { dp[n] = 1; } else if(n == 1) { dp[n] = 2; } else if(n == 2) { dp[n] = 2; } else if(n == 3) { dp[n] = 1; } else { if((MemorySearch(n-2, dp) && MemorySearch(n-3, dp)) || (MemorySearch(n-3, dp) && MemorySearch(n-4, dp) )) { dp[n] = 2; } else { dp[n] = 1; } } if(dp[n] == 2) return true; return false; } } // 方法二 StackOverflow public class Solution { /** * @param n: an integer * @return: a boolean which equals to true if the first player will win */ public boolean firstWillWin(int n) { // write your code here boolean []dp = new boolean[n+1]; boolean []flag = new boolean[n+1]; return MemorySearch(n, dp, flag); } boolean MemorySearch(int i, boolean []dp, boolean []flag) { if(flag[i] == true) { return dp[i]; } if(i == 0) { dp[i] = false; } else if(i == 1) { dp[i] = true; } else if(i == 2) { dp[i] = true; } else { dp[i] = !MemorySearch(i-1, dp, flag) || !MemorySearch(i-2, dp, flag); } flag[i] =true; return dp[i]; } } //方法三 public class Solution { /** * @param n: an integer * @return: a boolean which equals to true if the first player will win */ public boolean firstWillWin(int n) { // write your code here if (n == 0) return false; else if (n == 1) return true; else if (n == 2) return true; boolean []dp = new boolean[n+1]; dp[0] = false; dp[1] = true; dp[2] = true; for (int i = 3; i <= n; i++) dp[i] = !dp[i - 1] || !dp[i - 2]; return dp[n]; } }
There are n coins with different value in a line. Two players take turns to take one or two coins from left side until there are no more coins left. The player who take the coins with the most value wins.
Could you please decide the first player will win or lose?
Have you met this question in a real interview? Yes
Example
Given values array A = [1,2,2], return true.
Given A = [1,2,4], return false.
来来来, 走个栗子:
[5,1,2,10] 先手拿 1(5) / \ 2(6) [1,2,10] [2,10] 后手拿 1(1)/ \2(3) 1(2)/ \2(12) [2,10] [10] [10] []
每次拿硬币的原则不是当前手拿的最多, 而是加上剩下能拿的总共最多。
f[i]先手还剩i个硬币:
f[5] 左 = 5 + min(f[2],f[1]) 右 = 6 + min(f[1],f[0])
另外一种方法:
public class Solution { /** * @param values: an array of integers * @return: a boolean which equals to true if the first player will win */ public boolean firstWillWin(int[] values) { // write your code here int n = values.length; int[] sum = new int[n + 1]; for (int i = 1; i <= n; ++i) sum[i] = sum[i - 1] + values[n - i]; int[] dp = new int[n + 1]; dp[1] = values[n - 1]; for (int i = 2; i <= n; ++i) dp[i] = Math.max(sum[i] - dp[i - 1], sum[i] - dp[i - 2]); return dp[n] > sum[n] / 2; } } // 方法一 import java.util.*; public class Solution { /** * @param values: an array of integers * @return: a boolean which equals to true if the first player will win */ public boolean firstWillWin(int[] values) { // write your code here int []dp = new int[values.length + 1]; boolean []flag =new boolean[values.length + 1]; int sum = 0; for(int now : values) sum += now; return sum < 2*MemorySearch(values.length, dp, flag, values); } int MemorySearch(int n, int []dp, boolean []flag, int []values) { if(flag[n] == true) return dp[n]; flag[n] = true; if(n == 0) { dp[n] = 0; } else if(n == 1) { dp[n] = values[values.length-1]; } else if(n == 2) { dp[n] = values[values.length-1] + values[values.length-2]; } else if(n == 3){ dp[n] = values[values.length-2] + values[values.length-3]; } else { dp[n] = Math.max( Math.min(MemorySearch(n-2, dp, flag,values) , MemorySearch(n-3, dp, flag, values)) + values[values.length-n], Math.min(MemorySearch(n-3, dp, flag, values), MemorySearch(n-4, dp, flag, values)) + values[values.length-n] + values[values.length - n + 1] ); } return dp[n]; } } // 方法二 public class Solution { /** * @param values: an array of integers * @return: a boolean which equals to true if the first player will win */ public boolean firstWillWin(int[] values) { // write your code here int n = values.length; int []dp = new int[n + 1]; boolean []flag =new boolean[n + 1]; int []sum = new int[n+1]; int allsum = values[n-1]; sum[n-1] = values[n-1]; for(int i = n-2; i >= 0; i--) { sum[i] += sum[i+1] + values[i]; allsum += values[i]; } return allsum/2 < MemorySearch(0, n, dp, flag, values, sum); } int MemorySearch(int i, int n, int []dp, boolean []flag, int []values, int []sum) { if(flag[i] == true) return dp[i]; flag[i] = true; if(i == n) { dp[n] = 0; } else if(i == n-1) { dp[i] = values[i]; } else if(i == n-2) { dp[i] = values[i] + values[i + 1]; } else { dp[i] = sum[i] - Math.min(MemorySearch(i+1, n, dp, flag, values, sum) , MemorySearch(i+2, n, dp, flag, values, sum)); } return dp[i]; } }
There are n coins in a line. Two players take turns to take a coin from one of the ends of the line until there are no more coins left. The player with the larger amount of money wins.
Could you please decide the first player will win or lose?
Example
Given array A = [3,2,2], return true.
Given array A = [1,2,4], return true.
Given array A = [1,20,4], return false.
方法一:
方法二:
public class Solution { /** * @param values: an array of integers * @return: a boolean which equals to true if the first player will win */ public boolean firstWillWin(int[] values) { // write your code here int n = values.length; int[] sum = new int[n + 1]; sum[0] = 0; for (int i = 1; i <= n; ++i) sum[i] = sum[i - 1] + values[i - 1]; // s[i][j] = sum[j + 1] - sum[i]; int[][] dp = new int[n][n]; for (int i = 0; i < n; ++i) dp[i][i] = values[i]; for (int len = 2; len <= n; ++len) { for (int i = 0; i < n; ++i) { int j = i + len - 1; if (j >= n) continue; int s = sum[j + 1] - sum[i]; dp[i][j] = Math.max(s - dp[i + 1][j], s - dp[i][j - 1]); } } return dp[0][n - 1] > sum[n] / 2; } } // 方法一 import java.util.*; public class Solution { /** * @param values: an array of integers * @return: a boolean which equals to true if the first player will win */ public boolean firstWillWin(int[] values) { // write your code here int n = values.length; int [][]dp = new int[n + 1][n + 1]; boolean [][]flag =new boolean[n + 1][n + 1]; int sum = 0; for(int now : values) sum += now; return sum < 2*MemorySearch(0,values.length - 1, dp, flag, values); } int MemorySearch(int left, int right, int [][]dp, boolean [][]flag, int []values) { if(flag[left][right]) return dp[left][right]; flag[left][right] = true; if(left > right) { dp[left][right] = 0; } else if (left == right) { dp[left][right] = values[left]; } else if(left + 1 == right) { dp[left][right] = Math.max(values[left], values[right]); } else { int pick_left = Math.min(MemorySearch(left + 2, right, dp, flag, values), MemorySearch(left + 1, right - 1, dp, flag, values)) + values[left]; int pick_right = Math.min(MemorySearch(left, right - 2, dp, flag, values), MemorySearch(left + 1, right - 1, dp, flag, values)) + values[right]; dp[left][right] = Math.max(pick_left, pick_right); } return dp[left][right]; } } // 方法二 import java.util.*; public class Solution { /** * @param values: an array of integers * @return: a boolean which equals to true if the first player will win */ public boolean firstWillWin(int[] values) { // write your code here int n = values.length; int [][]dp = new int[n + 1][n + 1]; boolean [][]flag =new boolean[n + 1][n + 1]; int[][] sum = new int[n + 1][n + 1]; for (int i = 0; i < n; i++) { for (int j = i; j < n; j++) { sum[i][j] = i == j ? values[j] : sum[i][j-1] + values[j]; } } int allsum = 0; for(int now : values) allsum += now; return allsum < 2*MemorySearch(0,values.length - 1, dp, flag, values, sum); } int MemorySearch(int left, int right, int [][]dp, boolean [][]flag, int []values, int [][]sum) { if(flag[left][right]) return dp[left][right]; flag[left][right] = true; if(left > right) { dp[left][right] = 0; } else if (left == right) { dp[left][right] = values[left]; } else if(left + 1 == right) { dp[left][right] = Math.max(values[left], values[right]); } else { int cur = Math.min(MemorySearch(left+1, right, dp, flag, values, sum), MemorySearch(left,right-1, dp, flag, values, sum)); dp[left][right] = sum[left][right] - cur; } return dp[left][right]; } }