labuladong 的算法小抄
代码随想录
62.不同路径
代码
class Solution {
/**
* 1. 确定dp数组下表含义 dp[i][j] 到每一个坐标可能的路径种类
* 2. 递推公式 dp[i][j] = dp[i-1][j] dp[i][j-1]
* 3. 初始化 dp[i][0]=1 dp[0][i]=1 初始化横竖就可
* 4. 遍历顺序 一行一行遍历
* 5. 推导结果 。。。。。。。。
*
* @param m
* @param n
* @return
*/
public static 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 i = 0; i < n; i++) {
dp[0][i] = 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];
}
}
63. 不同路径 II
代码
class Solution {
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
if(obstacleGrid.length<0){
return 0;
}
int n = obstacleGrid.length;
int m = obstacleGrid[0].length;
int[][] dp = new int[n][m];
// 初始化
for(int i=0;i<n;i++){
if(obstacleGrid[i][0]==1){
break;
}
dp[i][0]=1;
}
for(int j=0;j<m;j++){
if(obstacleGrid[0][j]==1){
break;
}
dp[0][j]=1;
}
for(int i=1;i<n;i++){
for(int j=1;j<m;j++){
if(obstacleGrid[i][j]==0){
dp[i][j]=dp[i-1][j]+dp[i][j-1];
}
}
}
return dp[n-1][m-1];
}
}
希望用一种规律搞定背包问题
解题思路
常见的背包问题有1、组合问题。2、True、False问题。3、最大最小问题。
以下题目整理来自大神CyC,github地址:
github
我在大神整理的基础上,又做了细分的整理。分为三类。
1、组合问题:
377. 组合总和 Ⅳ
494. 目标和
518. 零钱兑换 II
2、True、False问题:
139. 单词拆分
416. 分割等和子集
3、最大最小问题:
474. 一和零
322. 零钱兑换
组合问题公式
dp[i] += dp[i-num]
True、False问题公式
dp[i] = dp[i] or dp[i-num]
最大最小问题公式
dp[i] = min(dp[i], dp[i-num]+1)或者dp[i] = max(dp[i], dp[i-num]+1)
以上三组公式是解决对应问题的核心公式。
作者:Jackie1995
链接:https://leetcode-cn.com/problems/combination-sum-iv/solution/xi-wang-yong-yi-chong-gui-lu-gao-ding-bei-bao-wen-/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
背包问题,大家都知道,有N件物品和一个最多能被重量为W 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。
状态:
一般公式都是:dp[j] = Math.max(dp[j],dp[j - nums[i]]);
求装满背包有几种方法,一般公式都是:dp[j] += dp[j - nums[i]];
遍历顺序,
一维数组:
先遍历物品,再遍历容量
二维数组:
都可以
for(int i = 0; i < weight.size(); i++) { // 遍历物品
for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
}
}
这里遍历容量的时候是从大到小遍历,因为从小到大遍历使用了覆盖后的元素进行计算,导致结果不准备,或者说是物品被放入了两次。
416. 分割等和子集
给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
示例 1:
输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。
示例 2:
输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。
思路
状态:
代码
class Solution {
/**
* 背包的容量跟可选择的物品就是状态
*/
boolean canPartition(int[] nums) {
int sum=0;
for(int num:nums){
sum += num;
}
if(sum%2!=0){
return false;
}
int n = nums.length;
sum = sum/2;
boolean[][] dp = new boolean[n+1][sum+1];
for(int i=0;i<=n;i++){
dp[i][0]=true;
}
for(int i=1;i<=n;i++){
for(int j=0;j<=sum;j++){
if(j-nums[i-1]>=0){
dp[i][j]= dp[i-1][j] || dp[i-1][j-nums[i-1]];
}else{
dp[i][j]= dp[i-1][j];
}
}
}
return dp[n][sum];
}
}
1049. 最后一块石头的重量 II
转换为01背包问题
class Solution {
public int lastStoneWeightII(int[] stones) {
int n = stones.length;
int sum =0 ;
for(int stone:stones){
sum += stone;
}
int target = sum / 2; // 将石头分成两堆
int[][] dp = new int[n+1][target+1];
for(int i=1;i<=n;i++){
for(int j=0;j<=target;j++){
if(j-stones[i-1]>=0){
dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-stones[i-1]]+stones[i-1]);
}else{
dp[i][j] = dp[i-1][j];
}
}
}
return sum - dp[n][target]-dp[n][target];
}
}
一位数组
class Solution {
public int lastStoneWeightII(int[] stones) {
int n = stones.length;
int sum =0 ;
for(int stone:stones){
sum += stone;
}
int target = sum / 2;
int[] dp = new int[target+1];
for(int i=1;i<=n;i++){
for(int j=target;j>=stones[i-1];j--){// 每一个元素一定是不可重复放入,所以从大到小遍历 物品 i 的重量是 nums[i],其价值也是 nums[i]
dp[j]=Math.max(dp[j],dp[j-stones[i-1]]+stones[i-1]);
}
}
return sum - dp[target]-dp[target];
}
}
494. 目标和
给你一个整数数组 nums 和一个整数 target 。
向数组中的每个整数前添加 ‘+’ 或 ‘-’ ,然后串联起所有整数,可以构造一个 表达式 :
例如,nums = [2, 1] ,可以在 2 之前添加 ‘+’ ,在 1 之前添加 ‘-’ ,然后串联起来得到表达式 “+2-1” 。
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目
思路
代码
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int n = nums.length;
int sum = 0;
for(int num:nums){
sum += num;
}
if((sum+target)%2!=0){
return 0;
}
target = (sum+target)/2;
if(target<0){
target= -target;
}
int[][] dp = new int[n+1][target+1];
for(int i=0;i<=n;i++){
dp[i][0]=1;
}
for(int i=1;i<=n;i++){
for(int j=0;j<=target;j++){
if(j-nums[i-1]>=0){
dp[i][j] = dp[i-1][j]+ dp[i-1][j-nums[i-1]];
}else{
dp[i][j] =dp[i-1][j];
}
}
}
return dp[n][target];
}
}
二维数组
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int sum = 0;
for (int i = 0; i < nums.length; i++) sum += nums[i];
if ((target + sum) % 2 != 0) return 0;
int size = (target + sum) / 2;
if(size < 0) size = -size;
int[] dp = new int[size + 1];
dp[0] = 1;
for (int i = 0; i < nums.length; i++) {
for (int j = size; j >= nums[i]; j--) {
dp[j] += dp[j - nums[i]];
}
}
return dp[size];
}
}
474. 一和零
给你一个二进制字符串数组 strs 和两个整数 m 和 n 。
请你找出并返回 strs 的最大子集的长度,该子集中 最多 有 m 个 0 和 n 个 1 。
如果 x 的所有元素也是 y 的元素,集合 x 是集合 y 的 子集 。
思路
物品:字符串
背包容量:0和1的数量
代码
class Solution {
public int findMaxForm(String[] strs, int m, int n) {
//dp[i][j]表示i个0和j个1时的最大子集
int[][] dp = new int[m + 1][n + 1];
int oneNum, zeroNum;
for (String str : strs) {
oneNum = 0;
zeroNum = 0;
for (char ch : str.toCharArray()) {
if (ch == '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];
}
}
遍历顺序考究。
而完全背包的物品是可以添加多次的,所以要从小到大去遍历。
纯背包问题,遍历顺序可以调换。
// 先遍历物品,再遍历背包
for(int i = 0; i < weight.size(); i++) { // 遍历物品
for(int j = weight[i]; j < bagWeight ; j++) { // 遍历背包容量
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
}
}
518. 零钱兑换 II
给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。
请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。
假设每一种面额的硬币有无限个。
题目数据保证结果符合 32 位带符号整数。
思路
完全背包问题:每个物品都可以无限选取。
遍历顺序:
代码
class Solution {
public int change(int amount, int[] coins) {
int n = coins.length;
int[][] dp = new int[n + 1][amount + 1];
for (int i = 0; i <= n; i++) {
dp[i][0] = 1; // base case:无为而治
}
for (int i = 1; i < n + 1; i++) {
for (int j = 0; j < amount + 1; j++) {
if (j - coins[i - 1] >= 0) {
dp[i][j] = dp[i - 1][j] + dp[i][j - coins[i - 1]];// 注意不是dp[i][j] = dp[i - 1][j] + dp[i-1][j - coins[i - 1]] ,区别于0,1背包
} else {
dp[i][j] = dp[i - 1][j] ;
}
}
}
return dp[n][amount];
}
}
class Solution {
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 + 1; j++) { // 枚举金额,从小到大进行遍历。表示可以取无限次数
if (j >= coin) {
dp[j] += dp[j - coin];
}
}
}
return dp[amount];
}
}
377. 组合总和 Ⅳ
给你一个由 不同 整数组成的数组 nums ,和一个目标整数 target 。请你从 nums 中找出并返回总和为 target 的元素组合的个数。
题目数据保证答案符合 32 位整数范围。
参考资料
思路
本题与爬楼梯问题类似。
完全背包问题,排列问题。
遍历顺序:
也可以用普通的动态规划去解释。
代码
class Solution {
public int combinationSum4(int[] nums, int target) {
int n = nums.length;
int[] dp = new int[target+1];
dp[0]=1;
for (int i = 0; i <= target; i++) {
for (int j = 0; j < nums.length; j++) {
if (i >= nums[j]) {
dp[i] += dp[i - nums[j]];
}
}
}
return dp[target];
}
}