要点:
动规解题的一般思路
1. 将原问题分解为子问题
把原问题分解为若干个子问题,子问题和原问题形式相同或类似,只不过规模变小了。子问题都解决,原问题即解决(数字三角形例)。
子问题的解一旦求出就会被保存,所以每个子问题只需求 解一次。
2.确定状态
在用动态规划解题时,我们往往将和子问题相关的各个变量的一组取值,称之为一个“状 态”。一个“状态”对应于一个或多个子问题, 所谓某个“状态”下的“值”,就是这个“状 态”所对应的子问题的解。
所有“状态”的集合,构成问题的“状态空间”。“状态空间”的大小,与用动态规划解决问题的时间复杂度直接相关。 在数字三角形的例子里,一共有N×(N+1)/2个数字,所以这个问题的状态空间里一共就有N×(N+1)/2个状态。
整个问题的时间复杂度是状态数目乘以计算每个状态所需时间。在数字三角形里每个“状态”只需要经过一次,且在每个状态上作计算所花的时间都是和N无关的常数。
3.确定一些初始状态(边界状态)的值
以“数字三角形”为例,初始状态就是底边数字,值就是底边数字值。
4. 确定状态转移方程
定义出什么是“状态”,以及在该“状态”下的“值”后,就要找出不同的状态之间如何迁移――即如何从一个或多个“值”已知的 “状态”,求出另一个“状态”的“值”(递推型)。状态的迁移可以用递推公式表示,此递推公式也可被称作“状态转移方程”。
数字三角形的状态转移方程:
能用动规解决的问题的特点
1) 问题具有最优子结构性质。如果问题的最优解所包含的 子问题的解也是最优的,我们就称该问题具有最优子结 构性质。
2) 无后效性。当前的若干个状态值一旦确定,则此后过程的演变就只和这若干个状态的值有关,和之前是采取哪种手段或经过哪条路径演变到当前的这若干个状态,没有关系。
————————————————
版权声明:本文为CSDN博主「ChrisYoung1314」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/baidu_28312631/article/details/47418773
爬楼梯问题:
先列出表达式
f(n)=f(n-1)+f(n-2)
到第n阶楼梯只有两种办法,一个是只走一层,一个只走两层,所以分别对应从n-1和n-2来的两种办法
再寻找初始化值:
f(1)= 1;
f(2)=2;
public int climbStairs(int n) {
if(n == 1)
return 1;
//创建数组来存储对应值
int[] dp = new int[n];
//初始化相关值
dp[0] = 1;
dp[1] = 2;
int i =2;
while(i < n){
dp[i] = dp[i-1] + dp[i-2];
i++;
}
return dp[n - 1];
}
这道题为本人考试的时候的一道题
经典采用动态规划
在(i,j)点的路径只跟来它这个地方的两个坐标有关(i-1,j)he(i,j-1)有关
而跟路径无关
来求的公式
f(i,j)=f(i-1,j)+f(i,j-1)
而初始化的值全部都是路边上的点
把路边上的点的值全部变成1
public static int uniquePaths(int m, int n) {
//使用dp来存储
int[][] dp = new int[m][n];
//进行初始化
for (int i = 0; i < m; i++) {
//把路边上的点全部变成1
dp[i][0] = 1;
}
for (int i = 0; i < n; i++) {
dp[0][i] = 1;
}
if (m<=1||n<=1)
return dp[m-1][n-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];
}
这道题和上面一道题类似
因为只能向下或者向右
所以到(i,j)点只有两种情况还是(i-1,j)(i,j-1)
所以到(i,j)点的最短距离就是,(i-1,j)加上这点到(i,j)的距离与(i,j-1)加上这点到(i,j)点的距离这两个距离的最小值
首先还是初始化dp的值
将路边上的点全部初始化
再写出递归公式
f(i,j) = min{f(i-1,j),f(i,j-1)} + grid(i,j)
public static int minPathSum(int[][] grid) {
//创建dp
int[][] dp = new int[grid.length][grid[0].length];
//初始化dp值
dp[0][0] = grid[0][0];
if (grid.length >= 2) {
for (int i = 1; i < grid.length; i++) {
dp[i][0] = dp[i - 1][0] + grid[i][0];
}
}
if (grid[0].length >= 2) {
for (int i = 1; i < grid[0].length; i++) {
dp[0][i] = dp[0][i - 1] + grid[0][i];
}
}
if (grid.length == 1||grid[0].length==1)
return dp[grid.length - 1][grid[0].length - 1];
//根据递归公式求解
for (int i = 1; i < grid.length; i++) {
for (int j = 1; j < grid[0].length; j++) {
if (dp[i-1][j] > dp[i][j-1])
dp[i][j] = dp[i][j-1] + grid[i][j];
else
dp[i][j] = dp[i-1][j] + grid[i][j];
}
}
return dp[grid.length-1][grid[0].length-1];
}
不会先跳过
public static String longestPalindrome(String s) {
/*
状态方程
dp[i][j] = (i == j)&&dp[i+1][j-1]
如果发生i到j相邻的情况,就直接判断,相等就为ture反之相反
实际含义就是查看回文串的时候,先看比自己这个字符串左右各缩小一个之后是否为回文串,如果是就再判断增加之后的两个是否相同,相同就返回true
*/
//先进行dp的初始化,对角线上都为ture
boolean[][] dp = new boolean[s.length()][s.length()];
for (int i = 0; i < s.length(); i++) {
for (int j = 0; j < s.length(); j++) {
if (i == j)
dp[i][j] = true;
else
dp[i][j] = false;
}
}
//内行外列的循环
for (int i = 0; i < s.length(); i++) {
for (int j = 0; j < i; j++) {
//j为前,i为后
//判断i与j的距离
if (i - j <= 1) {
//直接判断这两个是否相等
if (s.charAt(i) == s.charAt(j))
dp[j][i] = true;
else
dp[j][i] = false;
}else {
//大于等于2就进行判断
if (dp[j+1][i-1] == true&&s.charAt(j) == s.charAt(i))
dp[j][i] = true;
else
dp[j][i] = false;
}
}
}
//循环找最大,记录下标
int i1 = 0;
int j1 = 0;
int cnt = 0;
for (int i = 0; i < s.length(); i++) {
for (int j = 0; j <= i; j++) {
if (dp[j][i]){
if (j1 - i1 < i - j) {
j1 = i;
i1 = j;
}
}
}
}
String t = "";
for (int i = i1; i <= j1; i++) {
t += s.charAt(i);
}
return t;
}
dp[i]表示前i天的最大收入
用一个cnt来存储前i天的最小买入点
dp(i)的值由dp(i-1)与cnt共同决定
比较前一天的收入 与 当前这个点与cnt的差 的值,取最大
public int maxProfit(int[] prices) {
int dp[] = new int[prices.length];
int cnt = prices[0];
dp[0] = 0;
for (int i = 1; i < prices.length; i++) {
//如若当前值比cnt还小,并且前一项dp为0,就无法卖出
if (prices[i] <= cnt&&dp[i-1] == 0){
dp[i] = 0;
}else {
//比较前一天的收入 与 当前这个点与cnt的差 的值,取最大
if (prices[i] - cnt > dp[i-1]){
dp[i] = prices[i] - cnt;
} else {
dp[i] = dp[i-1];
}
}
//更新int的值
if (prices[i] < cnt)
cnt = prices[i];
}
return dp[prices.length-1];
}
122. 买卖股票的最佳时机 II - 力扣(LeetCode)
动态算法想着想着,找到了规律,联合股市的涨跌图想象,最赚钱的方法就是,只要一赚钱就卖掉
public static int maxProfit(int[] prices) {
//创建dp数组
int[] dp = new int[prices.length];
dp[0] = 0;
//用end点记录前i个点中最大点的位置
for (int i = 1; i < prices.length; i++) {
if (prices[i] <= prices[i-1]){
//前面比后面大,无法买入卖出
dp[i] = dp[i-1];
}else {
dp[i] = prices[i] - prices[i-1] + dp[i-1];
}
}
return dp[prices.length-1];
}
核心思想用d(i)来表示前i个数字的最大收益
因为考虑到有相邻报警的情况存在,所以第i个数收益得看第i-1个数字有没有取,以及第i-1个数字是多少,还有i-2影响。在i-1个数字取之后,与i-2有关,比较取i-2与第i的和and i-1相比较,看第i个数字取不取。i-1个数字没取,则最好情况就是直接取第i个。
public int rob(int[] nums) {
if (nums.length == 1)
return nums[0];
//创建dp
int [] dp = new int[nums.length];
//创建标志位
int flag = 1;//第一个数字初始化,必取
dp[0] = nums[0];
//为了防止越界,将第二个数也初始化
flag = nums[0] > nums[1] ? 0 : 1;
dp[1] = nums[0] > nums[1] ? nums[0] : nums[1];
for (int i = 2; i < nums.length; i++) {
if (flag == 1){
//前一位数字已经取过了
if (dp[i-1] > dp[i-2] + nums[i]){
//第i位不取最好
dp[i] = dp[i-1];
//更新flag
flag = 0;
}else {
//取第i位
dp[i] = dp[i-2] + nums[i];
flag = 1;
}
}else {
//前一位数字没取,直接加就行
dp[i] = dp[i-1] + nums[i];
flag = 1;
}
}
return dp[nums.length - 1];
}
继续进行优化,实际到i家就两种选择,偷i和不偷i,再将偷i和不偷i进行大小比较,就可,最后还可将其空间变成回滚数组进行优化,就不演示了
public int rob(int[] nums) {
if (nums.length == 1)
return nums[0];
//定义数组
int[] dp = new int[nums.length];
//初始化
dp[0] = nums[0];
dp[1] = nums[1] > nums[0] ? nums[1] : nums[0];
for (int i = 2; i < nums.length; i++) {
//第i家不偷 > 第i家偷
if (dp[i-1] > dp[i-2] + nums[i]){
dp[i] = dp[i-1];
}else {
dp[i] = dp[i-2] + nums[i];
}
}
return dp[nums.length - 1];
}
其实还是两种情况,一个是抢第一家最后一家不抢,一个是抢最后一家第一家不抢,如上进行两次dp运算,即可
public int rob(int[] nums) {
if (nums.length == 1)
return nums[0];
if (nums.length == 2)
return nums[1] > nums[0] ? nums[1] : nums[0];
//定义数组
int[] dp1 = new int[nums.length - 1];
int[] dp2 = new int[nums.length - 1];
//初始化
dp1[0] = nums[0];
dp1[1] = nums[1] > nums[0] ? nums[1] : nums[0];
dp2[0] = nums[1];
dp2[1] = nums[2] > nums[1] ? nums[2] : nums[1];
//dp1进行记录
for (int i = 2; i < nums.length - 1; i++) {
//第i家不偷 > 第i家偷
if (dp1[i-1] > dp1[i-2] + nums[i]){
dp1[i] = dp1[i-1];
}else {
dp1[i] = dp1[i-2] + nums[i];
}
}
//dp2进行记录
for (int i = 2; i < nums.length - 1; i++) {
//第i家不偷 > 第i家偷
if (dp2[i-1] > dp2[i-2] + nums[i+1]){
dp2[i] = dp2[i-1];
}else {
dp2[i] = dp2[i-2] + nums[i+1];
}
}
return dp1[nums.length - 2] > dp2[nums.length - 2] ? dp1[nums.length - 2] : dp2[nums.length - 2];
}
用map去重并计算各个元素的总的大小,然后用上面的算法计算dp
其实还可以进行空间时间优化,就不优化了
public static int deleteAndEarn(int[] nums) {
//用map来接
Map<Integer,Integer> map = new HashMap<>();
for (int num : nums) {
if (map.containsKey(num)){
map.put(num,map.get(num) + num);
}else {
map.put(num,num);
}
}
Object[] tmp = map.keySet().toArray();
Arrays.sort(tmp);
if (tmp.length == 1)
return map.get(tmp[0]);
int[] dp =new int[map.size()];
List<Integer> list = new ArrayList<>();
//初始化前两个
dp[0] = map.get(tmp[0]);
if ((int)tmp[0] == (int)tmp[1] - 1) {
dp[1] = map.get(tmp[0]) > map.get(tmp[1]) ? map.get(tmp[0]) : map.get(tmp[1]);
if (map.get(tmp[0]) > map.get(tmp[1])){
list.add((int)tmp[0]);
list.add((int) tmp[0] + 1);
}else {
list.add((int)tmp[1]);
list.add((int) tmp[1] + 1);
}
}else {
dp[1] = map.get(tmp[1]) + dp[0];
list.add((int)tmp[1] + 1);
}
if (map.get(tmp[0]) > map.get(tmp[1])){
list.add((int)tmp[0]);
list.add((int) tmp[0] + 1);
}else {
list.add((int)tmp[1]);
list.add((int) tmp[1] + 1);
}
for (int i = 2; i < tmp.length; i++) {
if (list.contains((int)tmp[i])){
//包含进行大小判断
if (dp[i-2] + map.get(tmp[i]) > dp[i-1]){
//加入这个数
dp[i] = dp[i-2] + map.get(tmp[i]);
//修改list
list.add((int)tmp[i] + 1);
}else {
//不加入这个数
dp[i] = dp[i-1];
//不修改lsit
}
}else {
//不包含直接加入
dp[i] = dp[i-1] + map.get(tmp[i]);
//修改list
list.add((int)tmp[i] + 1);
}
}
return dp[map.size() - 1];
}
到某一点分为两种点数
一种是到该点前面点剩余的点数,一个是当前点数。用贪心,下一步取这两种点数中最大的点数即可
如果出现剩余点数与自身点数都是零的情况返回flase即可
public static boolean canJump(int[] nums) {
//两种点数
int rest,have;
rest = nums[0];
have = rest;
if (have == 0 && rest == 0 && nums.length > 1)
return false;
for (int i = 1; i < nums.length; i++) {
//剩余点数就是上一轮最大的里面选一个减一
rest = rest > have ? rest : have;
rest--;
//自身点数直接就等于自身
have = nums[i];
if (have == 0 && rest == 0 &&i != nums.length - 1){
//运行到这就是无法进行下去了
return false;
}
}
return true;
}
这道题只要在前面的基础上加上一个flag来记录步数
如果rest点数继承自have就要flag++
如果rest点数继承自rest,就flag不动
上面就是我的错误理解方法,下面为真的
dp(i)表示前i个点能到达的最远距离
也就两种情况这个点到底加不加入所跳的点,如果dp(i-1)比现在所处的点加上这个点的点数大,就不加入,反之则加入
dp(i)=max(i+1+这个点拥有的点数,dp(i-1))
这个算法有点问题,但是改一下就是贪心了这里就不细想了
这里就用一下别人的算法
本题主流解法是利用贪心,每次跳跃到可能跳的最远的位置上,不断求局部最优,最终达到全局最优。
在这里提供一种动态规划的思路:
我们维持一个数组dp,dp[i]的意义是:到达位置i需要的最小跳跃数。
初始状态:dp[0]=0,最开始我们就站在0。其余dp[i]是一个极大的数值,因为初始我们假设跳不到这些位置。
状态更新:假设dp[i]可以跳n下,我们就更新dp[i+1]到dp[i+n],也就是dp[i]可以一次跳跃到达的位置。
根据dp[i]的意义,可以分析出dp[i+j]状态转移的两种状况:
1.到达i+n位置需要的最小跳跃,是由i跳跃过来的,dp[i+j]=dp[i]+1;
2.到达i+n位置需要的最小跳跃,是由i之前的某次跳跃来的,也就是dp[i+j]<dp[i]+1,dp[i+j]维持原状。
这样就得到了状态转移方程,在i+1到i+n的范围内进行更新:
dp[i+j]<dp[i]+1 时:dp[i+j]=dp[i+n];
否则:dp[i+n]=dp[i]+1;
————————————————
版权声明:本文为CSDN博主「Darius吴贺」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Dariuswuhe/article/details/124298010
public static int jump(int[] nums) {
int[] dp = new int[nums.length];
//初始化
for (int i = 0; i < nums.length; i++) {
dp[i] = 100000;
}
dp[0] = 0;
if (nums.length == 1)
return 0;
for (int i = 0; i < nums.length; i++) {
for (int j = i+1; j <= i + nums[i]; j++) {
if (j > nums.length - 1)
//越界
break;
dp[j] = dp[j] < dp[i] + 1 ? dp[j] : dp[i] + 1;
if (dp[nums.length - 1] < 100000)
return dp[nums.length-1];
}
}
return dp[nums.length - 1];
}
https://leetcode.cn/problems/maximum-subarray/solution/dong-tai-gui-hua-fen-zhi-fa-python-dai-ma-java-dai/
附上链接,后效性讲的太牛了
dp(i)为第i个元素为结尾的最大和
递归公式
dp(i)=max(dp(i-1)+nums(i),nums(i))
如果i-1为负数,直接另起炉灶
如果为正数,无脑加
public int maxSubArray(int[] nums) {
//定义dp
int[] dp = new int[nums.length];
//初始化
dp[0] = nums[0];
for (int i = 1; i < nums.length; i++) {
if (dp[i-1] <= 0)
//前面小于等于0,就直接另起炉灶
dp[i] = nums[i];
else
//大于0,就直接加
dp[i] = dp[i-1] + nums[i];
}
//遍历寻找最大元素
int tmp = dp[0];
for (int i = 1; i < nums.length; i++) {
tmp = tmp > dp[i] ? tmp : dp[i];
}
return tmp;
}
最大的环形子数组和 = max(最大子数组和,数组总和-最小子数组和)
直接看答案的,我的思路是将上面的所有情况遍历一遍,也就是每一个元素作为起点遍历找最大情况
答案思路就是两种情况,最大数组没有跨第一个元素循环和跨第一个元素循环两种情况,第二种情况要找最小数组和,用total减去最小数组和就是最大数组和
public static int maxSubarraySumCircular(int[] nums) {
//初始化元素
int first = nums[0];//第一种情况下的循环量
int firstMax = first;//第一种情况下的最大值
int second = nums[0];
int secondMin = second;
int total = 0;
//第一种情况不跨第一个元素找
for (int i = 1; i < nums.length; i++) {
first = Math.max(first+nums[i],nums[i]);
firstMax = Math.max(first,firstMax);
}
//第二种情况下找最小数组和
for (int i = 1; i < nums.length; i++) {
second = Math.min(nums[i], second+nums[i]);
secondMin = Math.min(secondMin,second);
}
//找total
for (int i = 0; i < nums.length; i++) {
total += nums[i];
}
//算出第二种情况最大值
int secondMax = total - secondMin;
//如果相减全部为0就说明这个数组全为负数
if (secondMax == 0)
return firstMax;
return firstMax > secondMax ? firstMax : secondMax;
}
直接看的答案
public int maxProduct(int[] nums) {
int max = Integer.MIN_VALUE, imax = 1, imin = 1;
for(int i=0; i<nums.length; i++){
if(nums[i] < 0){
int tmp = imax;
imax = imin;
imin = tmp;
}
imax = Math.max(imax*nums[i], nums[i]);
imin = Math.min(imin*nums[i], nums[i]);
max = Math.max(max, imax);
}
return max;
}
dp表示前面的点到i点的最大值
这个最大值与前一个dp和这两个values有关
递归公式为:
dp(i) = max(dp[i-1] - values[i-1] + values[i] - 1 , values[i] + values[i-1] - 2)
前面一个是前面的点到该点的值,后面的是前面一个点到该点的值
public static int maxScoreSightseeingPair(int[] values) {
//设置dp
int[] dp = new int[values.length];
dp[1] = values[1] + values[0] - 1;
for (int i = 2; i < values.length; i++) {
if (dp[i-1] - values[i-1] + values[i] - 1 > values[i] + values[i-1] - 2){
dp[i] = dp[i-1] - values[i-1] + values[i] - 1;
}else {
dp[i] = values[i] + values[i-1] - 1;
}
}
int tmp = 0;
for (int i = 0; i < values.length; i++) {
if (tmp < dp[i])
tmp = dp[i];
}
return tmp;
}
122. 买卖股票的最佳时机 II - 力扣(LeetCode)
这里直接用别人解答(74条消息) [Leetcode] 买卖股票合集(动态规划)_leetcode买卖股票_wy_hhxx的博客-CSDN博客
public int maxProfit(int[] prices) {
//设置dp
int[][] dp = new int[prices.length][2];
dp[0][0] = 0;
dp[0][1] = -prices[0];
for (int i = 1; i < prices.length; i++) {
//dp[i][0] 代表第i天收盘后不持有股票的最大收益 --> 前日不持有今日无操作、前日持有今日卖出
//dp[i][1] 代表第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[prices.length-1][0];
}
714. 买卖股票的最佳时机含手续费 - 力扣(LeetCode)
public int maxProfit(int[] prices, int fee) {
//设置dp
int[][] dp = new int[prices.length][2];
dp[0][0] = 0;
dp[0][1] = -prices[0];
for (int i = 1; i < prices.length; i++) {
//dp[i][0] 代表第i天收盘后不持有股票的最大收益 --> 前日不持有今日无操作、前日持有今日卖出
//dp[i][1] 代表第i天收盘后持有股票的最大收益 --> 前日已持有今日无操作 、前日不持有今日买入
//加入小费即可
dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1] + prices[i]-fee);
dp[i][1] = Math.max(dp[i-1][1],dp[i-1][0] - prices[i]);
}
return dp[prices.length-1][0];
}
309. 最佳买卖股票时机含冷冻期 - 力扣(LeetCode)
public int maxProfit(int[] prices) {
int[][] dp = new int[prices.length][3];
dp[0][0] = 0;
dp[0][2] = -prices[0];
dp[0][1] = 0;
for (int i = 1; i < prices.length; i++) {
dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]);
dp[i][2] = Math.max(dp[i-1][2],dp[i-1][0] - prices[i]);
dp[i][1] = dp[i-1][2] + prices[i];
}
return Math.max(dp[prices.length-1][0],dp[prices.length-1][1]);
}
public static boolean wordBreak(String s, List<String> wordDict) {
boolean[] dp = new boolean[s.length()+1];
dp[0] = true;
for (int i = 0; i < s.length(); i++) {
for (int j = i; j >= 0; j--) {
if (wordDict.contains(s.substring(j,i+1))&&dp[j]){
dp[i+1] = true;
break;
}
}
}
return dp[s.length()];
}
第i列接到的雨水的值,为左右两面的最大值减去当前列的值
即
dp(i)= min(left(i),right(i))- height(i)
为了解决当前列为最大值的情况,将left包括自己的最大值,right也包括自己的最大值,这样相减起来就是0
public static int trap(int[] arr) {
// 找出每个arr[i]左边的最大值(包含它自己)
int[] leftMax = new int[ arr.length ];
leftMax[0] = arr[0];
for ( int i=1; i<arr.length; i++ )
leftMax[i] = Math.max( leftMax[i-1], arr[i] );
// 找出每个arr[i]右边的最大值(包含它自己)
int[] rightMax = new int[arr.length];
rightMax[arr.length-1] = arr[arr.length-1];
for ( int i=arr.length-2; i>=0; i-- )
rightMax[i] = Math.max( rightMax[i+1], arr[i] );
// 用max( iLeft, iRight) - arr[i]求出每个arr[i]的最大接水量
int ret = 0;
for ( int i=0; i<arr.length; i++ )
ret += Math.min( leftMax[i], rightMax[i] ) - arr[i];
return ret;
}
用一个数组来存前一个与后一个的差值,如果够三个就加入集合中,最后将集合中的元素都取出来进行总体的数量计算
public static int numberOfArithmeticSlices(int[] nums) {
//创建等差数列组
int[] dp = new int[nums.length-1];
for (int i = 0; i < nums.length-1; i++) {
dp[i] = nums[i+1] - nums[i];
}
//查找等差数组中相等数量超过三个的
List<Integer> list = new ArrayList<>();
int t = 0;
for (int i = 1; i < dp.length; i++) {
if (dp[t] != dp[i]){
if (i-t>=2){
//足够三个就加入集合
list.add(i-t+1);
}
t = i;
}
}
if (dp.length-t>=2){
list.add(dp.length-t+1);
}
System.out.println(list);
int cnt = 0;
if (list.isEmpty())
return 0;
for (Integer integer : list) {
for (int i = integer-3+1; i >= 1; i--) {
cnt += i;
}
}
return cnt;
}
分类讨论,进行状态切换
public static int numDecodings(String s) {
//创建dp,dp(i)表示前i个元素的最多个数
int[] dp = new int[s.length()+1];
dp[0] = 0;
if (s.charAt(0) - '0' != 0)
dp[1] = 1;
else
return 0;
for (int i = 1; i < s.length(); i++) {
if (s.charAt(i) - '0' == 0){
//这一位等于0,就表明不能单独成位,这时判断前面一位是多少,如果大于等于3,就表明不能成立
if (s.charAt(i-1) - '0' >= 3||s.charAt(i-1) - '0' <= 0)
return 0;
else {
if (i-2 <= 0)
dp[i+1] = 1;
else
dp[i+1] = dp[i-1];
}
}else{
//判断是不是7到9,,如果前一位为1开头才行
if (s.charAt(i) - '0' >= 7) {
if (s.charAt(i-1) - '0' == 1) {
if (i-1 == 0)
dp[i + 1] = dp[i] + 1;
else
dp[i + 1] = dp[i] + dp[i - 1];
}else {
dp[i+1] = dp[i];
}
} else {
//如果小于6,则直接判断前面一位是不是大于0小于3
if (s.charAt(i-1) - '0' > 0 && s.charAt(i-1) - '0' < 3) {
if (i-1 == 0)
dp[i + 1] = dp[i] + 1;
else
dp[i + 1] = dp[i] + dp[i - 1];
} else
dp[i+1] = dp[i];
}
}
}
return dp[s.length()];
}
96. 不同的二叉搜索树 - 力扣(LeetCode)、用卡特兰数
public static int numTrees(int n) {
int[] dp = new int[n + 1];
dp[0] = 1;
dp[1] = 1;
for(int i = 2; i < n + 1; i++)
for(int j = 1; j < i + 1; j++)
dp[i] += dp[j-1] * dp[i-j];
return dp[n];
}
简单题
public static int minFallingPathSum(int[][] matrix) {
int[][] dp = new int[matrix.length][matrix[0].length];
for (int i = 0; i < matrix[0].length; i++) {
dp[0][i] = matrix[0][i];
}
for (int i = 1; i < matrix.length; i++) {
for (int j = 0; j < matrix[0].length; j++) {
if (j-1 < 0){
//左面越界
dp[i][j] = Math.min(dp[i-1][j] + matrix[i][j],dp[i-1][j+1]+ matrix[i][j]);
}else if (j+1 >= matrix[0].length){
//右面越界
dp[i][j] = Math.min(dp[i-1][j]+ matrix[i][j],dp[i-1][j-1]+ matrix[i][j]);
}else {
dp[i][j] = Math.min(Math.min(dp[i-1][j]+ matrix[i][j],dp[i-1][j-1]+ matrix[i][j]),dp[i-1][j+1]+ matrix[i][j]);
}
}
}
int tmp = dp[matrix.length-1][0];
for (int i = 0; i < dp[0].length; i++) {
if (dp[matrix.length-1][i] < tmp)
tmp = dp[matrix.length-1][i];
}
return tmp;
}
简单题
public static int minimumTotal(List<List<Integer>> triangle) {
int[][] dp = new int[triangle.size()][triangle.get(triangle.size()-1).size()];
dp[0][0] = triangle.get(0).get(0);
for (int i = 1; i < triangle.size(); i++) {
for (int j = 0; j < triangle.get(i).size(); j++) {
if (j-1 < 0){
//左面越界
dp[i][j] = dp[i-1][j] + triangle.get(i).get(j);
}else if (j+1 >= triangle.get(i).size()){
//右面越界
dp[i][j] = dp[i-1][j-1] + triangle.get(i).get(j);
}else {
dp[i][j] = Math.min(dp[i-1][j-1] + triangle.get(i).get(j),dp[i-1][j] + triangle.get(i).get(j));
}
}
}
int tmp = dp[triangle.size()-1][0];
for (int i = 1; i < dp[0].length; i++) {
if (tmp > dp[dp.length-1][i])
tmp = dp[dp.length-1][i];
}
return tmp;
}
直接用答案
动态规划|二维前缀和算法 + 图解 - 矩阵区域和 - 力扣(LeetCode)
public static int[][] matrixBlockSum(int[][] mat, int k) {
//设置dp数组,dp(i)(j)表示以dp[i][j] 表示数组 mat 中以 (0, 0) 为左上角,(i, j) 为右下角的矩形子数组的元素之和。
int[][] dp = new int[mat.length][mat[0].length];
//设置初始值
dp[0][0] = mat[0][0];
for (int i = 1; i < mat.length; i++) {
dp[i][0] = dp[i-1][0] + mat[i][0];
}
for (int i = 1; i < mat[0].length; i++) {
dp[0][i] = dp[0][i-1] + mat[0][i];
}
//开始循环得值
for (int i = 1; i < mat.length; i++) {
for (int j = 1; j < mat[0].length; j++) {
dp[i][j] = dp[i][j-1] + dp[i-1][j] - dp[i-1][j-1] + mat[i][j];
}
}
//设置得到的数组
int[][] answer = new int[mat.length][mat[0].length];
//开始
int x1,x2,y1,y2;
for (int i = 0; i < mat.length; i++) {
for (int j = 0; j < mat[0].length; j++) {
//进行越界处理
if (i-k < 0) x1 = 0;
else x1 = i-k;
if (j-k < 0) y1 = 0;
else y1 = j-k;
if (i+k >= mat.length) x2 = mat.length-1;
else x2 = i + k;
if (j+k >= mat[0].length) y2 = mat[0].length-1;
else y2 = j + k;
//两个都越界
if (x1-1 < 0&&y1-1 < 0)
answer[i][j] = dp[x2][y2];
else if (x1-1 < 0)
//越界一个
answer[i][j] = dp[x2][y2] - dp[x2][y1 - 1];
else if (y1 - 1 < 0)
//越界一个
answer[i][j] = dp[x2][y2] - dp[x1-1][y2];
else
//不越界
answer[i][j] = dp[x2][y2] - dp[x2][y1-1] - dp[x1-1][y2] + dp[x1-1][y1-1];
}
}
return answer;
}
304. 二维区域和检索 - 矩阵不可变 - 力扣(LeetCode)
跟上面一道题一样
public class NumMatrix {
int[][] s;
int[][] dp;
public NumMatrix(int[][] matrix) {
this.s = matrix;
{
//计算前缀和
int[][] dp = new int[s.length][s[0].length];
dp[0][0] = matrix[0][0];
for (int i = 1; i < s.length; i++) {
dp[i][0] = dp[i-1][0] + s[i][0];
}
for (int i = 1; i < s[0].length; i++) {
dp[0][i] = dp[0][i-1] + s[0][i];
}
//开始循环得值
for (int i = 1; i < s.length; i++) {
for (int j = 1; j < s[0].length; j++) {
dp[i][j] = dp[i][j-1] + dp[i-1][j] - dp[i-1][j-1] + s[i][j];
}
}
this.dp = dp;
}
}
public int sumRegion(int row1, int col1, int row2, int col2) {
//两个都越界
if (row1-1 < 0&&col1-1 < 0)
return dp[row2][col2];
else if (row1-1 < 0)
//越界一个
return dp[row2][col2] - dp[row2][col1 - 1];
else if (col1 - 1 < 0)
//越界一个
return dp[row2][col2] - dp[row1-1][col2];
else
//不越界
return dp[row2][col2] - dp[row2][col1-1] - dp[row1-1][col2] + dp[row1-1][col1-1];
}
}
简单题
看注释吧
public static int uniquePathsWithObstacles(int[][] obstacleGrid) {
//设置dp,dp[i][j]表示到ij点有多少路径
int[][] dp = new int[obstacleGrid.length][obstacleGrid[0].length];
//将边路上的dp全部设置成1
for (int i = 0; i < obstacleGrid.length; i++) {
//如果该点不为障碍物才设置为1,否则设置为0,后面的也设置为0
if (obstacleGrid[i][0] == 1){
for (int j = i; j < obstacleGrid.length; j++) {
dp[i][0] = 0;
}
break;
}else
dp[i][0] = 1;
}
for (int i = 0; i < obstacleGrid[0].length; i++) {
//如果该点不为障碍物才设置为1,否则设置为0,后面的也设置为0
if (obstacleGrid[0][i] == 1){
for (int j = i; j < obstacleGrid[0].length; j++) {
dp[0][i] = 0;
}
break;
}else
dp[0][i] = 1;
}
//开始动态规划
for (int i = 1; i < obstacleGrid.length; i++) {
for (int j = 1; j <obstacleGrid[0].length ; j++) {
//先判断这个点是不是障碍物,如果是就设置为0,如果不是就加上左面和上面的
if (obstacleGrid[i][j] == 1){
dp[i][j] = 0;
}else {
dp[i][j] = dp[i-1][j] + dp[i][j-1];
}
}
}
return dp[obstacleGrid.length-1][obstacleGrid[0].length-1];
}
dp(i,j)=min(dp(i−1,j),dp(i−1,j−1),dp(i,j−1))+1
这个东西比较难推出
代码就简单了
public static int maximalSquare(char[][] matrix) {
//设置dp,dp[i][j]表示到ij点为右下角元素的最大正方形的面积
int a = matrix.length;
int b = matrix[0].length;
int[][] dp = new int[a][b];
//设置边界值
for (int i = 0; i < a; i++) {
dp[i][0] = matrix[i][0] - '0';
}
for (int i = 0; i < b; i++) {
dp[0][i] = matrix[0][i] - '0';
}
//开始动态规划
for (int i = 1; i < a; i++) {
for (int j = 1; j < b; j++) {
//判断自身是不是1
if (matrix[i][j] == '1'){
dp[i][j] = Math.min(Math.min(dp[i-1][j],dp[i][j-1]),dp[i-1][j-1]) + 1;
}else dp[i][j] = 0;
}
}
int tmp = 0;
for (int i = 0; i < a; i++) {
for (int j = 0; j < b; j++) {
if (tmp < dp[i][j])
tmp = dp[i][j];
}
}
return tmp * tmp;
}
剑指 Offer II 103. 最少的硬币数目 - 力扣(LeetCode)
class Solution {
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount + 1];
//dp,i表示i元的最少硬币,为-1则表示不可
dp[0] = 0;
for (int i = 1; i <= amount; i++) {
int tmp = -1;
for (int j = 0; j < coins.length; j++) {
int cnt = -1;
if (i-coins[j] < 0){
//不满足
cnt = -1;
}else {
if (dp[i-coins[j]] < 0){
//也不满足
cnt = -1;
}else {
cnt = dp[i-coins[j]] + 1;
}
}
if (tmp == -1){
tmp = cnt;
}else {
if (cnt != -1){
if (tmp > cnt)
tmp = cnt;
}
}
}
dp[i] = tmp;
}
return dp[amount];
}
}
1043. 分隔数组以得到最大和 - 力扣(LeetCode)
用别人的解法