package boke; import java.util.ArrayList; import java.util.List; import java.util.PriorityQueue; public class bo3 { //动态规划笔记 //DP定义:是分支的延伸,在将大问题化为小问题的过程中,保存这些小问题已经处理好的结果,以供后面的使用 //动态规划的特点 /* 1.把原来的问题分解成了几个相似的子问题 2.所有的子问题都只需要解决一次 3.存储子问题的解 */ //例1: /* 字符串分割 给你一个字符串 s 和一个字符串列表 wordDict 作为字典。请你判断是否可以利用字典中出现的单词拼接出 s 。 注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。 定义F[n]为前n个字符能否用字典拼出; F[n]可分解为 1.{F[i](1<=i<=n)是否能用字典拼出&&字符串s从i开始到结束是否能用字典拼出} 2.F[n]直接可由字典拼出(一次拼出) */ public static boolean wordBreak(String s, List<String> wordDict) { boolean F[]=new boolean[s.length()]; F[0]=wordDict.contains(s.charAt(0)+""); for (int i = 1; i <F.length; i++) { for (int j = 0; j <i; j++) { // 第一种情况 F[i]=F[j]&&wordDict.contains(s.substring(j+1,i+1))||F[i]; //1. ||F[i]的作用;当F[i]某一时刻被设置为true,如果j++后仍然小于i时保证F[i]始终为true; //2. s=abb 字典为a,b时 求F[2]时,F[2]=F[0]&&wordDict.contains(s.substring(1,3)); // 及a存在,但bb不存在在字典中,我们把F[2]暂时设置为false;在我们看来,abb可以由字典拼出,但输出的结果为false // 其实abb这个字符被分解成ab与b时会被设置为true 所以代码整体不会出错 //从整体上来讲一个字符串能被字典拼出,则一定可以分解为前面1到多个作为一个整体能被拼出和最后一个可以被拼出,我们无需考虑其他情况 } F[i]=F[i]||wordDict.contains(s.substring(0,i+1)); } return F[F.length-1]; } //例2 /* 三角矩阵 给出一个三角形,计算从三角形顶部到底部的最小路径和,每一步都可以移动到下面一行相邻的数字, 例如,给出的三角形如下: [[20],[30,40],[60,50,70],[40,10,80,30]] 最小的从顶部到底部的路径和是20 + 30 + 50 + 10 = 110。 注意: 如果你能只用O(N)的额外的空间来完成这项工作的话,就可以得到附加分,其中N是三角形中的行总数。 通过观察发现: 从第1层开始遍历到倒数第二层; 每一层中除第一个数最后一个数需要特殊处理,其余的值往下一层中正下方和正下方加一位置加,下一层的值接受上一层正上方的值和正上方-1的值,并取其最小值,然后 在加到当前数中 即 F[i+1][j]=Math.min(F[i][j-1],F[i][j])+F[i+1][j]; 当j==第一个数时 和j==最后一个数时 F[i+1][0]=F[i][0]+F[i+1][0]; F[i+1][i+1]=F[i][i]+F[i+1][i+1]; 最后遍历最后一层,找到最小值即可; 即F[i][j]为从顶端开始到第i行j列的最小路径和; F[i][j]可分解为 F[i-1][j]与F[i-1][j-1]的最小值加F[i][j]; */ public static int minimumTotal(ArrayList<ArrayList<Integer>> triangle) { int F[][]=new int[triangle.size()][triangle.size()]; for (int i = 0; i < F.length; i++) { for (int j = 0; j <=i; j++) { F[i][j]=triangle.get(i).get(j); } } for (int i = 0; i <F.length-1; i++) { F[i+1][0]=F[i][0]+F[i+1][0]; for (int j = 1; j <=i ; j++) { F[i+1][j]=Math.min(F[i][j-1],F[i][j])+F[i+1][j]; } F[i+1][i+1]=F[i][i]+F[i+1][i+1]; } int min=F[F.length-1][0]; for (int i = 1; i <F.length; i++) { min= min>F[F.length-1][i]?F[F.length-1][i]:min; } return min; } //3. 01背包 public static int backPackII(int m, int[] a, int[] v) { // 有 n 个物品和一个大小为 m 的背包. 给定数组 A 表示每个物品的大小和数组 V 表示每个物品的价值. // 问最多能装入背包的总价值是多大? /* 问题分析: 设物品是按编号往背包中放 设F[i][j]为背包中放或不放第i个物品,容量为j时能装入背包的最大总价值 当物体的体积小于背包的容量时,放不进去,当前的价值为F[i-1][j];(子问题) 当物体的体积大于背包的容量时,如果放进去总的价值能增加,就放,否则不放 即看不放第i个物体的价值 和 放第i个物体的价值加上放完后剩余的空间能存储的物品的价值 中的最大值 if(a[i]<j){ F[i][j]=F[i-1][j]; } else{ F[i][j]=Math.max(F[i-1][j],F[i-1][j-a[i]]+v[i]); } 理解: 给定物品的大小 a[]=[0,1, 2, 5, 6] 对应的价值 v=[0,1, 6, 18, 22] 背包能装的最大重量为 capicity=11 相当于想往背包里一个个的放物品,从1开始一直放到n j=0(空间为0) j=1 2 3 4 5 6 7 8 9 10 11 放第1个 0 1 1 1 1 1 1 1 1 1 1 1 //当背包空间为0时,无法放物品,为1时可以放,因为后续没有物品可以放所以总价值一直为1 放第2个 0 1 6 7 7 7 7 7 7 7 7 7 // 当背包容量为2时,出现了一个v[2]=6,比上一阶段大,所以设置F[2][2]=6;当j==3时放完第二个数后有一个空闲位置,可以用来放v[1]=1;结果为7 放第3个 0 1 6 7 7 18 19 24 25 25 25 25 //当a[i]<j时,当前物品放不进去,就相当于上一阶段,即0--j-1时,把前面的值搬过来,当j==5时,可以放v[3]=18;并且 放v[3]比不放v[3]大,即v[3]>F[2][5]=7 即F[3][5]=18;当j==6时,就会多出一个新的空余空间;即看F[2][j-v[3]]:剩余空间能放物品的最大值,当 当前的总价值(F[i-1][j-a[i]]+v[i])大于上一阶段的总价值(F[i-1][j])时,更新F[3][j]; i==2是因为当我们把第三个物体放进去了,肯定物品数量少了个当前值,就在上一阶段中选 即F[i][j]=Math.max(F[i-1][j],F[i-1][j-a[i]]+v[i]); 放第4个 0 1 6 7 7 18 22 24 28 29 29 40 */ int n=a.length; int F[][]=new int[n+1][m+1]; for (int i = 1; i <=n; i++) { for (int j = 1; j <=m; j++) { if(j>=a[i-1]){ //a[i-1]代表第i个 数组a[1,2,5,6] v[1,6,18,22] F[i][j]=Math.max(F[i-1][j],F[i-1][j-a[i-1]]+v[i-1]); } else { F[i][j]=F[i-1][j]; } } } return F[n][m]; } //4.回文串分割 //给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是回文。返回符合要求的 最少分割次数 //设F[i]:代表把前i个字符分割成子串,使得每个子串都是回文串的最小分割次数 //那么F[i]=min(F[j]+s除去前j个是否为回文串) (0<j<i) //当前i个字符作为一个整体是一个回文串时需要处理一下 // 判断字符串的所有字串是否为回文串: /* 设a[i][j]:从i开始到j的串是否为回文串 a[i]:记为字符串的第i个字符 if(a[i]==a[j]) { a[i][j]=a[i+1][j-1];//如果前后相等看从i+1开始到j-1的串是否为回文串 如果i与j之间的差值为2或1时,即aba/aa 时是最小的回文串 差值为1/0也行(aba--b)差值为0 } else{ a[i][j]=false; } */ /* */ public static int countSubstrings(String s) { //a[][]是用来s的字串是否是回文串 boolean a[][]=new boolean[s.length()][s.length()]; for (int i = 0; i <s.length(); i++) { for (int j = 0; j <=i; j++) { if (s.charAt(i) == s.charAt(j)) { if (i - j < 2){ a[j][i]=true; } else { a[j][i]=a[j+1][i-1]; } } } } /* int F[][]=new int[s.length()][s.length()]; for (int i = 0; i < F.length; i++) { F[i][i]=1; } for (int i = 2,k=1; i <=F.length; i++,k++) { for (int j = 0; j < F.length-k; j++) { if(a[j][j+k]){ F[j][j+k]=1; }else { F[j][j+k]=k+1; for (int l = j; l <j+k ; l++) { F[j][j+k]=Math.min(F[j][j+k],F[j][l]+F[l+1][j+k]); } } } } return F[0][F.length-1]-1; */ int f[]=new int[s.length()]; for (int i = 0; i <f.length; i++) { f[i]=i+1; } for (int i = 0; i <f.length; i++) { if(a[0][i]){ f[i]=0; } for (int j = 0; j <i; j++) { if(a[j+1][i]) { f[i] = Math.min(f[j] + 1, f[i]); } } } return f[f.length-1]; } //5.不同的子序列 /* 给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。 字符串的一个 子序列 是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。 (例如,"ACE"是"ABCDE"的一个子序列,而"AEC"不是) 分析:设F[i][j]为s的前i个字符中包含t的前j个字符的个数 当s[i]==t[j]时,代表s的最后一个字符与t的最后一个字符相等,这时我们有两种选择(最终答案为两种选择之和): 1.把s[i],t[j]计算在内;看s[i-1]包含t[j-1]的个数 即F[i-1][j-1] 例如:s="abcabcabc" t="abc" i==s.length-1 ,j=t.length-1 s[i]==t[j] 意思就是最后一个字符匹配上了,我们只需要考虑”abcabcab"中包含多少个“ab"(包含6个),那么s[i][j] 的值为F[i-1][j-1]; 2.当s[i]==t[j]时,我们可以不选择s[i],s[j] 即看s[i-1]中包含t[j]的个数:F[i-1][j]; 例如:s="abcabcabc" t="abc" i==s.length-1 ,j=t.length-1 s[i]==t[j] 意思就是最后一个字符匹配上了,但我们不选择它,我们看”abcabcab"中包含多少个”abc“,那么s[i][j]的 值为F[i-1][j] 总的F[i][j]=F[i-1][j]+F[i-1][j-1]; 当s[i]!=t[j]时,只能在s的前i-1个中看是否有t;即F[i][j]=F[i-1][j] 总的方程为: if(s[i]==t[j]){ F[i][j]=F[i-1][j]+F[i-1][j-1]; } else{ F[i][j]=F[i-1][j]; } */ public int numDistinct(String s, String t) { int F[][]=new int[s.length()][t.length()]; //初始条件 //写代码时F[i][j] i代表s从头开始到下标为i ,j同理 即F[i][j]代表s的前i+1个字符 包含 t的前j+1个字符 的个数 if(s.charAt(0)==t.charAt(0)){ F[0][0]=1; } for (int i = 1; i <s.length(); i++) { if(s.charAt(i)==t.charAt(0)){ F[i][0]=F[i-1][0]+1; } else { F[i][0]=F[i-1][0]; } } for (int j = 1; j <t.length(); j++) { F[0][j]=0; } //主要代码 for (int i = 1; i <s.length(); i++) { for (int j = 1; j <t.length(); j++) { if(s.charAt(i)==t.charAt(j)){ F[i][j]=F[i-1][j]+F[i-1][j-1]; } else{ F[i][j]=F[i-1][j]; } } } return F[s.length()-1][t.length()-1]; } //6.股票买卖 /* 穷举所有可能性 定义dp[i][k][0 or 1]: i:第i天 k:剩余交易次数,当交易次数为0时,无法交易(从买股票开始到出售完结束视为一次交易) 0:未持有股票,当没有股票时只能进行买股票操作或者继续等待 1:持有股票,当有股票时只能进行抛出股票操作或者继续等待 dp[i][k][0]:第i天,剩余交易次数为k,并未持有股票的最大利润(可以理解为股票已卖出或未买入) dp[i][k][1]:第i天,剩余交易次数为k,并已持有股票的最大利润(可以理解为股票为卖出) v[i]:第i天的股票价格 状态转移: 0:未持有股票,当没有股票时只能进行买股票操作或者继续等待 1:持有股票,当有股票时只能进行抛出股票操作或者继续等待 dp[i][k][0]=Math.max(dp[i-1][k][1]+v[i],dp[i-1][k][0]) // 第i天未持有股票,要么第i-1天未持有股票选择了继续保持,及第i天的输入和第i-1天一致(没有进行操作) //要么第i-1天持有股票(dp[i-1][k][1]),今天卖了(v[i]),即第i天的钱为 dp[i-1][k][1]+v[i]; dp[i][k][1]=Math.max(dp[i-1][k][1],dp[i-1][k-1][0]-v[i]) //第i天持有股票,要么第i-1天持有股票今天没进行操作,要么第i-1天未持有股票今天买了 //k-1是因为 从买到卖算为一次操作,无论卖的时候k-1还是买的时候k-1其实是一样的 */ //给你一个整数数组 prices ,其中prices[i] 表示某支股票第 i 天的价格。 //在每一天,你可以决定是否购买和/或出售股票。你在任何时候最多只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。 //返回 你能获得的最大利润。 // public int maxProfit(int[] prices) { //根据题意,不限制买卖次数,即不考虑k int dp[][]=new int[prices.length][2]; dp[0][1]=-prices[0]; //dp[i][0/1]:代表第i+1天 dp[0][0]=0; for (int i = 1; i <prices.length; i++) { dp[i][0]=Math.max(dp[i-1][1]+prices[i],dp[i-1][0]); dp[i][1]=Math.max(dp[i-1][1],dp[i-1][0]-prices[i]); } return dp[prices.length-1][0]; } //给定一个整数数组prices,其中第prices[i]表示第i天的股票价格 。 //设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票): //卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。 //思想:这题是限制买入的,所以我们需要确定买入前一天的状态 //如果前一天的状态为卖出,我们只能在前两天买入 //如果前一天的状态为保持上一状态不变,即还是前两天的状态 //所以买入时只需考虑前两天的状态即可 public int maxProfit1(int[] prices) { //根据题意,不限制买卖次数,即不考虑k if(prices.length==1){ return 0; } int dp[][]=new int[prices.length][2]; dp[0][1]=-prices[0]; //dp[i][0/1]:代表第i+1天 dp[0][0]=0; dp[1][1]=Math.max(dp[0][1],-prices[1]); dp[1][0]=Math.max(prices[1]-prices[0],0); for (int i = 2; i <prices.length; i++) { dp[i][0]=Math.max(dp[i-1][1]+prices[i],dp[i-1][0]); dp[i][1]=Math.max(dp[i-1][1],dp[i-2][0]-prices[i]); } return dp[prices.length-1][0]; } public int maxProfit(int max_k, int[] prices) { //有k限制 if(prices.length<=1){ return 0; } //因为当第i天只有一次机会时,我们可以选择 在当天买股票 和 看前1--i-1天有一次机会时买股票消耗的钱 在他们之间选择消耗钱最小的方案 //即 0-prices[i]与dp[i-1][1][1] // 如果我们加上第i天有0次机会时,即dp[i][0][1/0]=0;且适用于 dp[i][k][1]=Math.max(dp[i-1][k][1],dp[i-1][k-1][0]-prices[i]); // 就可以少一个判断i==1的if语句 int dp[][][]=new int[prices.length][max_k+1][2]; for (int k =1; k <max_k+1; k++) { dp[0][k][0]=0; dp[0][k][1]=-prices[0];} for (int i = 1; i <prices.length; i++) { for (int k =1; k <max_k+1; k++) { dp[i][k][0]=Math.max(dp[i-1][k][0],dp[i-1][k][1]+prices[i]); dp[i][k][1]=Math.max(dp[i-1][k][1],dp[i-1][k-1][0]-prices[i]); //当k==1(一次交易机会)时,需要处理 dp[i-1][k-1][0] ,因为此时比较的时买股票花费最小的钱,需要比较 -[prices]与dp[i-1][k][1]中最大的 // 即设 dp[i-1][k-1][0] =0;满足条件 //当k>1时,即考虑本次买股票+之前有k-1次机会的最大值 和本次不买股票+之前有k次机会的最大值 } } return dp[prices.length-1][max_k][0]; } public static int maxProfit2(int[] prices) { //当k==2 时的另类方法 // fir[i] 表示从第1天开始到第i天可以获得的最大利润 // las[i] 表示从第i天开始到最后一天可以获得的最大利润 //进行遍历,从 0--prices.length-1;求得fir[i]+las[i]的最大值 int fir[]=new int[prices.length]; fir[0]=0; int max=prices[0]; int min=prices[0]; for (int i = 1; i <fir.length; i++) { //向后面添加元素 if(prices[i]>max){ max=prices[i]; //当出现最大值,最大值减去最小值即为最优解,更新最大值 fir[i]=prices[i]-min; } else if(prices[i]<min){ min=prices[i]; //当出现最小值,最优解为上一阶段的最优解,更新最小值 fir[i]=fir[i-1]; } else { fir[i]=Math.max(fir[i-1],prices[i]-min);//当出现中间值时,因为不确定最小值更新了没,所以不确定rices[i]-min与之前的最大值fir[i-1] //的关系,所以要比较一下 } } int las[]=new int[prices.length]; las[las.length-1]=0; max=prices[prices.length-1]; min=prices[prices.length-1]; for (int i = las.length-2; i >=0; i--) { //参考fir[] if(prices[i]>max){ max=prices[i]; las[i]=las[i+1]; } else if(prices[i]<min){ min=prices[i]; las[i]=max-prices[i]; } else { las[i]=Math.max(las[i+1],max-prices[i]); } } max=fir[0]+las[0]; for (int i = 0; i <prices.length; i++) { max=Math.max(max,fir[i]+las[i]); } return max; } //求子集 /* 给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。 解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。 输入:nums = [1,2,3,4] 输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3],[4],[1,4],[2,4],[1,2,4],[3,4],[1,3,4],[2,3,4],[1,2,3,4]] 找规律:当我们求1,2,3这三个元素构成的子集时 3 和[] 可以构造 [3] 3 和[1] 构成[1,3] 3 和[2] 构成[2,3] 3 和[1,2] 构成 [1,2,3] 即 求前i个数能构成多少个子集就得从前i-1个数构成的子集数在拼接上第i个数 */ public static List<List<Integer>> subsets(int[] nums) { //求子集 List<List<Integer>>list=new ArrayList<>(); list.add(new ArrayList<>()); List<Integer>list1=new ArrayList<>(); list1.add(nums[0]); list.add(list1); for (int i = 1; i <nums.length; i++) { int size=list.size(); list1=new ArrayList<>(); list1.add(nums[i]); list.add(list1); for (int j = 1; j <size; j++) { list.add(subsets(list.get(j),nums[i])); } } return list; } public static List<Integer> subsets(List<Integer> list1,int n) { // 给list1加上当前值 List<Integer> temp=new ArrayList<>(); for (int i = 0; i <list1.size(); i++) { temp.add(list1.get(i)); } temp.add(n); return temp; } //编辑距离 /* 给你两个单词word1 和word2, 请返回将word1转换成word2 所使用的最少操作数 。 你可以对一个单词进行如下三种操作: 插入一个字符 删除一个字符 替换一个字符 设F[i][j]为把word1的前i个元素转化为word2前j个元素所使用的最小距离 F[i][j]可由已经处理好的状态+插入/删除/替换操作得到(一步操作) 例如,当word1的第i个元素与word2的第j个元素不相等时(相等直接看F[i-1][j-1]) F[i][j]=F[i-1][j]+1 :可以通过删除第i个元素看word1的前i-1个元素转化为word2前j个元素所使用的最小距离;+1代表的是当前删除操作 F[i][j]=F[i-1][j-1]+1 :可以通过把word1的第i个元素转化为word2的第j个元素,再看word1的前i-1个元素转化为word2前j-1个元素所使用的最小距离;+1代表的当前替换操作 F[i][j]=F[i][j-1]+1:在word1中添加word2的第j个元素(等价于删掉word2的第j个元素)再看word1的前i个元素转化为word2前j-1个元素所使用的最小距离 然后看那个F[i][j]的值最小,取哪个 */ public static int minDistance(String word1, String word2) { if(word1.length()==0){ return word2.length(); } if(word2.length()==0){ return word1.length(); } int n=word1.length()+1; int m=word2.length()+1; int F[][]=new int[n][m]; int ii=n+m; for (int i = 1; i <n ; i++) { if(word1.charAt(i-1)!=word2.charAt(0)){ F[i][1]=i; } else { ii=i; break; } } for (int i = ii; i < n; i++) { F[i][1]=i-1; } ii=n+m; for (int i = 1; i <m; i++) { if(word2.charAt(i-1)!=word1.charAt(0)){ F[1][i]=i; } else { ii=i; break; } } for (int i = ii; i < m; i++) { F[1][i]=i-1; } for (int i = 2; i <n; i++) { for (int j = 2; j < m; j++) { if(word1.charAt(i-1)==word2.charAt(j-1)){ F[i][j]=F[i-1][j-1]; } else { F[i][j]=Math.min(Math.min(F[i][j-1],F[i-1][j]),F[i-1][j-1])+1; } } } return F[n-1][m-1]; } public static void main(String[] args) { // subsets(new int[]{1,2,3}); System.out.println( minDistance("ta" ,"appea")); //maxProfit2(new int []{7,1,5,3,6,4}); /* int[] arr = new int[]{3,2,3,1,2,4,5,5,6}; int k = 5; findKthLargest3(arr,k);*/ } }
动态规划粗浅理解
最新推荐文章于 2024-09-12 18:58:16 发布