动态规划总结——线性DP(1)(基于LeetCode题目)

LeetCode dp问题汇总

线性DP

俺的理解就是给定一个数组,遍历过程中不断向前增大dp的范围,感觉有点像01背包,不过消耗是变长的。
在这里插入图片描述
这道题,与什么最大和啊啥的区别就在于他的比较的不是数组本身的子数组,而是自己另外一个数组,解决思路,还是归结到选与不选的问题上来,子问题就是s2子序列匹配s1串的子序列,到最小的子序列肯定一个字符啦。
dp数组的定义:
dp[i][j]的话,就是到i的s2子串有的子序列匹配到j为止的子序列最长匹配多少。(我这里写的优化了下空间,意义一样的,因为每次只需要用到i-1,j或者i,j-1位置的数据)
转移方程:
如果新字符可以匹配上,那么就是上一个子串在该位置匹配时的最大长度+1,
如果不能,就分两种情况,1.看是自己之前匹配上最长串为多大2.上一个串在该处时最长长度,取较大者。
dp[i][j] = if 匹配上,dp[i-1][j]+1 else max(dp[i][j-1],dp[i-1][j]);
(这时dp数组的优化就好想了,只需要将上次的dp[j]位置保存下来,就不怕dp[j]被覆盖了)

‘’ a b a
‘’ 0 0 0 0
a 0 1 1 1
b 0 1 2 2
d 0 1 2 2
c 0 1 2 2
a 0 1 2 2
class Solution {
    public int longestCommonSubsequence(String text1, String text2) {
        int[] dp= new int[text1.length()+1];
        for(int j=0;j<text2.length();j++){
            int last = 0;
            for(int i=0;i<text1.length();i++){   
                int temp = dp[i+1];
                if(text2.charAt(j)==text1.charAt(i)){
                    dp[i+1] = last+1;
                }
                else{
                    dp[i+1] = Math.max(dp[i],temp);
                }
                last=temp;
            }
        }
        return dp[text1.length()];
    }
}

在这里插入图片描述
思路第一题
二分优化

class Solution {
    public int lengthOfLIS(int[] nums) {
        int[] dp = new int[nums.length+1];
        int count = 0;
        dp[0] = Integer.MIN_VALUE;
        for(int i=0;i<nums.length;i++){
            
            if(dp[count]<nums[i]){
                dp[++count] = nums[i];
            }
            else{
                int left= 1,right=count;
                while(left<right){
                    int mid = (right+left)>>1;
                    if(nums[i]>dp[mid]){
                        left = mid+1;
                    }else{
                        right = mid;
                    }
                }
                dp[left] = nums[i];
            }
        }
        return count;
    }
}

在这里插入图片描述
画画图就会发现,上一层(i)和下一层(j)的到达的索引关系为i-> i,i+1 j->j-1,j.
dp[i][j] 的含义,到达第i,j位置最短的路径。
转移方程:dp[i][j] = Min(dp[i-1][j],dp[i-1][j-1])+nums[i][j];
优化方式dp数组与子序列那题类似,只需保存dp[j]位置的值即可,防止覆盖
不过初始化需要注意一下,因为转移方程在0和length-1位置会变为只有一个方向。

//自顶向下,O(n)空间复杂度
class Solution {
   public static int minimumTotal(List<List<Integer>> triangle) {
        int size = triangle.size();
        int[] dp = new int[size];
        dp[0] = triangle.get(0).get(0);
        for (int i = 1; i < size; i++) {
            List<Integer> list = triangle.get(i);
            int pre = dp[0];
            dp[0] += list.get(0);
            for (int j = 1; j < list.size() - 1; j++) {
                int temp = dp[j];
                dp[j] = Math.min(pre, dp[j]) + list.get(j);
                pre = temp;
            }
            if(list.size()!=1) {
                dp[list.size() - 1] = pre + list.get(list.size() - 1);
            }
        }
        int min = dp[0];
        for (int i = 1; i < size; i++) {
            min = Math.min(min, dp[i]);
        }
        return min;
    }
}

在这里插入图片描述
用贪心的思想,如果和还是正数就加上它,如果加起来总和为负了就一点益处都没有了,前面的全部抛弃,再用一个值来记录每个情况下的最大值即可。
动规的话(本质就是多次局部贪心嘛)

class Solution {
    public int maxSubArray(int[] nums) {
            int max = nums[0],pre = nums[0];
            for(int i =1;i<nums.length;i++){
                pre = Math.max(pre,0)+nums[i];
                max = Math.max(pre,max);
            }
            return max;
    }
}

在这里插入图片描述
这个题与上一题的区别就在于需要考虑值为负数时,虽然此时为最小,但是可能之后再来个负数就为最大了,所以我们需要记录一下,最小值(负情况的最大值)和最大值。
在遇到负数时,最大值和最小值交换。

class Solution {
    public int maxProduct(int[] nums) {
        int max=nums[0],tmin=nums[0],tmax=nums[0];
        for(int i=1;i<nums.length;i++){
           if(nums[i]<0){
               int temp = tmax;
               tmax = tmin;
               tmin = temp;
           }
           tmax = Math.max(tmax*nums[i],nums[i]);
           tmin = Math.min(tmin*nums[i],nums[i]);
           max = Math.max(tmax,max);
        }
        return max;
    }
}

在这里插入图片描述
这道题差点把我送走,俺知道它难,没想到这么难。
首先暴力法,要知道能测出每一层楼(0~f)时的最小操作数,子问题就是,要我们选择的就是在哪一楼扔蛋(还有n个)最优,而在x楼扔都面临两个选择,一个蛋碎了,那么可能就是x-1楼,我们还剩n-1个蛋,如果没碎,那么可能就是f-x楼,剩n个蛋,因为要肯定能够全部试出来,所以我们选择次数多的,好了,可以暴力了,深搜来吧。

//暴力没过,能过1/4的数据
class Solution {
    public int superEggDrop(int K, int N) {
        //System.out.println(K+" "+N);
        if(K==1||N==1||N==0){
            return N;
        }
        int min = N;
        for(int i= 1;i<N;i++){
            int max = Math.max(superEggDrop(K-1,i-1),superEggDrop(K,N-i));
            min = Math.min(max+1,min);
        }
        return min;
    }
}

可想而知,计算了很多重复的情况,每个都需要计算到底层。那么我们就把计算过的保存一下吧。

//超时了,能过1/2的数据
class Solution {
    int[][] dp;
    public int superEggDrop(int K,int N){
        dp = new int[N+1][K+1];
        Object[] dd =
                 Arrays.stream(dp).map(x->{
                    return Arrays.stream(x).map(y->{ y =Integer.MAX_VALUE;return Integer.valueOf                    (y);}).toArray();
                }).toArray();
         System.arraycopy(dd, 0, dp, 0, dd.length);
        return superEggDropIn(K,N);
    }
    private int superEggDropIn(int K, int N) {
        //System.out.println(K+" "+N);
        if(K==1||N==1||N==0){
            return N;
        }
        if(dp[N][K]!=Integer.MAX_VALUE){
            return dp[N][K];
        }
        int min = N;
        for(int i= 1;i<N;i++){
            int max = Math.max(superEggDropIn(K-1,i-1),superEggDropIn(K,N-i));
            min = Math.min(max+1,min);
        }
        dp[N][K] = min;
        return min;
    }

啊还是用常规动规刷表的方式写写吧(主要是看了答案,深搜没办法后面的二分优化)
设想一下,dp数据该怎么写(dp选得好,用时就会少)。
按照刚才深搜的思路,dp数组应该为dp[i][j]在i层楼时,有j个蛋,这时最少的扔鸡蛋的次数为多少。
转移方程,从很明显就是选从1~i层楼分别作为起始点,最小的操作数啊,
dp[i][j] = Min(dp[0~i][?])
不完整,蛋碎不碎分情况的啊得,在某一层楼时,如果碎了和没碎可是有两种情况需要考虑的,因为要把每一层楼尝试一遍,所以取两者中的较大者
dp[i][j] = Min(Max(dp[0i][j-1],dp[0i][j]))
规整规整,
dp[i][j] = Mini1 (Max(dp[k][j-1],dp[i-k][j]));
初始化:在0楼肯定0,一个蛋肯定N,一楼肯定1

//超时+1,2/3的数据
class Solution {
    public int superEggDrop(int K, int N) {
        int[][] dp = new int[N+1][K+1];
        Arrays.fill(dp[0],0);
        Arrays.fill(dp[1],1);
//        dp = (int[][]) Arrays.stream(dp).map((x)-> x[0]=0
//        ).toArray();
        for(int i=0;i<dp.length;i++){
            dp[i][0] = 0;
            dp[i][1] = i;
        }
        for(int i=1;i<=N;i++)
            for(int j=2;j<=K;j++) {
                dp[i][j] = i;
                for (int k = 1; k <= i; k++) {
                    dp[i][j] = Math.min(dp[i][j], Math.max(dp[i - k][j], dp[k- 1][j - 1]) + 1);
                }
            }
        return dp[N][K];
    }
 
}

能把俺逼到这个程度优化的也只有这道题了(做的其他题简单嘛,能过就行了 (滑稽)
用二分法优化,一直用二分做搜索的我,看到这个说法是有点懵的,不过看了也是好理解,能用二分优化本质上是序列成单调递增或者单调递减,而这道题,我们稍作思考 (锤子个,琢磨了老久),这个本质上是求一个F(K,N)函数,求它的极值点,可想而知当K不变时,随着楼层的增加,F自然会增大,我们的两种情况可以写为F1(K-1,i-1),F2(K,N-i),如果i为自变量,可以轻易发现,两个操作,一个单增一个单减,它们的交点(或许有多个,不过都在一起的)的i便是我们需要求得的最优扔蛋楼层,该楼情况下操作数便是最优扔蛋次数。
那么如果F1>F2时交点肯定在当前楼层的楼上,如果F1>F2肯定在当前楼层的楼下。

class Solution {
   public int superEggDrop(int K, int N) {
        int[][] dp = new int[N+1][K+1];
        Arrays.fill(dp[0],0);
        Arrays.fill(dp[1],1);
//        dp = (int[][]) Arrays.stream(dp).map((x)-> x[0]=0
//        ).toArray();
        for(int i=0;i<dp.length;i++){
            dp[i][0] = 0;
            dp[i][1] = i;
        }
        for(int i=2;i<=N;i++)
            for(int j=2;j<=K;j++) {
                int left = 1;
                int right = i;
                while (left < right) {
                    int mid = left + (right - left + 1) / 2;

                    int breakCount = dp[mid - 1][j - 1];
                    int notBreakCount = dp[i - mid][j];
                    if (breakCount > notBreakCount) {
                        right = mid - 1;
                    } else {
                        left = mid;
                    }
                }

                dp[i][j] = Math.max(dp[left - 1][j - 1], dp[i - left][j]) + 1;

            }
        return dp[N][K];
    }
 
}

这种思路太秀了吧,逆向思维,题目要我们求N楼K蛋,操作多少次,那我们是不是可以求K蛋F次操作最多能到几楼,当楼层大于等于N时,就是我们要求的了。
dp数组定义如上所说。
转移方程:要想知道到达多少次操作最多能到几楼,它是不是由蛋碎了,蛋-1,操作数-1能确定的楼层数,蛋没碎操作数减1能确定的楼层数,加上本楼层被测试了的总楼层嘛
dp[i][j] = dp[i][j-1],dp[i-1][j-1]+1;
优化一下空间,就是
dp[j] = pre+dp[j]+1;

扫描二维码关注公众号,回复: 10894346 查看本文章
class Solution {
   public int superEggDrop(int K, int N) {
   
    int[] dp = new int[K+1];
    int m = 0;
    while (dp[K] < N) {
        m++;
        int pre =dp[0];
        for (int k = 1; k <= K; k++){
            int temp = dp[k];
            dp[k] = dp[k] + pre + 1;
            pre = temp;
        }
    }
    return m;
}

 
}

在这里插入图片描述
这道题思路与最长子序列类似,不过变成了二维,那么可以先将其中一维排序,再对二维做求最大子序列。

class Solution {
    public int maxEnvelopes(int[][] envelopes) {
        Arrays.sort(envelopes,(x,y)->{
            if(x[0]!=y[0]){
                return x[0]-y[0];
            }else{
                return y[1]-x[1];
            }
        });
        //排序之后根据高度求长子序列
        int[] height = Arrays.stream(envelopes).mapToInt(x->x[1]).toArray();
        return lengthOfLIS(height);
       
    }
    private int lengthOfLIS(int[] nums) {
        int[] dp = new int[nums.length+1];
        int count = 0;
        dp[0] = Integer.MIN_VALUE;
        for(int i=0;i<nums.length;i++){
            
            if(dp[count]<nums[i]){
                dp[++count] = nums[i];
            }
            else{
                int left= 1,right=count;
                while(left<right){
                    int mid = (right+left)>>1;
                    if(nums[i]>dp[mid]){
                        left = mid+1;
                    }else{
                        right = mid;
                    }
                }
                dp[left] = nums[i];
            }
        }
        return count;
    }
}

在这里插入图片描述
还是立足于偷还是不偷的问题,如果偷,那就需要前前个房间的最大值,如果不偷就要前一个房间的最大值,再从两者判断到底偷还是不偷。

class Solution {
    public int rob(int[] nums) {
        int pre=0,curr=0;
        for(int i=0;i<nums.length;i++){
            int temp = curr;
            curr = Math.max(nums[i]+pre,curr);
            pre = temp;
        }
        return curr;
    }
}

在这里插入图片描述
可以将环拆开,拆成0~n-1,1 ~ n,退化为上一个问题。

class Solution {
    public int rob(int[] nums) {
        
        int len = nums.length;
        if(len==1){
            return nums[0];
        }
        return Math.max(rob(nums,0,len-1),rob(nums,1,len));
    }
    public int rob(int[] nums,int start,int end) {
        int pre=0,curr=0;
        for(int i=start;i<end;i++){
            int temp = curr;
            curr = Math.max(nums[i]+pre,curr);
            pre = temp;
        }
        return curr;
    }
}
发布了19 篇原创文章 · 获赞 1 · 访问量 308

猜你喜欢

转载自blog.csdn.net/qq_38732834/article/details/105562393
今日推荐