一)按摩师:
这个题和打家劫舍(1)一模一样
我可以跳过1个,也可以跳过两个,还可以跳过三个
1)定义一个状态标识:再进行分析题目的时候,这是一个总左向右推导的模型,根据经验+题目要求
dp[i]表示选择到i位置的时候,此时的最长预约时间,是可以继续划分出两个子状态
2)根据状态表示推导状态转移方程:从最近的一步划分问题
1)f(i)表示如果选取了这个i位置的值,nums[i]必选,此时的最长预约时长
此时选择了i位置的值,那么此时一定是不会选择i-1位置的值,此时f(i)=g(i-1)+array[i]
2)g(i)表示不选择i这个位置的值,nums[i]必不会选,此时选择的最长预约市场就是
2.1)如果i位置不进行选择,如果选择i-1位置的值,那么g(i)=f(i-1)
2.2)如果i位置不进行选择,如果不选择i-1位置的值,那么g(i)=g(i-1)
3)初始化:保证在进行填写dp表的时候数组不会发生越界
g(0)表示不选择0这个位置的时候,最长的预约时长就是g(0)=0
f(0)表示选择0这个位置的时候,最长的预约时长就是f(0)=array[0]
4)填表顺序:保证在进行计算dp[i]的时候,保证他所需要的值已经计算过了,从左向右的时候,两个表同时进行填写
5)返回值:
这个返回值又分为两种情况,要么最后一个位置选,要么最后一个位置不选
return Math.MAX(f(n-1),g(n-1)
初始化操作:保证在进行计算dp[i]的时候,数组不越界
class Solution { public int massage(int[] array) { if(array.length==0||array==null) return 0; //1.创建两个dp表,这个题只要两天不被同时选中即可 int[] f=new int[array.length];//这个dp表表示,如果选择当前位置的值,那么最终计算出来的总预约时长 int[] g=new int[array.length];//这个dp表表示,如果如果不选择当前位置的值,那么最终计算出来的总的预约时长 //2.初始化 f[0]=array[0]; g[0]=0; //3.填表 for(int i=1;i<array.length;i++){ //3.1.如果选择i当前位置的值,前面的那个值i-1一定不会被选中,那么到达i位置的时候所需要的总预约时长是 f[i]=g[i-1]+array[i]; //3.2.如果不选择i位置的值,那么i前面的那一个值是有可能被选中也有可能没有被选中 //如果前面的值被选中g(i)=f(i-1),如果前面的值没有被选中,那么g(i)=g(i-1) g[i]=Math.max(f[i-1],g[i-1]); } //4.返回值 return Math.max(f[array.length-1],g[array.length-1]); } }
二)打家劫舍(2)
1)思路1:
假设考虑偷0号位置,那么在0号位置到array.length-1位置做一次打家劫舍的操作即可
假设不考虑偷0号位置,那么从1号位置到array.length-2这些位置做一次打家劫舍的操作即可
lass Solution { public int rob(int[] array) { //1.处理特殊情况 if(array==null){ return 0; }else if(array.length==1){ return array[0]; }else if(array.length==2){ return Math.max(array[0],array[1]); } int[] f=new int[array.length]; int[] ff=new int[array.length]; int[] gg=new int[array.length]; int[] g=new int[array.length]; f[0]=array[0]; g[0]=0; //选择从0号位置开始偷,肯定就不能选择最后一个位置偷 for(int i=1;i<array.length-1;i++){ f[i]=g[i-1]+array[i];//偷当前的财产 g[i]=Math.max(f[i-1],g[i-1]);//不偷当前的财产,说明当前的i位置的值是不可以加的 } int temp1=Math.max(f[array.length-1-1],g[array.length-1-1]); ff[1]=array[1]; gg[1]=0; //要是选择偷最后一个位置,肯定就不可以选择偷第一个位置 for(int i=2;i<array.length;i++){ ff[i]=gg[i-1]+array[i];//偷当前的财产 gg[i]=Math.max(ff[i-1],gg[i-1]);//不偷当前的财产,说明当前的i位置的值是不可以加的 } int temp2=Math.max(ff[array.length-1],gg[array.length-1]); return Math.max(temp1,temp2); } }
最优解法:2)思路2:可以通过分类讨论,将环形的问题,转化成两个线性打家劫舍1问题
1)假设一定偷0号位置的钱,那么第1个位置肯定不可以偷,最后一个位置肯定也是不可以偷的,那么只需要从(2,n-2)的位置这一段区间内偷的最大钱数即可
2)假设不偷0号位置的钱,那么从1号位置到n-1号位置做一次打家劫舍的操作即可
3)然后我们只需要1情况到2情况的最大值即可
1)确定一个状态表示:根据经验和题目要求,dp[i]表示从起始位置开始到当前位置所偷的钱的最大数目
f(i)表示偷到i位置的时候,偷array[i],此时的最大金额
g(i)表示偷到i位置的时候,不偷array[i],此时的最大金额
2)确定状态转移方程:
偷当前i位置的钱:f[i]=g[i-1]+array[i]
不偷当前位置的钱:g[i]=Math.max(f[i-1],g(i-1));
3)初始化:g(0)=0,f(0)=array[0]
4)填表顺序:从左到右,两个表一起进行填写
class Solution { public int GetRob(int left,int right,int[] array){ //1.创建dp表,创建数组原始规模的大小,多余的格子可以不填 int[] f=new int[array.length]; int[] g=new int[array.length]; //2.初始化 f[left]=array[left]; g[left]=0; for(int i=left+1;i<=right;i++){ f[i]=g[i-1]+array[i]; g[i]=Math.max(f[i-1],g[i-1]); } return Math.max(g[right],f[right]); } public int rob(int[] array) { int n=array.length; if(n==0) return 0; else if(n==1) return array[1]; else if(nums.length==2) return Math.max(nums[0],nums[1]); return Math.max((GetRob(2,n-2,array)+array[0]),GetRob(1,n-1,array)); } }
class Solution { public int GetRob(int left,int right,int[] array){ if(left>right) return 0; //第一种方式是开辟一个和原数组一样的数组,就不用考虑下标的映射关系 //第二种方法就是开辟一个区间范围内的数组,但是需要考虑下标之间的映射关系 int[] f=new int[right-left+1]; int[] g=new int[right-left+1]; f[0]=array[left]; g[0]=0; for(int i=left+1;i<=right;i++){ f[i-left] =g[i-1-left]+array[i]; g[i-left]=Math.max(f[i-1-left],g[i-1-left]); } return Math.max(g[right-left],f[right-left]); } public int rob(int[] array) { int n=array.length; if(n==0) return 0; else if(n==1) return array[0]; return Math.max((GetRob(2,n-2,array)+array[0]),GetRob(1,n-1,array)); } }
三)删除并获得点数:
3.1)题目要求:
1 1 2 2 2 4 5 5 6 6 7 7 7
1)假设我先删5,那么目标值等于5的值会被全部选中,此时删除的就是4和6的值
此时获得的点数就是:5+5=10
2)然后选择2,这个时候选择的是两个2,那么点数是1的元素会被全部删除掉
此时获得的点数就是:2+2+2=8
3)此时选择7,那么目标值是6的元素(已经被删除)和目标值是8的元素会被删除掉(数组中并不存在此元素),此时所获取的点数就是7+7+7=21
所以最终此序列获得的最大点数是:10+8+21
4)假设此时数组是有序的,况且每一个元素只是出现了一次,那么此时这个时候就和打家劫舍问题非常类似了:假设数组的元素是1 2 3 4 5
1 2 3 4 5
此时选择1就不可以选择2了,只能接下来选择3,选择3就不可以选择4了,直接可以选择5,就是不可以选择相邻的元素,因为相邻的元素全部被删除掉了
5)但是实际上这些数可能不是连续的,还有假设你选择删除3,那么3前面的数和3后面的数需要全部进行删除,不能计算到最终的结果里面
3.2)解题思路:
1)确定一个状态表示:这是一个线性模型,一般做题思路就是经验+题目要求
f(i)表示如果选择i下标这个位置的数,所获得的点数的最大值,选择到i位置的时候,nums[i]必选,此时可以获得的最大点数
g(i)表示如果不选择i下标的数,所获得的点数的最大值,表示选择到i位置的时候,nums[i]不进行选择,此时可以获得的最大点数
以某一个位置为结尾,巴拉巴拉
到达i位置的时候,一共有两种情况,要么你选择i位置,要么你不选择i位置
从左向右填表,两个表一起填class Solution { public int deleteAndEarn(int[] nums) { if(nums.length==1) return nums[0]; //1.首先进行预处理操作 //其实也可以遍历原来数组的最大值和最小值,新创建的数组的大小就是MAX-MIN+1即可,也可以看题目给定的数据范围 long[] array=new long[200000]; for(int i=0;i<nums.length;i++){ array[nums[i]]=nums[i]+array[nums[i]]; } // System.out.println(Arrays.toString(array)); // return 10; //2.定义两个数组 long[] f=new long[array.length]; long[] g=new long[array.length]; f[0]=array[0]; g[0]=0; for(int i=1;i<array.length;i++){ f[i]=g[i-1]+array[i]; g[i]=Math.max(f[i-1],g[i-1]); } //3.返回数组中的最大值 return (int)Math.max(f[array.length-1],g[array.length-1]); } }