198. 打家劫舍
dp[i]表示当前位置能偷到的最大值
max(当前位置偷+前两个位置的最大值,当前位置不偷即前一个位置的最大值)
dp[i] = Math.max(dp[i - 2]+ nums[i],dp[i-1]);
public int rob(int[] nums) {
if (nums == null || nums.length <= 0){
return 0;
}
if (nums.length == 1){
return nums[0];
}
int[] dp = new int[nums.length];
dp[0] = nums[0];
dp[1] = Math.max(nums[0],nums[1]);
for (int i = 2; i < nums.length; i++) {
dp[i] = Math.max(dp[i - 2]+ nums[i],dp[i-1]);
}
return dp[nums.length - 1];
}
于上一题不同的是是,所有的房屋围城了一圈,行成了环状,这就意味着偷了第一家,最后一家就不能偷了,偷了最后一家,第一家就不能偷了。可以分解成两个问题,偷nums[0:n-1]和偷nums[1:n],这两个之间的最大值。所以是两个动态规划数组的问题。
public int rob(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
if (nums.length == 1) {
return nums[0];
}
if (nums.length == 2){
return Math.max(nums[0],nums[1]);
}
int[] dp1 = new int[nums.length];//偷nums[0:n-1]
int[] dp2 = new int[nums.length];//偷nums[1:n]
dp1[0] = nums[0];
dp1[1] = Math.max(nums[0],nums[1]);
for (int i = 2; i < nums.length - 1; i++) {
dp1[i] = Math.max(dp1[i-2] + nums[i], dp1[i-1]);
}
dp2[1] = nums[1];
dp2[2] = Math.max(nums[2],nums[1]);
for (int i = 3;i < nums.length; i++) {
dp2[i] = Math.max(dp2[i-2] + nums[i], dp2[i-1]);
}
return Math.max(dp1[nums.length - 2],dp2[nums.length - 1]);
}
leetcode337 打家劫舍 III
树形dp问题
public int rob(TreeNode root) {
if ( root == null){
return 0;
}
//偷父节点:父节点的值 + 偷孩子节点的左右孩子节点
int robRoot = root.val;
if(root.left != null){
robRoot += rob(root.left.left) + rob(root.left.right);
}
if(root.right != null) {
robRoot += rob(root.right.left) + rob(root.right.right);
}
//不偷父节点: 偷左右孩子节点
int robChild = rob(root.left) + rob(root.right);
return Math.max(robRoot,robChild);
}
121. 买卖股票的最佳时机
很好的一篇题解:一个方法团灭 6 道股票问题
public int maxProfit(int[] prices) {
if (prices == null || prices.length == 0){
return 0;
}
int n = prices.length;
int[][]dp = new int[n][2];
dp[0][0] = 0;
dp[0][1] = - prices[0];
for(int i = 1; i < n; i++){
//今天手上没有持有股票的情况,1.前一天手上也没有持有股票 2.前一天持有股票 今天卖出
dp[i][0] = Math.max(dp[i - 1][0],dp[i - 1][1] + prices[i]);
//今天手上持有股票的情况,1.前一天手上也持有股票,今天不变 2.前一天手上没有持有股票,今天买入。
dp[i][1] = Math.max(dp[i - 1][1], - prices[i]);
}
return dp[n - 1][0];
}
空间复杂度O(1)的解法
public int maxProfit(int[] prices) {
if (prices == null || prices.length == 0){
return 0;
}
int n = prices.length;
int dp_i_0 = 0; //开始的那一天手上未持有股票
int dp_i_1 = - prices[0]; //开始的那一天手上持有股票 利润是-prices[0]
for (int i = 0; i < n; i++) {
dp_i_0 = Math.max(dp_i_0, dp_i_1 + prices[i]);
dp_i_1 = Math.max(dp_i_1, - prices[i]); //只要今天买入 手上的利润就是-prices[i] 因为限制了只进行一次交易
}
return dp_i_0;
}
买卖股票的最佳时机 II
不限制交易次数
public int maxProfit(int[] prices) {
if (prices == null || prices.length == 0){
return 0;
}
int n = prices.length;
int[][] dp = new int[n][2];
dp[0][0] = 0;
dp[0][1] = - prices[0];
for (int i = 1; i < prices.length; i++) {
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
}
return dp[n-1][0];
}
空间复杂度O(1)
public int maxProfit(int[] prices) {
if (prices == null || prices.length == 0){
return 0;
}
int n = prices.length;
int dp_i_0 = 0;
int dp_i_1 = - prices[0];
for (int i = 0; i < prices.length; i++) {
int temp = dp_i_0;
dp_i_0 = Math.max(dp_i_0, dp_i_1 + prices[i]);
dp_i_1 = Math.max(dp_i_1, temp - prices[i]);
}
return dp_i_0;
}
309. 最佳买卖股票时机含冷冻期
每次sell掉之后要等一天才能继续交易
public int maxProfit(int[] prices) {
if (prices == null || prices.length == 0){
return 0;
}
if(prices.length == 1){
return 0;
}
int n = prices.length;
int[][] dp = new int[n][2];
dp[0][0] = 0;
dp[0][1] = - prices[0];
dp[1][0] = Math.max(0,dp[0][1] + prices[1]);
dp[1][1] = Math.max(dp[0][1], - prices[1]);
for (int i = 2; i < n; i++) {
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
dp[i][1] = Math.max(dp[i - 1][1], dp[i - 2][0] - prices[i]);
}
return dp[n-1][0];
}
public int maxProfit(int[] prices) {
if (prices == null || prices.length == 0){
return 0;
}
int n =prices.length;
int dp_i_0 = 0;
int dp_i_1 = - prices[0];
int dp_pre_0 = 0; //代表dp[i-2][0] 前天卖出 今天买入
for (int i = 0; i < n; i++) {
int temp = dp_i_0;//昨天未持有股票
dp_i_0 = Math.max(dp_i_0, dp_i_1 + prices[i]); //今天未持有
dp_i_1 = Math.max(dp_i_1, dp_pre_0 - prices[i]); //今天持有
dp_pre_0 = temp; // 前天未持有
}
return dp_i_0;
}
714. 买卖股票的最佳时机含手续费
public int maxProfit(int[] prices, int fee) {
if (prices == null || prices.length == 0){
return 0;
}
int n = prices.length;
int[][] dp = new int[n][2];
dp[0][0] = 0;
dp[0][1] = - prices[0] - fee;//减去税
for (int i = 1; i < n; i++) {
dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] + prices[i]);
dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0] - prices[i] - fee); //减去税
}
return dp[n-1][0];
}
public int maxProfit(int[] prices, int fee) {
if (prices == null || prices.length == 0){
return 0;
}
int n = prices.length;
int dp_i_0 = 0;
int dp_i_1 = - prices[0] - fee;
for (int i = 0; i < n; i++) {
int temp = dp_i_0;
dp_i_0 = Math.max(dp_i_0, dp_i_1 + prices[i]);
dp_i_1 = Math.max(dp_i_1, temp - prices[i] - fee);
}
return dp_i_0;
}
最多只能进行两次交易 相当于又增加了一个交易次数的这个维度
123. 买卖股票的最佳时机 III
public int maxProfit(int[] prices) {
if (prices == null || prices.length == 0){
return 0;
}
int max_k = 2;
int n = prices.length;
int[][][] dp = new int[n][max_k + 1][2];
dp[0][2][0] = 0;
dp[0][2][1] = - prices[0];
dp[0][1][0] = 0;
dp[0][1][1] = - prices[0];
for (int i = 1; i < n; i++) {
for (int k = max_k; k >= 1; k--) {
dp[i][k][0] = Math.max(dp[i - 1][k][0], dp[i - 1][k][1] + prices[i]);
dp[i][k][1] = Math.max(dp[i - 1][k][1], dp[i - 1][k - 1][0] - prices[i]);
}
}
return dp[n - 1][max_k][0];//交易了两次的利润最大
}
public int maxProfit(int[] prices) {
if (prices == null || prices.length == 0){
return 0;
}
int n = prices.length;
int dp_i_2_0 = 0;
int dp_i_2_1 = - prices[0];
int dp_i_1_0 = 0;
int dp_i_1_1 = - prices[0];
for (int i = 1; i < n; i++) {
dp_i_2_0 = Math.max(dp_i_2_0, dp_i_2_1 + prices[i]);
dp_i_2_1 = Math.max(dp_i_2_1, dp_i_1_0 - prices[i]);
dp_i_1_0 = Math.max(dp_i_1_0, dp_i_1_1 + prices[i]);
dp_i_1_1 = Math.max(dp_i_1_1, - prices[i]);
}
return dp_i_2_0;
}
188. 买卖股票的最佳时机 IV
一次交易由买入和卖出,至少需要两天,所以说有效的限制了k应该不超过n/2,如果超过就表示没有约束作用了。
public int maxProfit(int max_k, int[] prices) {
if (prices == null || prices.length == 0 || max_k <= 0){
return 0;
}
int n = prices.length;
if (max_k > n/2){
return maxProfit_k_info(prices);
}
int[][][] dp = new int[n][max_k + 1][2];
for (int i = 0; i < n; i++){
for (int k = max_k; k >=1; k--) {
if ( i - 1 == -1) {
dp[i][k][0] = 0;
dp[i][k][1] = - prices[0];
continue;
}
dp[i][k][0] = Math.max(dp[i - 1][k][0], dp[i - 1][k][1] + prices[i]);
dp[i][k][1] = Math.max(dp[i - 1][k][1], dp[i - 1][k - 1][0] - prices[i]);
}
}
return dp[n - 1][max_k][0];
}
public int maxProfit_k_info(int[] prices) {
int n = prices.length;
int dp_i_0 = 0;
int dp_i_1 = - prices[0];
for (int i = 1;i < prices.length; i++) {
int temp = dp_i_0;
dp_i_0 = Math.max(dp_i_0, dp_i_1 + prices[i]);
dp_i_1 = Math.max(dp_i_1, temp - prices[i]);
}
return dp_i_0;
}
279. 完全平方数
dp[i] = MIN(dp[i], dp[i - j * j] + 1)
public int numSquares(int n) {
if (n <= 0) {
return 0;
}
int[] dp = new int[n + 1];
for (int i = 0; i <= n; i++){
dp[i] = i;
for (int j = 1; i - j*j >=0; j++) {
dp[i] = Math.min(dp[i], dp[i - j*j] + 1);
}
}
return dp[n];
}
72. 编辑距离
如果word1[i] 与Word2[j]相同 则
dp[i][j]= dp[i-1][j-1]
如果不同
dp[i][j] = min( 可以通过dp[i-1][j-1]让做replace
可以通过在dp[i-1][j]上做insert
可以在dp[i][j-1]上做delete )
public int minDistance(String word1, String word2) {
if (word1 == null || word2 == null) {
return 0;
}
int m = word2.length() + 1;
int n = word1.length() + 1;
int[][] dp = new int[m][n];
for (int i = 0; i < n ; i++) {
dp[0][i] = i;
}
for (int i = 0; i < m ; i++) {
dp[i][0] = i;
}
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
if (word2.charAt(i - 1) == word1.charAt(j - 1)){
dp[i][j] = dp[i - 1][j - 1];
}else{
dp[i][j] = Math.min(dp[i - 1][j - 1] + 1,dp[i - 1][j] + 1);
dp[i][j] = Math.min(dp[i][j],dp[i][j - 1] + 1);
}
}
}
return dp[m - 1][n - 1];
}
62. 不同路径
public int uniquePaths(int m, int n) {
int[][] dp = new int[m][n];
for (int i = 0; i <= m -1; i++){
dp[i][n - 1] = 1;
}
for (int i = 0; i <= n -1; i++){
dp[m - 1][i] = 1;
}
for (int i = m - 2; i >= 0; i--){
for ( int j = n - 2; j >= 0; j--) {
dp[i][j] = dp[i+1][j] + dp[i][j+1];
}
}
return dp[0][0];
}
public int uniquePaths(int m, int n) {
if ( m <=0 || n <= 0){
return 0;
}
int[] pre = new int[n];
int[] cur = new int[n];
Arrays.fill(pre,1);
Arrays.fill(cur,1);
for(int i = 1; i < m ; i++) { //循环次数
for(int j = 1; j < n; j++) {
cur[j] = cur[j-1] + pre[j];
}
pre = cur.clone();
}
return cur[n-1];
}
62. 不同路径
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
int m = obstacleGrid.length;
int n = obstacleGrid[0].length;
int[] pre = new int[n];
int[] cur = new int[n];
for (int i = 0; i < n; i++){
if (obstacleGrid[0][i] == 1){
pre[i] = 0;
break;
}else{
pre[i] = 1;
}
}
Arrays.fill(cur, 1);
if(obstacleGrid[1][0] == 1) cur[0] = 0;
for (int i = 1; i < m ; i++){
for(int j = 1 ; j < n; j++) {
cur[j] = obstacleGrid[i][j] == 1 ? 0 : cur[j-1] + pre[j];
}
pre = cur.clone();
}
return cur[n-1];
}
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
int m = obstacleGrid.length;
int n = obstacleGrid[0].length;
if(obstacleGrid[0][0] == 1 || obstacleGrid[m - 1][n - 1] == 1){
return 0;
}
int[][] dp = new int[m][n];
dp[m-1][n-1] = 1;
for (int i = m - 2; i >= 0 ; i--){
if(obstacleGrid[i][n-1] == 1) {
dp[i][n-1] = 0;
break;
}else{
dp[i][n-1] = 1;
}
}
for (int i = n - 2 ; i >= 0 ; i--){
if(obstacleGrid[m-1][i] == 1) {
dp[m - 1][i] = 0;
break;
}else{
dp[m - 1][i] = 1;
}
}
for(int i = m - 2; i >= 0; i--){
for(int j = n -2; j >= 0; j--){
dp[i][j] = obstacleGrid[i][j] == 1 ? 0 : dp[i+1][j]+dp[i][j+1];
}
}
return dp[0][0];
}
322. 零钱兑换
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount + 1];
for (int i = 1; i <= amount; i++) {
dp[i] = Integer.MAX_VALUE;
for (int coin : coins) {
if (i - coin >= 0 && dp[i - coin] != Integer.MAX_VALUE) {
dp[i] = Math.min(dp[i - coin] + 1,dp[i]);
}
}
}
return dp[amount] == Integer.MAX_VALUE ? -1 : dp[amount];
}
518. 零钱兑换 II
public int change(int amount, int[] coins) {
int[] dp = new int[amount + 1];
dp[0] = 1;
for(int coin : coins) {
for(int j = 1; j <= amount; j++) {
if( j >= coin){
dp[j] = dp[j] + dp[j - coin];
}
}
}
return dp[amount];
}
32. 最长有效括号
public int longestValidParentheses(String s) {
int res = 0;
if (s == null || s.length() == 0) {
return res;
}
int[] dp = new int[s.length()];
for (int i = 1; i < s.length(); i++) {
if (s.charAt(i) == ')') {
//右括号前面是左括号
if (s.charAt(i - 1) == '(') {
dp[i] = i >= 2 ? dp[i - 2] + 2 : 2;
//右括号前面是作括号,并且出去前边的合法序列的前边是左括号
}else if (i - dp[i - 1] > 0 && s.charAt( i - dp[i - 1] - 1) == '(') {
dp[i] = dp[i - 1] + ((i - dp[i - 1]) >= 2 ? dp[i - dp[i - 1] - 2] : 0) + 2;
}
res = Math.max(res,dp[i]);
}
}
return res;
}
64. 最小路径和
public int minPathSum(int[][] grid) {
if (grid == null || grid.length == 0 || grid[0] == null || grid[0].length == 0) {
return 0;
}
int m=grid.length-1;
int n=grid[0].length-1;
int dp[][]=new int[m+1][n+1];
dp[m][n]=grid[m][n];
for(int i=m-1;i>=0;i--){
dp[i][n]=grid[i][n]+dp[i+1][n];
}
for(int j=n-1;j>=0;j--){
dp[m][j]=grid[m][j]+dp[m][j+1];
}
for(int i=m-1;i>=0;i--){
for(int j=n-1;j>=0;j--){
dp[i][j]=grid[i][j]+Math.min(dp[i+1][j],dp[i][j+1]);
}
}
return dp[0][0];
}
91. 解码方法
public int numDecodings(String s) {
if (s == null || s.length() == 0){
return 0;
}
int len = s.length();
int[] dp = new int[len + 1];
dp[len] = 1;
dp[len - 1] = s.charAt(len - 1) == '0' ? 0 : 1;
for (int i = len - 2; i >= 0; i--) {
if(s.charAt(i) == '0') {
dp[i] = 0;
continue;
}
if ((s.charAt(i) - '0') * 10 + (s.charAt(i + 1) - '0') <= 26) {
dp[i] = dp[i + 1] + dp[i + 2];
}else{
dp[i] = dp[i + 1];
}
}
return dp[0];
}
221. 最大正方形
public int maximalSquare(char[][] matrix) {
if (matrix == null || matrix.length == 0)return 0;
int m = matrix.length;
int n = matrix[0].length;
int[][] dp = new int[m][n];
int maxLength = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (matrix[i][j] == '1') {
if (i == 0 || j == 0) {
dp[i][j] = 1;
}else{
dp[i][j] = Math.min(dp[i - 1][ j - 1], Math.min(dp[i - 1][j], dp[i][j - 1])) + 1;
}
maxLength = Math.max(maxLength, dp[i][j]);
}
}
}
return maxLength * maxLength;
}
647. 回文子串
public int countSubstrings(String s) {
if (s == null) return 0;
if (s.length() == 0 || s.length() == 1)return s.length();
int n = s.length();
boolean[][] dp = new boolean[n][n];
int result = 0;
for (int end = 0 ; end < n; end++) {
for (int start = 0; start <= end; start++) {
if (start == end) {
dp[start][end] = true;
result++;
}else {
if (s.charAt(end) == s.charAt(start) && (end - start <= 1 || dp[start + 1][end - 1])) {
dp[start][end] = true;
result++;
}
}
}
}
return result;
}
403. 青蛙过河
public boolean canCross(int[] stones) {
int n = stones.length;
HashMap<Integer,HashSet<Integer>> dp = new HashMap<>();
for (int i = 0; i < stones.length; i++) {
dp.put(stones[i],new HashSet<>());
}
dp.get(stones[0]).add(0);//开始位置
for (int i = 0; i < n; i++) {
HashSet<Integer> set = dp.get(stones[i]); //0 : 0
for (Integer k : set) {
for (int j = k - 1; j <= k + 1; j++) { //能跳的距离 j
if(j <= 0)continue;
if(dp.containsKey(stones[i] + j)) { //能跳到的位置
dp.get(stones[i] + j).add(j); //加上当前跳的距离
}
}
}
}
return !dp.get(stones[n - 1]).isEmpty();
}
552. 学生出勤记录 II
public int checkRecord(int n) {
if(n == 0)return 0;
if(n == 1)return 3;
if(n == 2)return 8;
int max = 1000000007;
long[][][] dp = new long[n+1][2][3];
dp[2][0][0] = 2; //两个数 且A的个数为0 结尾不为L PP LP
dp[2][1][0] = 3; //两个数 且A的个数为1,皆为不为L AP,LP,LA
dp[2][0][1] = 1; //两个数 结尾为L 没有A PL
dp[2][1][1] = 1; //两个数 有A 结尾为L AL
dp[2][0][2] = 1; //LL
dp[2][1][2] = 0;
for (int i = 3; i <= n; i++) {
dp[i][0][0] = (dp[i - 1][0][0] + dp[i - 1][0][2] + dp[i - 1][0][1]) % max;
dp[i][1][0] = (dp[i - 1][0][0] + dp[i - 1][0][1] + dp[i - 1][1][1] + dp[i -1][0][2] + dp[i - 1][1][2] + dp[i - 1][1][0]) % max;
dp[i][0][1] = dp[i - 1][0][0] % max;
dp[i][1][1] = dp[i - 1][1][0] % max;
dp[i][0][2] = dp[i - 1][0][1] % max;
dp[i][1][2] = dp[i - 1][1][1] % max;
}
return (int)((dp[n][0][0] + dp[n][1][0] + dp[n][0][1] + dp[n][0][2] + dp[n][1][1] + dp[n][1][2]) % max);
}
120. 三角形最小路径和I
public int minimumTotal(List<List<Integer>> triangle) {
if (triangle == null || triangle.size() == 0) return 0;
int[][] dp = new int[triangle.size() + 1][triangle.size() + 1];
for (int i = triangle.size() - 1; i >= 0; i--) {
List<Integer> cur = triangle.get(i);
for (int j = 0; j < cur.size(); j++) {
dp[i][j] = Math.min(dp[i + 1][j], dp[i + 1][j + 1])+ cur.get(j);
}
}
return dp[0][0];
}
53. 最大子序和
如果之前的subarray的总体和大于0的话,我们认为其对后续结果是由贡献的,选择加入之前的subarray
如果之间的subarray小于等于0的我们认为其对后面的结果是没有贡献的,这种情况下我们选择以当前的数字开始,重新起一个subarray
public int maxSubArray(int[] nums) {
int maxSum = nums[0];
int curSum = nums[0];
for (int i = 1; i < nums.length; i++) {
//如果curSum 对当前值有增益的话就加上,没有的话就重启一个curSum
curSum = Math.max(nums[i], curSum + nums[i]);
maxSum = Math.max(curSum, maxSum);
}
return maxSum;。
}
53. 最大连续子序列积
这题和最大连续子序和非常类似,只不过变成了,所以解决思路也很类似。
有个小细节需要注意,就是负负得正,两个负数的乘积是正数,因此我们不仅要跟踪最大值,也要跟踪最小值
public int maxProduct(int[] nums) {
int n = nums.length;
if (n == 0)return 0;
int[] dpMax = new int[n];
dpMax[0] = nums[0];
int[] dpMin = new int[n];
dpMin[0] = nums[0];
int max = nums[0];
for (int i = 1; i < n; i++) {
dpMax[i] = Math.max(dpMax[i - 1] * nums[i], Math.max(dpMin[i - 1] * nums[i], nums[i]));
dpMin[i] = Math.min(dpMin[i - 1] * nums[i], Math.min(dpMax[i - 1] * nums[i], nums[i]));
max = Math.max(dpMax[i], max);
}
return max;
}
300. 最长上升子序列
public int lengthOfLIS(int[] nums) {
int len = nums.length;
if (len < 2) {
return len;
}
int[] dp = new int[len];
Arrays.fill(dp, 1);
int max = 0;
for (int i = 1; i < len; i++) {
for (int j = 0; j < i ;j++) {
if (nums[j] < nums[i]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
max = Math.max(max, dp[i]);
}
return max;
}
132. 分割回文串 II
动态规划(Java、Python
public int minCut(String s) {
int len = s.length();
if (len < 2) {
return 0;
}
int[] dp = new int[len];//dp[i]表示以i结尾的字符串能分成回文串的最少分割数
for (int i = 0; i < len; i++) {
dp[i] = i;
}
for (int i = 1; i < len; i++) {
if (isPalidrome(s, 0 , i)) {
dp[i] = 0;
continue;
}
//当 j == i的时候 判断的是一个字符,一定是回文,所以枚举到i - 1即可
for (int j = 0; j <= i - 1; j++) {
if (isPalidrome(s,j + 1,i)) {
dp[i] = Math.min(dp[j] + 1, dp[i]);
}
}
}
return dp[len - 1];
}
private boolean isPalidrome(String s, int left, int right) {
while (left < right) {
if (s.charAt(left) != s.charAt(right)) {
return false;
}
left++;
right--;
}
return true;
}
使用动态规划保存状态来优化判断从i到j是否是回文串
public int minCut(String s) {
int len = s.length();
if (len < 2) {
return 0;
}
int[] dp = new int[len];//dp[i]表示以i结尾的字符串能分成回文串的最少分割数
for (int i = 0; i < len; i++) {
dp[i] = i;
}
boolean[][] dict = isPalidrome(s);
for (int i = 1; i < len; i++) {
if (dict[0][i]) {
dp[i] = 0;
continue;
}
//当 j == i的时候 判断的是一个字符,一定是回文,所以枚举到i - 1即可
for (int j = 0; j <= i - 1; j++) {
if (dict[j + 1][i]) {
dp[i] = Math.min(dp[j] + 1, dp[i]);
}
}
}
return dp[len - 1];
}
private boolean[][] isPalidrome(String s) {
int len = s.length();
boolean[][] dp = new boolean[len][len];
for (int right = 1; right < len; right++) {
for (int left = 0; left <= right; left++) {
if (s.charAt(left) == s.charAt(right) && (right - left <= 2 || dp[left + 1][right - 1])) {
dp[left][right] = true;
}
}
}
return dp;
}
97. 交错字符串
dp题解
dp[i][j] 表示s1[0,i) s2[0, j)能否组合构成s3[0, i + j);
这里不包括右边界,主要是为了考虑开始的时候如果只取s1,那么s2就是空串,这样的话dp[i][0]就能表示s2是空串
如果dp[i - 1][j] = true表示s1[0,i - 1) s2[0, j)能组合构成s3[0, i + j - 1),并且s1[i - 1] = s3[i + j - 1]那么dp[i][j] = true;
如果 dp [ i ] [ j - 1 ] == true,并且 s2 [ j - 1 ] == s3 [ i + j - 1], dp [ i ] [ j ] = true 。
否则的话,就更新为 dp [ i ] [ j ] = false。
如果 i 为 0,或者 j 为 0,那直接判断 s2 和 s3 对应的字母或者 s1 和 s3 对应的字母即可。
public boolean isInterleave(String s1, String s2, String s3) {
if (s1.length() + s2.length() != s3.length()) {
return false;
}
//dp[i][j] 表示s1[0,i) s2[0, j)组合构成s3[0, i + j);
boolean[][] dp = new boolean[s1.length() + 1][s2.length() + 1];
for (int i = 0; i <= s1.length(); i++) {
for (int j = 0; j <= s2.length(); j++) {
if (i == 0 && j == 0) {
dp[i][j] = true;
}else if (i == 0) {
dp[i][j] = dp[i][j - 1] && s2.charAt(j - 1) == s3.charAt(j - 1);
}else if (j == 0) {
dp[i][j] = dp[i - 1][j] && s1.charAt(i - 1) == s3.charAt(i - 1);
}else {
dp[i][j] = dp[i - 1][j] && s1.charAt(i - 1) == s3.charAt(i - 1 + j) ||
dp[i][j - 1] && s2.charAt(j - 1) == s3.charAt(i + j - 1);
}
}
}
return dp[s1.length()][s2.length()];
}
139. 单词拆分
//dp[i]表示s[0,i)能否由wordDict构成
public boolean wordBreak(String s, List<String> wordDict) {
boolean[] dp = new boolean[s.length() + 1];
dp[0] = true;
for (int i = 1; i <= s.length(); i++) {
for (int j = 0; j < i; j++) {
if (dp[j] && wordDict.contains(s.substring(j, i))){
dp[i] = true;
break;
}
}
}
return dp[s.length()];
}
140. 单词拆分 II
直接用递归的方法,先判断当前字符串在不在worddict中,如果在的话就递归的去求解剩余字符串的所有可能。
public List<String> wordBreak(String s, List<String> wordDict) {
HashSet<String> set = new HashSet<>();
for (int i = 0; i < wordDict.size(); i++) {
set.add(wordDict.get(i));
}
return wordBreakHelper(s, set, new HashMap<String, List<String>>());
}
private List<String> wordBreakHelper(String s, HashSet<String> set, HashMap<String, List<String>> map) {
if (s.length() == 0) {
return new ArrayList<>();
}
if (map.containsKey(s)) {
return map.get(s);
}
List<String> res = new ArrayList<>();
for (int j = 0; j < s.length(); j++) {
//判断当前字符串是否存在
if (set.contains(s.substring(j, s.length()))) {
//空串的情况,直接加入
if (j == 0) {
res.add(s.substring(j, s.length()));
} else {
//递归得到剩余字符串的所有组成可能,然后和当前字符串分别用空格连起来加到结果中
List<String> temp = wordBreakHelper(s.substring(0, j), set, map);
for (int k = 0; k < temp.size(); k++) {
String t = temp.get(k);
res.add(t + " " + s.substring(j, s.length()));
}
}
}
}
//缓存结果
map.put(s, res);
return res;
}
115. 不同的子序列
这里我们用一个二维数组 dp[m][n] 对应于从 S[m,S_len) 中能选出多少个 T[n,T_len)。
当 m == S_len,意味着S是空串,此时dp[S_len][n],n 取 0 到 T_len - 1的值都为 0。
当 n == T_len,意味着T是空串,此时dp[m][T_len],m 取 0 到 S_len的值都为 1。
然后状态转移的话和解法一分析的一样。如果求dp[s][t]。
S[s] == T[t],当前字符相等,那就对应两种情况,选择S的当前字母和不选择S的当前字母
dp[s][t] = dp[s+1][t+1] + dp[s+1][t]
S[s] != T[t],只有一种情况,不选择S的当前字母
dp[s][t] = dp[s+1][t]
public int numDistinct(String s, String t) {
int m = s.length();
int n = t.length();
int[][] dp = new int[m + 1][n + 1];
for (int i = 0; i <= m; i++){
dp[i][n] = 1;
}
for (int i = m - 1; i >= 0; i--) {
for (int j = n - 1; j >= 0; j--) {
if (s.charAt(i) == t.charAt(j)) {
dp[i][j] = dp[i + 1][j + 1] + dp[i + 1][j];
}else {
dp[i][j] = dp[i + 1][j];
}
}
}
return dp[0][0];
}
304. 二维区域和检索 - 矩阵不可变
private int[][] dp;
public NumMatrix(int[][] matrix) {
if (matrix.length == 0 || matrix[0].length == 0) return;
dp = new int[matrix.length + 1][matrix[0].length + 1];
for (int r = 0; r < matrix.length; r++) {
for (int c = 0; c < matrix[0].length; c++) {
dp[r + 1][c + 1] = dp[r + 1][c] + dp[r][c + 1] + matrix[r][c] - dp[r][c];
}
}
}
public int sumRegion(int row1, int col1, int row2, int col2) {
return dp[row2 + 1][col2 + 1] + dp[row1][col1] - dp[row1][col2 + 1] - dp[row2 + 1][col1];
}