动态规划就是分治的思想,通俗一点就是大事化小,小事化了。并且在将大问题化解为小问题的时候,保存对这些小问题处理好的结果,供后面处理更大规模问题去使用。
动态规划的特点:
1.把原来的问题分解成了几个相似的子问题
2.所有子问题只需要解决一边
3.存储子问题的解
动态规划问题解决步骤
动态规划的本质,是对问题状态的定义和状态方程的定义。
1.状态定义
2.状态间的转移方程定义
3.状态的初始化
4.返回结果(解或者间接解)
状态定义的要求:定义的状态一定要形成递推关系
适用场景最大最小值,可不可行,是不是,方案个数等问题。
================================================================================
先对问题进行分析
1.状态定义: F(n)
2.状态间的转移方程:F(n)=F(n-1)+F(n-2);
3.状态的初始化:(初始值) F(0)=0; F(1)=1;
4.返回结果:F(n)
代码如下(示例):
public static int Fib(int n){
if(n<=0){
return 0;
}
//从第零项开始所以多开辟一个空间
int []dp=new int[n+1];
dp[0]=0;
dp[1]=1;
for(int i=2;i<=n;i++){
//状态转移方程
//F(n)=F(n-1)+F(n-2)
dp[i]=dp[i-1]+dp[i-2];
}
return dp[n];
}
当然我们也可以不使用数组让空间复杂度达到O(1)
public static int Fib1(int n){
if(n<=0){
return 0;
}
if(n==1||n==2){
return 1;
}
int f1=1;
int f2=1;
int f3=0;
for(int i=3;i<=n;i++){
f3=f1+f2;
f1=f2;
f2=f3;
}
return f3;
}
很多动规问题都可以转化成Fib数列解决
==========================================================================
先是变态青蛙跳台阶,有n阶台阶,青蛙可以每次跳一个,二个…或者n个
分解问题:
1.状态定义F(n)
2.状态间转移方程定义 F(n)=2F(n-1)
3.状态的初始化 F(1)=1
4.返回结果 F(n)
public static int climbingStairs(int n){
int res=1;
for(int i=2;i<=n;i++){
//F(n)=2F(n-1)
res*=2;
}
return res;
}
之后是正常的青蛙,每次只能跳一个台阶或者两个台阶(斐波那契数列)
1.状态定义 F(n)
2.状态间转移方程定义F(n)=F(n-1)+F(n-2)
3.状态初始化 F(1)=1;
4.返回结果 F(n)
public static int climbingStairs1(int n){
if(n==1||n==2){
return n;
}
int f1=1;
int f2=2;
int f3=0;
for(int i=3;i<=n;i++){
f3=f1+f2;
f1=f2;
f2=f3;
}
return f3;
}
=========================================================================
分析问题后依旧是斐波那契数列
1.状态定义 F(n)
2.状态间转移方程定义 F(n)=F(n-1)+F(n-2)
3.状态的初始化 F(1)=1 F(2)=2
4.返回结果 F(n)
public static int rectCover(int target) {
if(target==1||target==2){
return target;
}
int t1=1;
int t2=2;
int t3=0;
for(int i=3;i<=target;i++){
t3=t1+t2;
t1=t2;
t2=t3;
}
return t3;
}
}
或者使用数组记录
public static int rectCover1(int target){
if(target==0){
return 0;
}
int []dp=new int[target+1];
dp[0]=1;
dp[1]=2;
for(int i=2;i<target;i++){
dp[i]=dp[i-1]+dp[i-2];
}
return dp[target-1];
}
=============================================================================
这种题使用动态规划来做的话就和前面几个不相同了,因为其无法定义状态间转移方程
所以就把大问题化成先问题去解决
例如求最大连续字数组和,先求前i个最大字数组的和,最终求最大连续字数组和
时间复杂度O(n),空间复杂度O(1)做法
public int maxSubArray(int[] nums) {
int res=nums[0];
for(int i=1;i<nums.length;i++){
//比较当前值和当前值加上前一个值的大小
nums[i]=Math.max(nums[i-1]+nums[i],nums[i]);
res=Math.max(nums[i],res);
}
return res;
}
或者另外一种如果前i项最大子序和<=0就重新赋值为下一个数值,记录max最大值
public static int maxSubArray(int[] nums) {
int max=nums[0];
int count=nums[0];
for(int i=1;i<nums.length;i++){
if(count<=0){
count=nums[i];
}else {
count+=nums[i];
}
max=Math.max(max,count);
}
return max;
}
==========================================================================
先把大问题化成小问题 前i个字母能否成功分割
状态转移方程是 j<i&&F(j)&&[j,i-1] 即F(j)为true [j,i-1]也为true 这样可以存储true
例如:给定s=“nowcode” 已知 now 和code 在词典中,在找到cow后就可以从j+1位置找到 i位置单词是否能在词典中找到
举例说明:
例如dict 中 [le ,et ,co ,de]是单词
那么当leetcode字符串传来时
F(1) false ——— F(2) true ——— F(3)false ————F(4)true
F(5) false——— F(6) true ——— F(7)false ——— F(8) true
即判断时可以借助前面的结果来判断 例如F(4)利用F(2)为true 只需要判断后两个字母是否能组成单词即可。
public boolean wordBreak(String s, Set<String> dict) {
if(s.length()==0){
return true;
}
boolean[]array=new boolean[s.length()+1];
for(int i=1;i<=s.length();i++){
//1~i整体范围是一个单词
if(dict.contains(s.substring(0,i))){//substring 是从0到i-1位置
array[i]=true;
continue;
}
for(int j=i-1;j>0;j--){
//部分判断 一部分为前面验证过的true 判断后面的也为true
if(array[j]&&dict.contains(s.substring(j,i))){//substring 是从j位置到i-1位置
array[i]=true;
break;
}
}
}
return array[s.length()];
}
也是这样的字符串拆分 双80%
public boolean wordBreak(String s, List<String> wordDict) {
boolean []array=new boolean[s.length()+1];
for(int i=1;i<=s.length();i++){
if(wordDict.contains(s.substring(0,i))){
array[i]=true;
continue;
}
for(int j=i-1;j>0;j--){
if(array[j]&&wordDict.contains(s.substring(j,i))){
array[i]=true;
break;
}
}
}
return array[s.length()];
}
==============================================================================
给定一个三角矩阵,找出自顶向下的最短路径和,每一步可以移动到下一行相邻的数字
首先确定相邻元素的概念:
1.如果在F(i,j)下一行相邻元素就是 (i+1,j)(i+1,j+1)
如果从下往上推的话,从F(i,j)位置往上的相邻位置就是(i-1,j)(i-1,j-1)
2.状态转移方程是 F(min)=Math.min(F(i-1,j),(i-1,j-1))+a(i,j)// 从相邻位置中找到最小值,之后加上当前位置坐标
注意每一行的第一列和最后一列只有一条路径
第一列F(i,0)=F(i-1,0)+a[i][0]
最后一列 F(i,i)=F(i-1,i-1)+a[i][i]
3.初始状态
F(0,0)=a[0][0]
最终思想是从上往下求(先判断边界情况,第一列只有一条路径,最后一列也只有一条路径都加上当前坐标值就好,)之后找最小路径值就可以了。
public int minimumTotal(List<List<Integer>> triangle) {
List<List<Integer>> list=new ArrayList<>();
for(int i=0;i<triangle.size();i++){
list.add(new ArrayList<>());
}
//先定义初始值 在0位置加上(0,0)坐标的值
list.get(0).add(triangle.get(0).get(0));
//之后遍历判断特殊情况 每一行的第一个 ,每一行的最后一个
for(int i=1;i<triangle.size();i++){
int count=0;//当前路径和
for(int j=0;j<=i;j++){
if(j==0){
count=list.get(i-1).get(j);
}else if(i==j){
count=list.get(i-1).get(j-1);
}else {
count=Math.min(list.get(i-1).get(j),list.get(i-1).get(j-1));
}
list.get(i).add(triangle.get(i).get(j)+count);
}
}
//之后找到list中到目标路径的最小值即可
int len=triangle.size();
int min=list.get(len-1).get(0);
for(int i=1;i<len;i++){
min=Math.min(min,list.get(len-1).get(i));
}
return min;
}
=========================================================================
最后
即使是面试跳槽,那也是一个学习的过程。只有全面的复习,才能让我们更好的充实自己,武装自己,为自己的面试之路不再坎坷!今天就给大家分享一个Github上全面的Java面试题大全,就是这份面试大全助我拿下大厂Offer,月薪提至30K!
CodeChina开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频】
我也是第一时间分享出来给大家,希望可以帮助大家都能去往自己心仪的大厂!为金三银四做准备!
一共有20个知识点专题,分别是:
Dubbo面试专题
JVM面试专题
Java并发面试专题
Kafka面试专题
MongDB面试专题
MyBatis面试专题
MySQL面试专题
Netty面试专题
RabbitMQ面试专题
Redis面试专题
Spring Cloud面试专题
SpringBoot面试专题
zookeeper面试专题
常见面试算法题汇总专题
计算机网络基础专题
设计模式专题
18067267)]
MyBatis面试专题
[外链图片转存中…(img-6bvUUT6e-1630718067268)]
MySQL面试专题
[外链图片转存中…(img-20oPg2SJ-1630718067268)]
Netty面试专题
[外链图片转存中…(img-ZSZAilq7-1630718067269)]
RabbitMQ面试专题
[外链图片转存中…(img-fC7rArcg-1630718067270)]
Redis面试专题
[外链图片转存中…(img-c86Vmq5j-1630718067270)]
Spring Cloud面试专题
[外链图片转存中…(img-su2BotZm-1630718067271)]
SpringBoot面试专题
[外链图片转存中…(img-NQDR5cYZ-1630718067272)]
zookeeper面试专题
[外链图片转存中…(img-mdJVG4x2-1630718067272)]
常见面试算法题汇总专题
[外链图片转存中…(img-tBJ0g1cV-1630718067273)]
计算机网络基础专题
[外链图片转存中…(img-8mtVcOB9-1630718067274)]
设计模式专题
[外链图片转存中…(img-5OT0Eb0q-1630718067274)]