动态规划初步

}

2.2 爬楼梯

70. 爬楼梯

dp:

  1. 爬到第i层楼梯,有dp[i]种方法

  2. dp[i] = dp[i-1] + dp[i-2] 从i-1上来和从i-2上来

  3. dp[1] = 1,dp[2] = 2

  4. 从左往右

  5. image-20211114203042738

class Solution {

public int climbStairs(int n) {

if(n <=2) return n;

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];

}

}

2.3 使用最小花费爬楼梯

746. 使用最小花费爬楼梯

dp:

  1. 爬到第i层,最小花费

  2. dp[i] = min(dp[i-1],dp[i-2]) + cost[i]

  3. dp[0] = cost[0],dp[1] = cost[1]

  4. 从左往右

  5. image-20211114204346293

class Solution {

public int minCostClimbingStairs(int[] cost) {

int n = cost.length;

int[] dp = new int[n+1];

dp[0] = cost[0];

dp[1] = cost[1];

for(int i = 2;i < n;i++) dp[i] = Math.min(dp[i-1],dp[i-2]) + cost[i];

return Math.min(dp[n-1],dp[n-2]);

}

}

2.4 不同路径

62. 不同路径

dp:

  1. dp[i] [j],从起点到 (i,j) 有多少种方案

  2. dp[i] [j] = dp[i-1] [j] + dp[i] [j-1]

  3. dp[0] [0] = 0 dp[01] = 1 dp[1 0] = 1

  4. 从左上到右下

  5. image-20211114210351356

class Solution {

public int uniquePaths(int m, int n) {

// dp[i][j] = dp[i-1][j]+dp[i][j-1]

int[][] dp = new int[m][n];

dp[0][0] = 0;

// 最左一列

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];

}

}

爆搜:

class Solution {

public int uniquePaths(int m, int n) {

return dfs(1,1,m,n);

}

private int dfs(int i,int j,int m,int n){

if(i == m && j == n) return 1;

// 越界

else if(i > m || j > n) return 0;

return dfs(i+1,j,m,n) + dfs(i,j+1,m,n);

}

}

数论:

63. 不同路径 II

dp:

  1. dp[i] [j] 从起点到(i,j)有多少种方案

  2. if 不是障碍物 dp[i] [j] = dp[i] [j-1] + dp[i-1] [j]

  3. dp[0] [0] = 0 出现障碍物前 dp[i,0] = 1,dp[0,j] = 1,之后为0

  4. 从左上到右下,即一行一行的遍历,因为只会用到自己上面一行和左边的数字

  5. [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MtI6QvCe-1637219477435)(https://i.loli.net/2021/11/15/RDY8QkEIqC2gzSB.png)]

class Solution {

public int uniquePathsWithObstacles(int[][] obstacleGrid) {

int n = obstacleGrid.length;int m = obstacleGrid[0].length;

int[][] dp = new int[n][m];

// dp[i][j] = dp[i-1][j] + dp[i][j-1]

// 初始化

// 最左一列和最右一行有一个出现了障碍物,那么之后的都为0

for(int i = 0;i < n && obstacleGrid[i][0] != 1;i++) dp[i][0] = 1;

for(int j = 0;j < m && obstacleGrid[0][j] != 1;j++) dp[0][j] = 1;

// 当前不是障碍物就正常处理,否则为0

for(int i = 1;i < n;i++){

for(int j = 1;j < m;j++){

if(obstacleGrid[i][j] != 1) dp[i][j] = dp[i-1][j] + dp[i][j-1];

}

}

return dp[n-1][m-1];

}

}

343.整数拆分

343. 整数拆分

dp:

  1. dp[i],将数值i拆分,得到的最大乘积

  2. dp[i] = max( dp[i] ,max( dp[j-i] * j , j * ( j - i ) ))

  3. dp[2] = 1

  4. 从左往右

  5. image-20211115163648955

class Solution {

public int integerBreak(int n) {

// dp[i] 拆分数字i,可以得到最大乘机为dp[i]

// j*(i-j) jdp[i-j] j(i-j) 是将整数拆分成两个数 j*dp[i-j]是拆分成两个及两个以上

// dp[i] = max(dp[i],max(jdp[i-j],j(i-j)))

// dp[2] = 1

int[] dp = new int[n+1];

dp[2] = 1;

for(int i = 3;i <= n;i++){

for(int j = 1;j < i ;j++){

dp[i] = Math.max(dp[i],Math.max(jdp[i-j],j(i-j)));

}

}

return dp[n];

}

}

96.不同的二叉搜索树

96. 不同的二叉搜索树

class Solution {

public int numTrees(int n) {

// dp[i] 有i个节点组成,且1到i互不相同的二叉搜索树的个数

// dp[i] += dp[j-1]*dp[i-j]

// dp[3] = 元素1为头结点搜索树的数量 + 元素2为头结点搜索树的数量 + 元素3为头结点搜索树的数量

// 元素1为头结点搜索树的数量 = 左子树0个元素,右子树2个元素

// 元素2为头结点搜索树的数量 = 左子树1个元素,右子树1个元素

// 元素3为头结点搜索树的数量 = 左子树2个元素,右子树0个元素 左小右大

// dp[2] = 2个节点组成,且互补相同

// dp[1] = 1个节点组成,且互不相同

// dp[3] = dp[0]*dp[2] + dp[1]*dp[1] + dp[2]*dp[0]

int[] dp = new int[n+1];

dp[0] = 1;

dp[1] = 1;

for(int i = 2;i <= n;i++){

// 第i个节点需要考虑从1~i作为根节点的情况

// 一共i个节点,j为首节点,左边有j-1个节点,右边有i-j个节点

for(int j = 1;j <= i;j++) dp[i] += dp[j-1]*dp[i-j];

}

return dp[n];

}

}

0-1背包问题

​ 有N件物品和一个最多能背重量为W 的背包。第i件物品的重量是weight[i],得到的价值是value[i]每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ygU60Oyw-1637219477437)(https://i.loli.net/2021/11/16/DCxmIl3oZdbTQt5.jpg)]

0-1背包问题

二维:

  1. dp[i j] 从0~i 中任选、背包容量为j,最大价值

  2. dp[i j] = max(dp[ i-1 , j ],dp[i - 1 , j - weight[i] ] + value[i])

  3. dp[i 0] 容量为0的都为0,还有最上面一行,能放下第一个物品后的价值都为value[1]

  4. 由左上推出右下

一维:

  1. dp[j] 容量为 j ,最大价值

  2. dp[ j ] = max(dp[ j ] , dp[ j - weight[i] ] + value[i])

  3. dp[0] = 0

  4. 滚动数组,右边的从上一伦的左边推出来

#include <bits/stdc++.h>

using namespace std;

const int N = 1e3 + 10;

// 重量、价值

int weight[N],value[N];

// 个数,容量

int n,bag;

void m1();

void m2();

int main(){

scanf(“%d%d”,&n,&bag);

for(int i = 0;i < n;i++) scanf(“%d%d”,&weight[i],&value[i]);

// m1();

m2();

}

void m2(){

// dp[j] 容量为j的背包,所背的物品价值可以最大为dp[j]。

vector dp(bag+1,0);

// dp[j] = max(dp[j],dp[j-weight[i]]+value[i])

// dp[j] 不取物品 dp[j-weight[i]+value[i] 取第i件物品

// 初始化

// dp[0] = 0

dp[0] = 0;

for(int i = 0;i < n;i++){

// 为什么是倒叙?

// 因为一维dp是滚动数组,其实就是将i-1行复制到第i行

// 第i行依赖于第i-1行的正上方或者是左上方,如果

// 左边更新了,会影响到右边

// 对于每次新的一行,更新右边的值,其实是用上一行的左边的值

// j 要大于 weight[i] 要不然就是背包容量小于 物品的重量了

for(int j = bag;j >= weight[i];j–) dp[j] = max(dp[j],dp[j-weight[i]] + value[i]);

}

cout << dp[bag] << endl;

// for(int i = 0;i <= bag;i++) cout << dp[i] << " ";

}

void m1(){

// dp[i][j] 在0~i件物品中任选,背包容量为j,dp[i][j]是最大价值

// dp[i][j] = max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i])

// dp[i-1][j]表示不要第i件物品,dp[i-1][j-weight[j]]+value[i]表示要第i件物品

vector<vector> dp(n,vector(bag+1,0));

dp[0][0] = 0;

// 最右边一列是0

// for(int i = 0;i < n;i++) dp[i][0] = 0;

// 最上面一列只有能装下第一件物品才有价值

for(int j = weight[0];j <= bag;j++) dp[0][j] = value[0];

for(int i = 1;i < n;i++){ // 遍历物品,第0件不用遍历了

for(int j = 0;j <= bag;j++){

if(j < weight[i]) dp[i][j] = dp[i-1][j];

else dp[i][j] = max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i]);

}

}

cout << dp[n-1][bag] << endl;

}

416. 分割等和子集
  1. dp[i] 容量为i,能放下的最大价值为dp[i]

  2. dp[ i ] = max(dp[i],dp[i-weight[i]]+value[i])

  3. dp[0] = 0

  4. 滚动数组,右边的从上一伦的左边推出来

class Solution {

public boolean canPartition(int[] nums) {

// 背包体积为sum/2

// 背包的商品的重量是商品的数值、价值也是元素的数值

// 可以分割表示背包刚好装满

// dp[i] 容量为i,最大凑成的i的子集

// dp[i] = max(dp[i],dp[i-nums[i]]+nums[i])

int n = nums.length,target;

int sum = 0;

for(int i = 0;i < n;i++) sum += nums[i];

// sum不是偶数,凑不出来

if(sum % 2 != 0) return false;

target = sum / 2;

// 容量为target

int[] dp = new int[target+1];

dp[0] = 0;

for(int i = 0;i < n;i++){

for(int j = target;j >= nums[i];j–) dp[j] = Math.max(dp[j],dp[j-nums[i]]+nums[i]);

}

// 背包容量是sum/2,并且最大能装sum/2

// dp[i] <= i 永远成立

// i是容量 dp[i]是容量为i的时候,能装进去的最大值

// 物品的重量 = 物品的质量 = 物品的数值

return dp[target] == target;

}

}

第二次写总结:

class Solution {

public boolean canPartition(int[] nums) {

int sum = 0,n = nums.length,target;

for(int i = 0;i < n;i++) sum += nums[i];

if(sum % 2 != 0) return false;

else target = sum / 2;

// 现在就是找nums中能否组合出一个和为target的组合

// 并且每个元素只能用一次

// dp[j] 容量为j,凑成最大的字节和

// dp[j] = max(dp[j],dp[j-nums[i]]+nums[i])

// 很类似以0-1背包问题,就是一堆物品,一个容量,看最多能装的多大价值的东西

// 只是这里的重量和价值是一样的而已

int[] dp = new int[target+1];

for(int i = 0;i < n;i++){ // 遍历物品

for(int j = target;j >= nums[i];j–) dp[j] = Math.max(dp[j],dp[j-nums[i]]+nums[i]);

}

// dp[target] 容量为 target,看最大能不能凑出 target

// 不能的话就没希望了,因为dp[j] <= j

return dp[target] == target;

}

}

1049. 最后一块石头的重量 II

class Solution {

public int lastStoneWeightII(int[] stones) {

// 本题其实就是将石头分成两份最接近的,然后剩下的就是答案

// dp[i] 容量为i,最多能装dp[i]重量的石头

// 石头共重sum,那么一半是target

int n = stones.length,sum = 0,target;

for(int i = 0;i < n;i++) sum += stones[i];

target = sum / 2;

// target是sum/2向下取整

// 背包容量是石头总重的一半,看最大能装多重

int[] dp = new int[target+1];

dp[0] = 0;

for(int i = 0;i < n;i++){

for(int j = target;j >= stones[i];j–) dp[j] = Math.max(dp[j],dp[j-stones[i]]+stones[i]);

}

// 此时一堆石头是 dp[target],另外一堆是 sum - dp[targer]

// dp[target] <= target 所以 sum - dp[targer] >= dp[target]

return sum - dp[target] - dp[target];

}

}

494. 目标和

class Solution {

public int findTargetSumWays(int[] nums, int target) {

// nums 可以分为两部分、一部分做正数 a、一部分做负数 b(b还是一个正数,前面整体加一个符号就是负数了)

// target = a - b

// sum = a + b -> b = sum - a

// target = a - sum + a -> a = (target + sum ) / 2;

// a 是 nums中的一部分数之和

// 能在 nums中找到a,那么b自然就能找到

// 使用0-1背包来思考,dp[a],填满a这么大容积的背包,有dp[a]种方法

int n = nums.length, sum = 0;

for(int i = 0;i < n;i++) sum += nums[i];

// dp[i] 填满i这么大容积的背包、由dp[i]种方法

// dp[j] += dp[j-num[i]]

// 搞满容量为3的背包,有dp[3]种方法

// 如果 num[i] = 2,那么至少dp[5]有dp[3]种

// dp[0] = 1 装满0,只有一种,装0

// target比全是正数还大

if(Math.abs(target) > sum) return 0;

// 按照上面的推倒、a应该是偶数

if((target + sum) % 2 == 1) return 0;

int a = (target + sum) / 2;

int[] dp = new int[a+1];

for(int i = 0;i < n;i++){

for(int j = a;j >= nums[i];j–) dp[j] += dp[j-nums[i]];

}

return dp[a];

// 在求装满背包有几种方法的情况下,递推公式一般为:

// dp[j] += dp[j - nums[i]];

}

}

我感觉这几题都在做一件事:从一个序列里面找和为target的、可能是能否为target、可能是为target有多少种。

474. 一和零

重量有两个维度的0-1背包问题

class Solution {

public int findMaxForm(String[] strs, int m, int n) {

// dp[i][j] 最多有i个0,j个1的strs的最大子集的大小为dp[i][j]

// dp[i][j] = max(dp[i][j],dp[i-zeroNum][j-oneNum] + 1);

// dp[0][0] = 0

// 这个其实不是二维dp,是物品的重量有两个维度,[i][j]一起构成j

// 字符串的zeroNum和oneNum相当于物品的重量(weight[i]),字符串本身的个数相当于物品的价值(value[i])

int[][] dp = new int[m+1][n+1];

for(String str : strs){ // 遍历物品

int zeroNum = 0,oneNum = 0;

for(int i = 0;i < str.length();i++){

if(str.charAt(i) == ‘0’) zeroNum++;

else oneNum++;

}

for(int i = m;i >= zeroNum;i–){

for(int j = n;j >= oneNum;j–) dp[i][j] = Math.max(dp[i][j],dp[i-zeroNum][j-oneNum]+1);

}

}

return dp[m][n];

}

}

完全背包问题

有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品都有无限个(也就是可以放入背包多次),求解将哪些物品装入背包里物品价值总和最大。

完全背包和01背包问题唯一不同的地方就是,每种物品有无限件

01背包

for(int i = 0;i < n;i++){ // 遍历商品个数

// 反向遍历

for(int j = bag;j >= weight[i];j–) dp[j] = max(dp[j],dp[j-weight[i]]+value[i]);

}

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

面试是跳槽涨薪最直接有效的方式,马上金九银十来了,各位做好面试造飞机,工作拧螺丝的准备了吗?

掌握了这些知识点,面试时在候选人中又可以夺目不少,暴击9999点。机会都是留给有准备的人,只有充足的准备,才可能让自己可以在候选人中脱颖而出。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。[外链图片转存中…(img-Jl64yxLF-1713551169416)]

[外链图片转存中…(img-Xatlbj9M-1713551169417)]

[外链图片转存中…(img-SRIZhcgA-1713551169417)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

面试是跳槽涨薪最直接有效的方式,马上金九银十来了,各位做好面试造飞机,工作拧螺丝的准备了吗?

掌握了这些知识点,面试时在候选人中又可以夺目不少,暴击9999点。机会都是留给有准备的人,只有充足的准备,才可能让自己可以在候选人中脱颖而出。

[外链图片转存中…(img-Qhkmxwd5-1713551169417)]

[外链图片转存中…(img-99KHihn9-1713551169418)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 9
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值