模版:
java
判空
特殊条件
dp[n] = dp[n-1] + dp[n-2]
遍历dp
条件判断
取值返回
70-爬楼梯
**思路:**设置初始值1和2位置的,然后算3
**注意:**返回值里n代表索引,不是数组长度;
数组长度要设置为n+1,多一个,方便和i对齐;
java
class Solution {
public int climbStairs(int n) {
int[] dp=new int[n+1];//0到n ,总共n+1个
if(n<2){return n;}
dp[1]=1;
dp[2]=2;
for(int i=3;i<n+1;i++){
dp[i]=dp[i-1]+dp[i-2];
}
return dp[n];
}
}
118-杨辉三角
思路:建二维的,先设尖尖初始值,一层层处理,然后添加进结果; 层处理时,前后添加1,中间值为上一层的j-1 和j的和
**注意:**判空条件;
添加用add(xxx),获取用 get(i);
注意层里元素的位置和层数,可用遍历的 i变形来表示出来
java
class Solution {
public List<List<Integer>> generate(int numRows) {
List <List<Integer>> result=new ArrayList<>(); //建二维列表
if(numRows==0){return result;}//判空
List<Integer> firstRows=new ArrayList<>(); //设初始值塔尖
firstRows.add(1);
result.add(firstRows);
for(int i=1;i<numRows;i++){//遍历层数,第1行有了,从第2行开始
List<Integer>curRows=new ArrayList<>(); //当前层
List<Integer>preRows=result.get(i-1); //上一层,并获取层的值.get()
curRows.add(1);//开头加一
//中间 上一层左右之和
for(int j=1;j<i;j++){
int num=preRows.get(j-1)+preRows.get(j);
curRows.add(num);
}
curRows.add(1); //结尾加一
result.add(curRows);
}
return result;
}
}
198-打家劫舍
**思路:**遍历到i时,两种情况i-1可以偷也可以不偷,选个最大(dp[i-1] ,dp[i-2]+nums[i]) ,dp存 的是每个位置最大金额数
注意: 特殊情况和边界,nums为0为1;比较大小用Math.max( )
java
class Solution {
public int rob(int[] nums) {
//特殊情况
if(nums==null||nums.length==0){return 0;}
if(nums.length==1){return nums[0];}
int [] dp=new int[nums.length];
dp[0]=nums[0];
dp[1]=Math.max(dp[0],nums[1]);
for(int i=2;i<nums.length;i++){
dp[i]=Math.max(dp[i-1],dp[i-2]+nums[i]);
}
return dp[nums.length-1];
}
}
279-完全平方数
**思路:**每次取前一个(减去一个平方数)的值+1 ,判断和现在的平方数谁更少
**注意:**dp[ 0 ] 不参与使用;
考虑特殊情况,全为1;
注意条件边界选n还是n+1
java
class Solution {
public int numSquares(int n) {
int []dp=new int[n+1];//从1开始,0空下,对应上i索引
//最坏情况全由1组成
for(int i=1;i<n+1;i++){
dp[i]=i;
}
//遍历每个i,i的上一个是 减一个平方数再加1
for(int i=1;i<n+1;i++){
for(int j=1;j*j<=i;j++){
dp[i]=Math.min(dp[i],dp[i-j*j]+1);//比较上一个再+1
}
}
return dp[n];
}
}
322-零钱兑换
思路:dp存硬币数,先把所有的ap初始值设成报错形式(amount+1) ;遍历dp,遍历硬币,只要硬币能凑齐 就返回少一个硬币的最小值。 使用**min(dp[ i ] ,dp[ i -coin] +1 )**更新dp,并设置条件防止dp[负数]的出现。
**注意:**dp初始值要设成最大的,且报错形式;
由于i-coin 遍历过程中可能出现负数,要做coin<= i 的预防;
结果只判断amount位置的金额,没错就返回,有错(即初始值)就返-1
java
class Solution {
public int coinChange(int[] coins, int amount) {
int [] dp=new int[amount+1];//dp表示每个金额所需硬币数量
for(int i=0;i<amount+1;i++){
dp[i]=amount+1; //要进行min,初始值设置最大值(不易实现就行,要比amount大,方便返回报错)
}
//每次减一个硬币参与比较
dp[0]=0;
for(int i=1;i<amount+1;i++){
for(int coin:coins){
if(coin<=i){ //可进行(即硬币小于金额 避免出现dp里有负的)
dp[i]=Math.min(dp[i],dp[i-coin]+1);
}
}
}
if(dp[amount]>amount){ //把初始值设置成报错形式 dp都为amount+1
return -1;
}else{
return dp[amount];
}
}
}
139-单词拆分
**思路:**遍历dp,存i之前的是否在字典里;判断方法,取i从j开始判断(j之前都对,且j到i之间也在字典里),没问题就标记dp[ i ],下一个
**注意:**输入是list 有重复,设成set形式保证不重复;
set的用 .contains( ) ,其他的用get()
java
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
Set<String> wordSet=new HashSet<>(wordDict);//把wordDict变成set形式好操作,元素不重复
boolean []dp =new boolean[s.length()+1];
dp[0]=true;
for( int i=1;i<=s.length();i++){ //取s字符串的长度用length()
for(int j=0;j<i;j++){
if(dp[j]==true&&wordSet.contains((s.substring(j,i)))){
dp[i]=true; //j以前的都存在,j到i字典也包含,则没毛病
break; //没问题就下一个
}
}
}
return dp[s.length()];
}
}
300-最长递增子序列
**思路:**三板斧:判空特殊条件、设初始值、遍历dp[ i ]和j
本题条件 要找到每个位置 前面按升序的数字个数,遍历每个位置为结束点,再从头遍历数
有几个,dp[ i ] 与dp[ j ]+1进行比较并更新最大的
**注意:**这次dp长度直接nums就行,0到length-1就可以索引;
i不用从i=0开始,从i=1开始就行,0的位置本身就算一个
java
class Solution {
public int lengthOfLIS(int[] nums) {
if(nums==null||nums.length==0){return 0;}//特殊情况
int []dp=new int [nums.length];
Arrays.fill(dp,1);//给dp所有内容变成1
int max=1;
for(int i=1;i<nums.length;i++){
for(int j=0;j<i;j++){
if(nums[j]<nums[i]){
dp[i]=Math.max(dp[i],dp[j]+1);//更新dp[i]
}
}
max=Math.max(dp[i],max);
}
return max;
}
}
152-乘积最大子数组
**思路:**由于有正负两种情况 之前是最小 下一个就可能最大,所以设置两种max和min,分别对比(当前值、上一最大值*当前值、上一最小值*当前值),持续更新max和min,并比较三者。最大的值暂存就是结果
**注意:**老套路模版,dp和max的初始值要设置成第一个值;
由于只取最大值,所以循环一遍就行。
java
class Solution {
public int maxProduct(int[] nums) {
if(nums==null||nums.length==0){return 0;}
int n=nums.length;
int dpMax=nums[0];
int dpMin=nums[0];
int max=nums[0];
for(int i=1;i<nums.length;i++){
int premax=dpMax;//将上一轮的dpmax存在premax
int premin=dpMin;
dpMax=Math.max(nums[i],Math.max(premax*nums[i],premin*nums[i]));//更新dpmax 选最大
dpMin=Math.min(nums[i],Math.min(premax*nums[i],premin*nums[i]));
max=Math.max(dpMax,max); //取每轮最大值
}
return max;
}
}
416-分割等和子集
**思路:**包里的石头初始为目标重量,从重量范围内依次减石头,直到减到nums[ 0 ] ,将其也设置成true
例子 1、5、11、5
总和为22,中间目标值为11,从目标重量开始减nums[ i ] ,若出现到dp[ 0 ]初始值√ ,那也将其设置成√; 一轮结束后 ,在继续减5、11、5;
java
索引: 0 1 2 3 4 5 6 7 8 9 10 11
dp: ✅ ❌ ❌ ❌ ❌ ❌ ❌ ❌ ❌ ❌ ❌ ❌
第一轮: 从11开始持续 -1判断
索引: 0 1 2 3 4 5 6 7 8 9 10 11
dp: ✅ ✅ ❌ ❌ ❌ ❌ ❌ ❌ ❌ ❌ ❌ ❌
第二轮:从11开始持续 -5判断
索引: 0 1 2 3 4 5 6 7 8 9 10 11
dp: ✅ ✅ ❌ ❌ ❌ ✅ ✅ ❌ ❌ ❌ ❌ ❌
第三轮:从11开始持续 -11判断
索引: 0 1 2 3 4 5 6 7 8 9 10 11
dp: ✅ ✅ ❌ ❌ ❌ ✅ ✅ ❌ ❌ ❌ ❌ ✅
第四轮:从11开始持续**-5**判断
索引: 0 1 2 3 4 5 6 7 8 9 10 11
dp: ✅ ✅ ❌ ❌ ❌ ✅ ✅ ❌ ❌ ❌ ✅ ✅
**注意:**0-1背包问题,比较抽象,多刷,死记硬背
java
class Solution {
public boolean canPartition(int[] nums) {
int sum=0;//先算总和
for(int i=0;i<nums.length;i++){
sum+=nums[i];
}
if(sum%2==1){return false;}//奇数则不可能实现
int target=sum/2;
boolean[] dp=new boolean[target+1];
dp[0]=true;
for(int i=0;i<nums.length;i++){
for(int j=target;j>=nums[i];j--){
dp[j]=dp[j]||dp[j-nums[i]];
}
}
return dp[target];
}
}








