动态规划粗浅理解

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);*/



    }


}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值