力扣一维dp刷题(新年第一弹)

蓬门今始为君开~

最近期末周,更新比较慢,放假后会更新大量优质题目的解析~

983. 最低票价

来源 力扣

在一个火车旅行很受欢迎的国度,你提前一年计划了一些火车旅行。在接下来的一年里,你要旅行的日子将以一个名为 days 的数组给出。每一项是一个从 1 到 365 的整数。

火车票有 三种不同的销售方式 :

  • 一张 为期一天 的通行证售价为 costs[0] 美元;
  • 一张 为期七天 的通行证售价为 costs[1] 美元;
  • 一张 为期三十天 的通行证售价为 costs[2] 美元。

通行证允许数天无限制的旅行。 例如,如果我们在第 2 天获得一张 为期 7 天 的通行证,那么我们可以连着旅行 7 天:第 2 天、第 3 天、第 4 天、第 5 天、第 6 天、第 7 天和第 8 天。

返回 你想要完成在给定的列表 days 中列出的每一天的旅行所需要的最低消费 

示例 1:

输入:days = [1,4,6,7,8,20], costs = [2,7,15]
输出:11
解释: 
例如,这里有一种购买通行证的方法,可以让你完成你的旅行计划:
在第 1 天,你花了 costs[0] = $2 买了一张为期 1 天的通行证,它将在第 1 天生效。
在第 3 天,你花了 costs[1] = $7 买了一张为期 7 天的通行证,它将在第 3, 4, ..., 9 天生效。
在第 20 天,你花了 costs[0] = $2 买了一张为期 1 天的通行证,它将在第 20 天生效。
你总共花了 $11,并完成了你计划的每一天旅行。

示例 2:

输入:days = [1,2,3,4,5,6,7,8,9,10,30,31], costs = [2,7,15]
输出:17
解释:
例如,这里有一种购买通行证的方法,可以让你完成你的旅行计划: 
在第 1 天,你花了 costs[2] = $15 买了一张为期 30 天的通行证,它将在第 1, 2, ..., 30 天生效。
在第 31 天,你花了 costs[0] = $2 买了一张为期 1 天的通行证,它将在第 31 天生效。 
你总共花了 $17,并完成了你计划的每一天旅行。

提示:

  • 1 <= days.length <= 365
  • 1 <= days[i] <= 365
  • days 按顺序严格递增
  • costs.length == 3
  • 1 <= costs[i] <= 1000

思路分析:

我们每次拿到一个日期,可以有三种尝试方法:1天,7天和30天,我们记录每一种尝试方法,再找出最小花费即可,只用选择日期一个参数,很经典的一维动态规划。

为了方便大家入门,这里先不直接列出转移方程,而是先使用递归的方式呈现,比较直观

当然 直接暴力递归肯定会T,所以这里用了一个记忆数组,保证我们没有重复计算,极大地优化了时间

class Solution {
    	 static int MAX=366;
	    static int []durations = {1,7,30};
		 static int dp[]=new int [MAX];
    public int mincostTickets(int[] days, int[] costs) {
			 int n=days.length;
			 
				for(int i=0;i<n;i++)
				{
					dp[i]=-1;
				}//初始化格子
			
			return (dfs(0,days,costs));
		}
		 //dfs 含义 返回完成从当前天数到旅行结束in所需要的最小价格
		 static int dfs(int in,int []days,int []costs)
		 {
			 if(in==days.length) return 0;//完成所有旅行
			 if(dp[in]!=-1)  return dp[in];//记忆化搜索应该是在越界判断之下的 
			 int ans=Integer.MAX_VALUE;
			 for(int i=0,j=i;i<3;i++)
			 {
				 
				 while(j<days.length&&days[in]+durations[i]>days[j])
				 {
					 j++;//通过移动下标j 一直找到通行证过期的下一个旅行天数
				 }
				//枚举
				 int sum=costs[i]+dfs(j, days, costs);
				ans=Math.min(ans, sum);//找到花费最小的尝试
			 }
			 dp[in]=ans;
			 return ans;
		 }

    
}

这一版已经是最优解了

当然,也可以用dp的形式。

核心代码(转移方程都是从上一个版本扒过来的,这说明二者有相通之处)

稍微解释一下  首先定义dp表 长度为days的长度+1(因为从0开始遍历到n)

初始化,最后一个越界位置 ,代价是0,填上0

然后从n-2开始填表

dp【0】就是从第一个日期到结束所需要的最小花费拉~

class Solution { 
    public static int MAX=366;
    public static int []durations = {1,7,30};
	public static int dp[]=new int [MAX];
    public int mincostTickets(int[] days, int[] costs) {
			dp=new int [days.length+1];
			int N=dp.length;
			dp[N-1]=0;
			for(int index=N-2;index>=0;index--)
			{
				int ans=Integer.MAX_VALUE;
				 for(int k=0,j=index;k<3;k++)
				  {
					  
					  while(j<days.length && days[index]+durations[k]>days[j]) j++;//枚举
					  
					  ans=Math.min(ans,costs[k]+dp[j]);//记录当前选择的 价格 然后从days【j】天再买
				  }
				 dp[index]=ans;
			}	
			
			return dp[0];
			
		}
		  //在第index对应的天 完成所有旅行的最低消费
		
    }

91. 解码方法

来源 力扣

一条包含字母 A-Z 的消息通过以下映射进行了 编码 :

'A' -> "1"
'B' -> "2"
...
'Z' -> "26"

要 解码 已编码的消息,所有数字必须基于上述映射的方法,反向映射回字母(可能有多种方法)。例如,"11106" 可以映射为:

  • "AAJF" ,将消息分组为 (1 1 10 6)
  • "KJF" ,将消息分组为 (11 10 6)

注意,消息不能分组为  (1 11 06) ,因为 "06" 不能映射为 "F" ,这是由于 "6" 和 "06" 在映射中并不等价。

给你一个只含数字的 非空 字符串 s ,请计算并返回 解码 方法的 总数 。

题目数据保证答案肯定是一个 32 位 的整数。

示例 1:

输入:s = "12"
输出:2
解释:它可以解码为 "AB"(1 2)或者 "L"(12)。

示例 2:

输入:s = "226"
输出:3
解释:它可以解码为 "BZ" (2 26), "VF" (22 6), 或者 "BBF" (2 2 6) 。

示例 3:

输入:s = "06"
输出:0
解释:"06" 无法映射到 "F" ,因为存在前导零("6" 和 "06" 并不等价)。

提示:

  • 1 <= s.length <= 100
  • s 只包含数字,并且可能包含前导零。

解题思路:解码的方式有多种,最好想的办法就是dfs一路从左往右尝试,注意对应好1-26,排除前导0的情况。

class Solution {
    
    public int numDecodings(String s) {
       int dp[]=new int [s.length()];
	char word[]=new char [s.length()];
	
	for(int i=0;i<dp.length;i++)
	{
		dp[i]=-1;
	}
	   word=s.toCharArray();
	  
	   return( dfs(word,0,dp));
	   
	}
	static int dfs(char word[],int in,int dp[])//dfs含义 得到从当前下标到结尾的所有解码方法个数
	{
		if(in==word.length) return 1;//代表这是一种可行的方法
		if(dp[in]!=-1) return dp[in];
		//拿到一个字母的处理方法
		int ans=0;
		if(word[in]=='0') return 0;//这是一条死路,直接断掉就行
		ans=dfs(word, in+1,dp);//直接映射1-9
		if(in+1<word.length&&(word[in]-'0')*10+word[in+1]-'0'<=26)//判断,不能越界,并且对应26以内
			ans+=dfs(word, in+2,dp);
		dp[in]=ans;
		return ans;		
	}
	
	   
}

仍然已经是最优算法~

当然 有dp版本

总体思路相同。

class Solution {
    public int numDecodings(String s) {

	   char word[]=new char [s.length()];
	   word=s.toCharArray();
	   int dp[]=new int [s.length()+1];
       int N=dp.length;
       dp[N-1]=1;
	 for(int index=N-2;index>=0;index--)
     {
          int  ans=0;
		 if(word[index]=='0') ans=0;
		 //单独出现前导0 说明这个方案不符
		 else {
			ans+=dp[index+1];//处理一个
			//if(word[index]<=2) //记得防止越界  字符要转换成数字
			if(index+1<word.length&&((word[index]-'0')*10+word[index+1]-'0')<=26)
				ans+=dp[index+2];
		}
		 dp[index]=ans;
     }
	   
	  return dp[0];
	   
	}
	 

}

有人会问,我dfs已经是最优,为啥还要搞成dp的形式呢?

因为对于有些题目,dp可以更好地优化,比如状压dp。

对于练习来说,建议都试着改一下dp的形式~

当然,对于一些路径依赖的题目,无法改成dp,后面会写文章讲讲~

怎么样,是不是有点感觉了??

再来一个升级版~

639. 解码方法 II

一条包含字母 A-Z 的消息通过以下的方式进行了 编码 :

'A' -> "1"
'B' -> "2"
...
'Z' -> "26"

要 解码 一条已编码的消息,所有的数字都必须分组,然后按原来的编码方案反向映射回字母(可能存在多种方式)。例如,"11106" 可以映射为:

  • "AAJF" 对应分组 (1 1 10 6)
  • "KJF" 对应分组 (11 10 6)

注意,像 (1 11 06) 这样的分组是无效的,因为 "06" 不可以映射为 'F' ,因为 "6" 与 "06" 不同。

除了 上面描述的数字字母映射方案,编码消息中可能包含 '*' 字符,可以表示从 '1' 到 '9' 的任一数字(不包括 '0')。例如,编码字符串 "1*" 可以表示 "11""12""13""14""15""16""17""18" 或 "19" 中的任意一条消息。对 "1*" 进行解码,相当于解码该字符串可以表示的任何编码消息。

给你一个字符串 s ,由数字和 '*' 字符组成,返回 解码 该字符串的方法 数目 。

由于答案数目可能非常大,返回 109 + 7 的  。

示例 1:

输入:s = "*"
输出:9
解释:这一条编码消息可以表示 "1"、"2"、"3"、"4"、"5"、"6"、"7"、"8" 或 "9" 中的任意一条。
可以分别解码成字符串 "A"、"B"、"C"、"D"、"E"、"F"、"G"、"H" 和 "I" 。
因此,"*" 总共有 9 种解码方法。

示例 2:

输入:s = "1*"
输出:18
解释:这一条编码消息可以表示 "11"、"12"、"13"、"14"、"15"、"16"、"17"、"18" 或 "19" 中的任意一条。
每种消息都可以由 2 种方法解码(例如,"11" 可以解码成 "AA" 或 "K")。
因此,"1*" 共有 9 * 2 = 18 种解码方法。

示例 3:

输入:s = "2*"
输出:15
解释:这一条编码消息可以表示 "21"、"22"、"23"、"24"、"25"、"26"、"27"、"28" 或 "29" 中的任意一条。
"21"、"22"、"23"、"24"、"25" 和 "26" 由 2 种解码方法,但 "27"、"28" 和 "29" 仅有 1 种解码方法。
因此,"2*" 共有 (6 * 2) + (3 * 1) = 12 + 3 = 15 种解码方法。

提示:

  • 1 <= s.length <= 105
  • s[i] 是 0 - 9 中的一位数字或字符 '*'

解决方法;

本题多了一个“ * ” 字符,可以代表任意非0数字(很关键) 我们依然分类讨论即可

拿到一个字符   判断

             1.不是*

                      1.1  是0  拱!

                        1.2 不是0    1.11 直接合成一个字符 1-9

                                             1.12 跟后面一个一起合成 

                                                         1.111  后面是数字     判断 26

                                                          1.112  后面是*      1* 和2*符合题意

                                                                                      1*有9种  2*有6种  

               2.是*   

                                1.1 直接合成   那就是9种

                                 1.2 跟后面的一起合成     

                                                   1.21  后面是数字          *(1-6) 有2*1=2种  

                                                                                        *(7-9 ) 有1*1=1种

                                                   1.22 后面是*             **       有15种 

到了这里,恭喜,你已经讨论结束了  然后代码实现就可以拉

别忘了取模    你有没有忘了?O-O

class Solution {
    public static long mod=1000000007L;
    public int numDecodings(String s) {
      
		char  word[]=new char [s.length()];
		long dp[]=new long [s.length()];
		for(int i=0;i<dp.length;i++)
		{	
			dp[i]=-1;
		}
		word=s.toCharArray();
		
		return (int)(dfs(word,0,dp));
	}
//表示从第index位置开始看,最多有几种编码方法
  static long dfs(char word[],int index,long dp[])
	  {
		  if(index==word.length) return 1;//走到头了 表示这种解法符合题意
           if(word[index]=='0')  return 0;
		  if(dp[index]!=-1) return dp[index];
		  //讨论index位置是*还是数字,是*有九种可能
		  long ans=dfs(word,index+1,dp)*(word[index]=='*'? 9:1);
		  if(index+1<word.length)
		  {
		   if(word[index]!='*')
		   {
			   if(word[index+1]!='*')
			   {
				   if((word[index]-'0')*10+word[index+1]-'0'<=26)
					   ans+=dfs(word,index+2,dp);//数字 +数字
		   }else
		   {//数字+*
			   if(word[index]=='1')
			   {
				   ans+=9*dfs(word, index+2, dp);
			   }
			   if(word[index]=='2')
			   {
				   ans+=6*dfs(word, index+2, dp);
			   }
		   }
			
		   }else //*加数字
		   {
			if(word[index+1]!='*') {
				if((word[index+1]-'0')<=6)
					ans+=2*dfs(word,index+2,dp);
					else
						ans+=dfs(word,index+2,dp);
			     }else //* + *
			     {
					ans+=dfs(word, index+2, dp)*15;
				}
			}
		 }
         ans%=mod;
		  dp[index]=ans;
		  return ans;
		
	 
	}
}

一维dp版本

class Solution {
    public static long mod=1000000007L;
    public int numDecodings(String s) {
      
		char  word[]=new char [s.length()];
		long dp[]=new long [s.length()+1];
		int N=dp.length;
        dp[N-1]=1;
		word=s.toCharArray();
		for(int index=N-2;index>=0;index--)
        {
            long ans=0;
           
           if(word[index]!='0') 
            {//很关键  如果是0 直接跳过 不参与一切运算
		 
		  //讨论index位置是*还是数字,是*有九种可能
		  ans=ans+dp[index+1]*(word[index]=='*'? 9:1);
		  if(index+1<word.length)
		  {
		   if(word[index]!='*')
		   {
			   if(word[index+1]!='*')
			   {
				   if((word[index]-'0')*10+word[index+1]-'0'<=26)
					   ans+=dp[index+2];//数字 +数字
		   }else
		   {//数字+*
			   if(word[index]=='1')
			   {
				   ans+=9*dp[index+2];
			   }
			   if(word[index]=='2')
			   {
				   ans+=6*dp[index+2];
			   }
		   }
			
		   }else //*加数字
		   {
			if(word[index+1]!='*') {
				if((word[index+1]-'0')<=6)
					ans+=2*dp[index+2];
					else
						ans+=dp[index+2];
			     }else //* + *
			     {
					ans+=dp[index+2]*15;
				}
			}
		 }
            }
         ans%=mod;
		dp[index]=ans;
        }
		return (int)(dp[0]);
	}
//表示从第index位置开始看,最多有几种编码方法
}

题解大佬的版本,直接用两个变量优化掉整个数组 值得学习!!

public static int numDecodings4(String str) {
		char[] s = str.toCharArray();
		int n = s.length;
		long cur = 0, next = 1, nextNext = 0;
		for (int i = n - 1; i >= 0; i--) {
			if (s[i] != '0') {
				cur = (s[i] == '*' ? 9 : 1) * next;
				if (i + 1 < n) {
					if (s[i] != '*') {
						if (s[i + 1] != '*') {
							if ((s[i] - '0') * 10 + s[i + 1] - '0' <= 26) {
								cur += nextNext;
							}
						} else {
							if (s[i] == '1') {
								cur += nextNext * 9;
							}
							if (s[i] == '2') {
								cur += nextNext * 6;
							}
						}
					} else {
						if (s[i + 1] != '*') {
							if (s[i + 1] <= '6') {
								cur += nextNext * 2;
							} else {
								cur += nextNext;
							}
						} else {
							cur += nextNext * 15;
						}
					}
				}
				cur %= mod;
			}
			nextNext = next;
			next = cur;
			cur = 0;
		}
		return (int) next;
	}

后面开始做一些直接dp的题目,就不一步步递归分析了

264. 丑数 II

来源 你猜 ?当然是力扣

给你一个整数 n ,请你找出并返回第 n 个 丑数 。

丑数 就是质因子只包含 23 和 5 的正整数。

示例 1:

输入:n = 10
输出:12
解释:[1, 2, 3, 4, 5, 6, 8, 9, 10, 12] 是由前 10 个丑数组成的序列。

示例 2:

输入:n = 1
输出:1
解释:1 通常被视为丑数。

提示:

  • 1 <= n <= 1690

暴力思维  我直接枚举每一个数,看看是不是丑数?

弊端  仔细想想会发现  当n慢慢增大时,我们枚举的数增长速度快到离谱! 

那么循环次数直接爆炸了,肯定T

那么,可不可以优化一下? 我用现成的数,去直接得到下一个丑数??

当然  丑数 就是质因子只包含 23 和 5 的正整数。 那么 丑数的2 3 5倍一定也是丑数

哦哦,那我就依次找到我已经有的丑数的2 3 5倍中最小的数做下一个丑数就可以了

为了方便定位,我们使用三个指针来分别记录当下最小的2 3 5倍

为啥想到用多指针法?  因为可能性出现了单调性,数一定是越乘越大

不会双指针的朋友可以看这里 --->力扣双指针刷题(算法日记)-CSDN博客

class Solution {
    public int nthUglyNumber(int n) {

    int dp[]=new int [n+1];
    dp[1]=1;
    //思路  某一个丑数一定是前面丑数*2或*3或*5的倍数
    for(int i=2,p2=1,p3=1,p5=1;i<=n;i++)
    {//三个指针先同时指向第一个丑数
        int a=dp[p2]*2;
        int b=dp[p3]*3;//依次枚举
        int c=dp[p5]*5;
        int now=Math.min(Math.min(a,b),c);//定位到最小 的丑数 就是下一个丑数
        if(a==now) p2++;//如果枚举到了  就让对应的指针往后走一格
        if(b==now) p3++;
        if(c==now) p5++;
        dp[i]=now;
    }
    //System.out.println(dp[n]);
    return dp[n];
}
}

码字好累呜呜呜,再打一个题吧    TWT

32. 最长有效括号

给你一个只包含 '(' 和 ')' 的字符串,找出最长有效(格式正确且连续)括号子串的长度。

示例 1:

输入:s = "(()"
输出:2
解释:最长有效括号子串是 "()"

示例 2:

输入:s = ")()())"
输出:4
解释:最长有效括号子串是 "()()"

示例 3:

输入:s = ""
输出:0

提示:

  • 0 <= s.length <= 3 * 104
  • s[i] 为 '(' 或 ')'

这题的数据量就比较大了,递归可能太好搞。。。

dp一波 还是从左向右分析

定义dp【i】含义 , 在当前index位置之前 一个最长有效串的长度,

那么答案就是所有的dp【】表里最大的那一个 

然后开始分类讨论了

拿到左括号  : 未匹配 ,返回0

拿到右括号:   往前找,定位到左括号  

如果找到了     那么长度至少是2    然后再加上左括号前面的格子的计算长度

比如(  ) (  )   计算4的时候要加上前面的2

dp        2        4

找不到  那么这个右括号废了 对应格子填0 

稍微注意一下边界处理就行 

class Solution {
    public int longestValidParentheses(String s) {
	//思路 
	 //定义递归函数 在当前index位置之前 最长有效串的长度
	 //)(()())
	
	 char []a=new char [30005];
	 int dp[]=new int [s.length()];
	 a=s.toCharArray();
	 int ans=0;
	 for(int i=1,p;i<s.length();i++)
	 {
		 if(a[i]=='(') dp[i]=0;
		 if(a[i]==')') 
		 {
			 p=i-dp[i-1]-1;//定位找到需要判定的位置
			 if(p>=0&&a[p]=='(')
			 {
			  dp[i]=2+dp[i-1]+(p>0? dp[p-1]:0);
			 }else 
			 dp[i]=0;
		 }
		 ans=Math.max(ans, dp[i]);
	 }
	
	return ans;
}
 
}

dp刷题,未完待续,新人博主,欢迎关注,如果不行的话,点赞个赞再走叭~

  • 49
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值