leecode更新
This commit is contained in:
parent
c732207ff8
commit
a01757b6e0
|
|
@ -0,0 +1,61 @@
|
|||
package com.markilue.leecode.dynamic;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* @BelongsProject: Leecode
|
||||
* @BelongsPackage: com.markilue.leecode.dynamic
|
||||
* @Author: markilue
|
||||
* @CreateTime: 2022-12-06 10:45
|
||||
* @Description:
|
||||
* TODO 力扣377题 组合总和IV:
|
||||
* 给你一个由 不同 整数组成的数组 nums ,和一个目标整数 target 。请你从 nums 中找出并返回总和为 target 的元素组合的个数。
|
||||
* 题目数据保证答案符合 32 位整数范围。
|
||||
* @Version: 1.0
|
||||
*/
|
||||
public class T13_CombinationSum4 {
|
||||
|
||||
@Test
|
||||
public void test(){
|
||||
int[] nums = {1, 2, 3};
|
||||
int target = 4;
|
||||
System.out.println(combinationSum4(nums,target));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test1(){
|
||||
int[] nums = {9};
|
||||
int target = 3;
|
||||
System.out.println(combinationSum4(nums,target));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 思路:由题意知:顺序不同的序列被视作不同的组合,因此这个一个排列题:
|
||||
* TODO 动态规划五部曲:
|
||||
* (1)dp定义:dp[i][j] 表示使用nums[0-i]可以构成j的方法数dp[i][j]
|
||||
* (2)dp状态转移方程:dp[i][j]可以由不要num[i]的前nums[i-1]个数构成;或者要num[i]的dp[i-1][j-num[i]]
|
||||
* (3)dp初始化:dp[i][0]=1且dp[0][j%num[0]==0]=1
|
||||
* (4)dp遍历顺序:由于是排列问题,因此先背包后num[i]
|
||||
* (5)dp举例推导:
|
||||
* @param nums
|
||||
* @param target
|
||||
* @return
|
||||
*/
|
||||
public int combinationSum4(int[] nums, int target) {
|
||||
|
||||
int[] dp = new int[target + 1];
|
||||
|
||||
dp[0]=1;
|
||||
|
||||
for (int i = 0; i < target + 1; i++) {
|
||||
for (int j = 0; j < nums.length; j++) {
|
||||
if (i<nums[j]) continue;
|
||||
else dp[i]+=dp[i-nums[j]];
|
||||
}
|
||||
}
|
||||
|
||||
return dp[target];
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
package com.markilue.leecode.dynamic;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* @BelongsProject: Leecode
|
||||
* @BelongsPackage: com.markilue.leecode.dynamic
|
||||
* @Author: markilue
|
||||
* @CreateTime: 2022-11-21 11:16
|
||||
* @Description:
|
||||
* TODO 力扣70题 爬楼梯进阶版:
|
||||
* 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
|
||||
* 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
|
||||
* @Version: 1.0
|
||||
*/
|
||||
public class T14_ClimbStairs {
|
||||
|
||||
@Test
|
||||
public void test(){
|
||||
int n =4;
|
||||
System.out.println(climbStairs2(n));
|
||||
}
|
||||
|
||||
/**
|
||||
* 代码随想录实际实现
|
||||
* 速度击败100%,内存击败45.87%
|
||||
* @param n
|
||||
* @return
|
||||
*/
|
||||
public int climbStairs2(int n) {
|
||||
if(n<=1){
|
||||
return n;
|
||||
}
|
||||
int[] dp=new int[3];
|
||||
dp[1]=1;
|
||||
dp[2]=2;
|
||||
|
||||
for (int i = 3; i <= n; i++) {
|
||||
int now=dp[1]+dp[2];
|
||||
dp[1]=dp[2];
|
||||
dp[2]=now;
|
||||
}
|
||||
|
||||
return dp[2];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*动态规划法进阶思路:
|
||||
* TODO 递归五部曲如下:
|
||||
* (1)确定dp数组及下标含义:dp[i]是i楼可能的方法数是dp[i];题目限定了只能爬一楼或者两楼 => 实际上可以转换为可以从nums=[1,2]中选爬1或者2
|
||||
* (2)确定递推公式:题目已经把递推公式给我们了:状态转移方程 dp[i] +=dp[i-num[i]]
|
||||
* (3)dp数组如何初始化:dp[0] = 1 因为在一楼就是num[i]一个都不选就可以到达
|
||||
* (4)确定遍历顺序:实际上就是先遍历背包在遍历楼梯num[i]
|
||||
* (5)举例推导dp数组:dp数组应该是如下的数列:
|
||||
* 速度击败100%,内存击败5.7%
|
||||
* @param n
|
||||
* @return
|
||||
*/
|
||||
public int climbStairs3(int n) {
|
||||
int[] nums = {1,2};
|
||||
int[] dp = new int[n+1];
|
||||
dp[0]=1;
|
||||
for (int i = 0; i < n + 1; i++) {
|
||||
//可以理解理解为最后爬num[i]层到达,事实上前面怎么爬的不管,也就是排列问题
|
||||
for (int j = 0; j < nums.length; j++) {
|
||||
if (i>=nums[j]){
|
||||
dp[i]+=dp[i-nums[j]];
|
||||
}
|
||||
}
|
||||
}
|
||||
return dp[n];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,228 @@
|
|||
package com.markilue.leecode.dynamic;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* @BelongsProject: Leecode
|
||||
* @BelongsPackage: com.markilue.leecode.dynamic
|
||||
* @Author: markilue
|
||||
* @CreateTime: 2022-12-06 12:05
|
||||
* @Description: TODO 力扣322题 零件兑换:
|
||||
* 给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
|
||||
* 计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
|
||||
* 你可以认为每种硬币的数量是无限的。
|
||||
* @Version: 1.0
|
||||
*/
|
||||
public class T15_CoinChange {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
int[] coins = {1, 2, 5};
|
||||
int amount = 11;
|
||||
|
||||
System.out.println(coinChange1(coins, amount));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test1() {
|
||||
int[] coins = {2, 5, 10, 1};
|
||||
int amount = 27;
|
||||
|
||||
System.out.println(coinChange(coins, amount));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test2() {
|
||||
int[] coins = {186,419,83,408};
|
||||
int amount = 6249;
|
||||
|
||||
System.out.println(coinChange(coins, amount));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 思路:由于题目要求凑出金额所需最少的硬币个数,符合背包问题常见的字眼(最少最大,true or false,排列组合方法) =>错误
|
||||
* 因此可以把dp就定义为凑金币所需最少金币数
|
||||
* TODO 动规五部曲:
|
||||
* (1)dp的定义:dp[j]表示使用coins[0-i]能凑出j的最少硬币数
|
||||
* (2)dp的状态转移方程: dp[j]=min(dp[j],dp[j-coins[i]]+1)
|
||||
* (3)dp的初始化:dp[0]=Integer.maxvalue i=0时 dp[j%coin[i]==0]=j/coin[i]
|
||||
* (4)dp的遍历顺序:由于使用coins的顺序无所谓,因此两个for的顺序无所谓,这里先coins后背包 先coins在背包会出错,
|
||||
* 因为如果小钱在后面前面很多都是用不了小钱的,但实际上小钱都可以用Test1无法通过
|
||||
* 事实上是可以的,需要添加一个判断if(dp[j-coins[i]]!=Integer.MAX_VALUE),即条件成立时,该位才有选择的必要
|
||||
* (5)dp的举例推导:当test时,dp:
|
||||
* [0 1 2 3 4 5 6 7 8 9 10 11]
|
||||
* i=0 0 1 2 3 4 5 6 7 8 9 10 11
|
||||
* i=1 0 1 1 2 2 3 3 4 4 5 5 6
|
||||
* i=2 0 1 1 2 2 1 2 2 3 3 2 3
|
||||
* 根据官方代码简化后 速度击败97.78%,内存击败36.17%
|
||||
* @param coins
|
||||
* @param amount
|
||||
* @return
|
||||
*/
|
||||
public int coinChange(int[] coins, int amount) {
|
||||
|
||||
int[] dp = new int[amount + 1];
|
||||
int max=amount+1;
|
||||
dp[0] = 0;
|
||||
for (int i = 1; i < dp.length; i++) {
|
||||
dp[i] = max;
|
||||
}
|
||||
|
||||
for (int i = 0; i < coins.length; i++) {
|
||||
for (int j = coins[i]; j < dp.length; j++) {
|
||||
if(dp[j-coins[i]]!=max)//这个判断理论上来说可以去掉,可以看这句和下句那个更费时
|
||||
dp[j] = Math.min(dp[j], dp[j - coins[i]] + 1);//如果Integer.MAX_VALUE+1就等于负数了,直接选了
|
||||
}
|
||||
}
|
||||
|
||||
return dp[amount] == max ? -1 : dp[amount];
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 思路:由于题目要求凑出金额所需最少的硬币个数,符合背包问题常见的字眼(最少最大,true or false,排列组合方法)
|
||||
* 因此可以把dp就定义为凑金币所需最少金币数
|
||||
* TODO 动规五部曲:
|
||||
* (1)dp的定义:dp[j]表示使用coins[0-i]能凑出j的最少硬币数
|
||||
* (2)dp的状态转移方程: dp[j]=min(dp[j],dp[j-coins[i]]+1)
|
||||
* (3)dp的初始化:dp[0]=Integer.maxvalue i=0时 dp[j%coin[i]==0]=j/coin[i]
|
||||
* (4)dp的遍历顺序:由于使用coins的顺序有所谓,因为如果coins小的在后面可能会在前面用不到,所以coins循环需要在内部,即最后一个硬币可以是任意
|
||||
* (5)dp的举例推导:当test时,dp:
|
||||
* [0 1 2 3 4 5 6 7 8 9 10 11]
|
||||
* i=0 0 1 2 3 4 5 6 7 8 9 10 11
|
||||
* i=1 0 1 1 2 2 3 3 4 4 5 5 6
|
||||
* i=2 0 1 1 2 2 1 2 2 3 3 2 3
|
||||
* 速度击败87.61%,内存击败10.73%
|
||||
* @param coins
|
||||
* @param amount
|
||||
* @return
|
||||
*/
|
||||
public int coinChange1(int[] coins, int amount) {
|
||||
|
||||
int[] dp = new int[amount + 1];
|
||||
|
||||
dp[0] = 0;
|
||||
for (int i = 1; i < dp.length; i++) {
|
||||
//由于coins[i]最小为1,所以dp[i]最大为amount,一定会小于amount+1
|
||||
dp[i] = i % coins[0] == 0 ? i / coins[0] : amount+1;
|
||||
}
|
||||
|
||||
|
||||
for (int j = 0; j < dp.length; j++) {
|
||||
for (int i = 1; i < coins.length; i++) {
|
||||
if (j >= coins[i]) {
|
||||
dp[j] = Math.min(dp[j], dp[j - coins[i]] + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dp[amount] == amount+1? -1 : dp[amount];
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 代码随想录方法:钱币有顺序和没有顺序都可以,都不影响钱币的最小个数。所以内外for循环都可以
|
||||
* 速度击败97.78%,内存击败79.89%
|
||||
* 快在我的for循环还需要判断一次是否能整除
|
||||
* @param coins
|
||||
* @param amount
|
||||
* @return
|
||||
*/
|
||||
public int coinChange2(int[] coins, int amount) {
|
||||
int max = Integer.MAX_VALUE;
|
||||
int[] dp = new int[amount + 1];
|
||||
//初始化dp数组为最大值
|
||||
for (int j = 0; j < dp.length; j++) {
|
||||
dp[j] = max;
|
||||
}
|
||||
//当金额为0时需要的硬币数目为0
|
||||
dp[0] = 0;
|
||||
for (int i = 0; i < coins.length; i++) {
|
||||
//正序遍历:完全背包每个硬币可以选择多次
|
||||
for (int j = coins[i]; j <= amount; j++) {
|
||||
//只有dp[j-coins[i]]不是初始最大值时,该位才有选择的必要
|
||||
if (dp[j - coins[i]] != max) {
|
||||
//选择硬币数目最小的情况
|
||||
dp[j] = Math.min(dp[j], dp[j - coins[i]] + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return dp[amount] == max ? -1 : dp[amount];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 官方记忆化搜索法:本质其实就是动态规划法,但是通过dfs实现
|
||||
* 即假设最后一枚硬币为C,那么F(S)=F(S-C)+1,而最后一张的面值可以是coins[i],因此遍历,具体可以查看笔记
|
||||
* 时间复杂度和空间复杂度与动态规划法一致
|
||||
* 但速度击败6.66%,内存击败5.94% 38ms
|
||||
* @param coins
|
||||
* @param amount
|
||||
* @return
|
||||
*/
|
||||
public int coinChange3(int[] coins, int amount) {
|
||||
if (amount < 1) {
|
||||
return 0;
|
||||
}
|
||||
return coinChange(coins, amount, new int[amount]);
|
||||
}
|
||||
|
||||
private int coinChange(int[] coins, int rem, int[] count) {
|
||||
if (rem < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (rem == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (count[rem - 1] != 0) {
|
||||
return count[rem - 1];
|
||||
}
|
||||
int min = Integer.MAX_VALUE;
|
||||
for (int coin : coins) {
|
||||
int res = coinChange(coins, rem - coin, count);
|
||||
if (res >= 0 && res < min) {
|
||||
min = 1 + res;
|
||||
}
|
||||
}
|
||||
count[rem - 1] = (min == Integer.MAX_VALUE) ? -1 : min;
|
||||
return count[rem - 1];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 评论区贪心+dfs法:本质上就是优先选择硬币面值较大的方法,但实际上就是暴力解法,但是利用贪心剪枝,效果可能比动规还好
|
||||
* 速度击败99% 8ms
|
||||
* 由于本质上是暴力解法,如果案例不好可能会超时
|
||||
*/
|
||||
int res = Integer.MAX_VALUE;
|
||||
public int coinChange4(int[] coins, int amount){
|
||||
if(amount==0){
|
||||
return 0;
|
||||
}
|
||||
Arrays.sort(coins);
|
||||
mincoin(coins,amount,coins.length-1,0);
|
||||
return res==Integer.MAX_VALUE? -1:res;
|
||||
}
|
||||
private void mincoin(int[] coins,int amount, int index, int count){
|
||||
if(amount==0){
|
||||
res = Math.min(res,count);
|
||||
return;
|
||||
}
|
||||
if(index<0){
|
||||
return;
|
||||
}
|
||||
for(int i = amount/coins[index];i>=0 && i+count<res; i--){
|
||||
mincoin(coins,amount - (i*coins[index]), index-1, count+i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue