动归个人理解
- 一般来说求i到j的范围数据,需要参考前面的,必须new int[][]维护dp数组;
- 如果只是单纯的求xx最大值,就只需要维护一个int maxXxx来作为状态转移即可
- 一般递归都能改成dp,就像是递归的方法名必须要明确其含义(输入xx和yy,来得到zz),dp数组的含义也必须明确(dp[i][j]代表例如:i天买入,j天卖出的收益)
- dp[][]数组的状态转移方程,所依赖的位置必须事先已经遍历过,否则可能因为new数组时默认赋值而产生问题(力扣5题)
力扣5最长回文子串
题目描述
超经典的一道题,用二维数组维护状态转移方程,有一个细节点就是到底维护dp[][]的右上角还是左下角
状态转移方程
dp[i][j]表示i到j是回文串,为true =
当前arr[i]等于arr[j] 且
内层dp[i+1][j-1]为true 或
长度小于3( j-i<=2 )
题解
- 因为
- 所以这是错的,状态转移参考的是dp数组默认的false
- 这是对的
最终实现
class Solution {
public String longestPalindrome(String s) {
if(s == null || s.length() <= 1){return s;}
char[] arr = s.toCharArray();
int len = s.length();
//保留最长回文串的前后指针
int left = 0;
int right = 0;
//dp保存i到j是否是回文串:状态转移方程是:i+1~~j-1也是true
boolean[][] dp = new boolean[len][len];//默认都是false
for(int i = 0 ; i < len ; i++){
dp[i][i] = true;//自身是一个回文串
}
//i到j是否是回文串
for(int j = 1 ; j < len ; j++){//左i
for(int i = 0 ; i < j ;i++){//右j
//首尾不同 不是回文串
if(arr[i] != arr[j]){continue;}
//首尾相同,要参考内部是不是回文串
if( j-i <= 2 || dp[i+1][j-1] ){//长度小于3 or 内层(dp左下角是true)
dp[i][j] = true;
if(right-left < j-i){
right = j;
left = i;
}
}
}
}
return s.substring(left,right+1);
}
}
同程秋招
趁着回忆出来记录一下,不过感觉这个题状态转移方程没那么强的像动归,更像是一个普通的字符串问题
题目描述
若输入字符串s = “level” ,返回"l",即最长相同前后缀。(前缀:l、le、lev、leve)(后缀:e、ev、eve、evel)(不包括s自身)
若输入字符串s = “ababab” 则返回"abab"
题解
public class Solution {
public static void main(String[] args) {
Solution solution = new Solution();
String level = solution.longestPrefix("ababab");
System.out.println(level);
}
public String longestPrefix (String s) {
//最长、既是前缀、也是后缀
//1.空串
if(s == null || s.length() == 0){
return "";
}
int len = s.length();
char[] arr = s.toCharArray();
//长度1一定是
if(len == 1){
return s;
}
//题目说不能包括s自身,所以特殊情况
if(len == 2 && arr[0] == arr[1]){
return "" + arr[0];
}
//指定一个跨度
int gap = 1;
String dest = "";
//前gap 和 后gap是否相同
while(gap < len){
//前缀
String leftString = s.substring(0,gap);
//后缀
String rightString = s.substring(len-gap,len);
gap++;//gap扩大
if( !leftString.equals(rightString) ){
continue;//不能用break
}else{
dest = leftString;
}
}
return dest;
}
}
力扣55跳跃游戏
题目描述
状态转移方程
最大能达到的角标 = 遍历到的i位置 + i位置上的跳跃距离
题解
class Solution {
public boolean canJump(int[] nums) {
if(nums == null){return true;}
int remote = 0;//最远的角标(状态转移方程:= Math.max)
for(int i = 0 ; i < nums.length; i++){
//跳不到i这来
if(remote < i){return false;}
//当前角标 + 跳跃长度
remote = Math.max(remote, i+nums[i]);
if(remote >= nums.length - 1){return true;}
}
return false;
}
}
力扣139单词拆分
题目描述
状态转移方程
dp[x]表示x位置之前都能被字典匹配 = 当前角标i + 用String::startsWith(String word ; int offset)
匹配到的字符串长度
题解
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
if("".equals(s) || s == null){return true;}
int len = s.length();
//创建一个dp数组,boolean类型,保证第一个为true(最开始就能匹配)
//然后用这个 被匹配到的字典长度+原来角标———再去匹配
//每次匹配都会去遍历wordDict字典
boolean[] dp = new boolean[len + 1];//因为最后是判断dp[len] == true,保证指针不越界
dp[0] = true;//认为字符串s前面还拼接了一个"",dp的长度位len+1,0号位是""一定能被匹配,dp[1]对应的是s的0号位
for(int i = 0 ; i < len ; i++){
if( !dp[i] ){continue;}
//i位置,遍历匹配字典
for(String word : wordDict){
//不越界 && s从i开始能匹配上word
if( i + word.length() <= len && s.startsWith(word,i) ){
dp[i + word.length()] = true;
}
}
}
return dp[len];
}
}
力扣62不同路径
题目描述
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
题解1:递归
太暴力了,容易爆栈
class Solution {
public int uniquePaths(int m, int n) {
if(m == 1 || n == 1){return 1;}
return uniquePaths(m-1,n) + uniquePaths(m,n-1);
}
}
题解2:dp
状态转移方程
dp[i][j] = dp[i-1][j] + dp[i][j-1];左边路径数 + 上边路径数
class Solution {
public int uniquePaths(int m, int n) {
int[][] dp = new int[m][n];
for(int i = 0 ; i < m ; i++){
dp[i][0] = 1;
}
for(int j = 0 ; j < n ; j++){
dp[0][j] = 1;
}
for(int i = 1 ; i < m ; i++){
for(int j = 1 ; j < n ; j++){
dp[i][j] = dp[i-1][j] + dp[i][j-1];
}
}
return dp[m-1][n-1];
}
}
力扣64最小路径和
上面这道题的延申
给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
状态转移方程
维护一个dp[i][j]表示该位置上的路径 = Math.min( 上or左路径 ) + 自身路径grid[i][j]
题解
其实可以发现这里不用新建一个dp[][],可以直接在原有的grid上进行修改
class Solution {
public int minPathSum(int[][] grid) {
int row = grid.length;
int len = grid[0].length;
int[][] dp = new int[row][len];
dp[0][0] = grid[0][0];
for(int i = 1 ; i < row ; i++){
dp[i][0] = grid[i][0] + dp[i-1][0];
}
for(int j = 1 ; j < len ; j++){
dp[0][j] = grid[0][j] + dp[0][j-1];
}
for(int i = 1 ; i < row ; i++){
for(int j = 1 ; j < len ; j++){
dp[i][j] = Math.min( dp[i-1][j] , dp[i][j-1]) + grid[i][j];
}
}
return dp[row-1][len-1];
}
}
力扣121买卖股票最佳时机
简单题
状态转移方程
因为我们只是求最大值,所以不用维护dp[][],直接维护一个maxProfit用Math.max()来更新最大值即可
题解
class Solution {
public int maxProfit(int[] prices) {
int len = prices.length;
int maxProfit = 0;
for(int i = 0 ; i < len-1 ; i++){
for(int j = i+1 ; j < len ; j++){
maxProfit = Math.max(maxProfit, prices[j]-prices[i]);
}
}
return maxProfit;
}
}
青蛙跳台、斐波那契、爬楼梯
随便参考一个,用一维dp[]来保存前两位的值
递归
递归老问题,容易超时、爆栈
class Solution {
public int climbStairs(int n) {
if(n == 1 ) return 1;
if(n == 2 ) return 2;
return climbStairs(n-1) + climbStairs(n-2);
}
}
动归
class Solution {
public int climbStairs(int n) {
if(n == 1)return 1;
if(n == 2)return 2;
int[] dp = new int[n+1];
dp[1] = 1;
dp[2] = 2;
for(int i = 3 ; i <= n ; i++){
dp[i] = dp[i-1] + dp[i-2];
}
return dp[n];
}
}