zoukankan      html  css  js  c++  java
  • leetcode动态规划题组笔记以及总结(持续更新)

    date:20210520
    No:1025
    title:除数博弈
    description:

    爱丽丝和鲍勃一起玩游戏,他们轮流行动。爱丽丝先手开局。
    
    最初,黑板上有一个数字 N 。在每个玩家的回合,玩家需要执行以下操作:
    
    选出任一 x,满足 0 < x < N 且 N % x == 0 。
    用 N - x 替换黑板上的数字 N 。
    如果玩家无法执行这些操作,就会输掉游戏。
    
    只有在爱丽丝在游戏中取得胜利时才返回 True,否则返回 False。假设两个玩家都以最佳状态参与游戏。
    

    解题方法:

    • 动态规划
    • 归纳法

    解题思路:

    • 动态规划:
      • 最后一个数字(即n)的状态和前一个因数的状态有关————(解释:爱丽丝轮到的因数会一步一步导致结果,那么我们倒过来,从前往后推,从爱丽丝轮到因数1和2开始推(因为二者是已知的))
      • 保存爱丽丝会轮到所有因数的状态(也就是说保存爱丽丝在每一个因数处的结果,以供下一个因数查询————记忆化查询)
    • 归纳法:
      • 数学方法,本专题主要记录动态规划,所以在此不赘述其他方法,官方有给题解,可以去leetcode看看

    实现代码:

    • 本题使用python3:
    class Solution:
        def divisorGame(self, n: int) -> bool:
            target = [0 for i in range(n+1)]
            target[1] = 0
            if n <= 1:
                return False
            else:
    
                target[2] = 1  # 若爱丽丝抽到2,则爱丽丝赢
                for i in range(3,n+1):#因为(3,n)不包括n所以用n+1
                    for j in range(1,i//2):
                        if i%j==0 and target[i-j]==0:
                            target[i] = 1
                return target[n] == 1
    
    
    • 动态规划法通过截图:

    date:20210521
    No:剑指offer No.42
    title: 连续子数组的最大和
    description:

    输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。
    
    要求时间复杂度为O(n)。
    

    解题方法:

    • 动态规划
    • 暴力

    解题思路:

    • 动态规划:
      • 构建动态规划数组,每一个格子记录以当前位置为结尾的最大连续序列的和,然后遍历完之后找dp数组中最大的数字,即为答案
    • 暴力:
      • 这没啥说的,遍历,以每个位置为结尾在嵌套一个遍历,把所有的可能都列出来,然后找最大的(简略的说了下,主题是动态规划,在此不再赘述)

    实现代码:

    • 本题使用java:
    public  int maxSubArray(int[] nums) {
            int[] dp = new int[nums.length];
    
            Arrays.fill(dp,Integer.MIN_VALUE);
    
            dp[0] = nums[0];
            for(int i = 1;i<nums.length;++i){
                dp[i] = Math.max(dp[i-1]+nums[i],nums[i]);
            }
            int res = Integer.MIN_VALUE;
            for (int i : dp) {
                res = Math.max(i,res);
            }
            return res;
        }
    
    
    • 通过截图
      image

    date:20210522
    No:764
    title: 使用最小花费爬楼梯
    description:

    数组的每个下标作为一个阶梯,第 i 个阶梯对应着一个非负数的体力花费值 cost[i](下标从 0 开始)。
    
    每当你爬上一个阶梯你都要花费对应的体力值,一旦支付了相应的体力值,你就可以选择向上爬一个阶梯或者爬两个阶梯。
    
    请你找出达到楼层顶部的最低花费。在开始时,你可以选择从下标为 0 或 1 的元素作为初始阶梯。
    
    

    解题方法

    • 剪枝
    • 动态规划

    解题思路:

    • 剪枝:通过递归模拟走楼梯,然后计算走一步和走两步的最小值,最后通过一个过渡数组记录经过的点的最优解,防止再次递归到这个点的时候重复计算,走楼梯过程如下图,会有重复的节点树
      image

    • 动态规划:动态规划的核心就是记忆化搜索,所以定义一个dp数组,每个格子保存当前到位置的最小消耗,最后返回倒数两个节点中的小值即可

    实现代码

    • 剪枝:
    package algorithm;
    
    import java.util.Arrays;
    
    public class MinCostClimbingStairs {
        private static int[] dp;
    
        public static int minCostClimbingStairs(int[] cost) {
            dp = new int[cost.length+1];
            Arrays.fill(dp,0);
            minCostClimbingStairs(cost,-1,dp);
    
            return dp[0];
        }
    
        private static int minCostClimbingStairs(int[] cost,int index,int[] dp){
            if(index>=cost.length){
                return 0;
            }
            int temp = index ==-1?0:cost[index];
    
            if(dp[index+1]!=0) {
                return dp[index+1];
            }
            int one = minCostClimbingStairs(cost, index + 1, dp);
            int two = minCostClimbingStairs(cost, index + 2, dp);
            dp[index+1] = Math.min(one + temp, two + temp);
            return dp[index+1];
        }
    
    
        public static void main(String[] args) {
            int[] cost = {1, 100, 1, 1, 1, 100, 1, 1, 100, 1};
            System.out.println(minCostClimbingStairs(cost));
        }
    }
    
    • 动态规划:
    class Solution {
    
        public  int minCostClimbingStairs(int[] cost) {
            for(int i = 2;i<cost.length;++i){
                cost[i] += Math.min(cost[i-1],cost[i-2]);
            }
    
            return Math.min(cost[cost.length-1],cost[cost.length-2]);
        }
    }
    
    

    通过截图

    image


    date:20210523
    No:面试题 17.16
    title: 按摩师
    description:

    一个有名的按摩师会收到源源不断的预约请求,每个预约都可以选择接或不接。在每次预约服务之间要有休息时间,因此她不能接受相邻的预约。给定一个预约请求序列,替按摩师找到最优的预约集合(总预约时间最长),返回总的分钟数。
    
    注意:本题相对原题稍作改动 
    

    解题方法:

    • 动态规划

    解题思路:

    • 动态规划:
      • 设计一个dp数组,每一个格子保存以当前位置结尾的最大预约时间(可以不包含当前位置预约),最后返回最后一个格子即可;保存策略就是,对比nums[i]+dp[i-2]和dp[i-1]的大小,保存大的值,一次遍历之后,最后一个格子保存的就是整个序列中最大的预约时间

    实现代码

    • 本题使用的语言是python3
    class Solution:
        def massage(self, nums: List[int]) -> int:
            if len(nums)<=0: return 0
            if len(nums)==1: return nums[0]
            dp = []
            dp.append(nums[0])
            dp.append(max(nums[0],nums[1]))
            for i in range(2, len(nums)):
                dp.append(max((dp[i-2]+nums[i]),dp[i-1]))
            return dp[-1]
    

    通过截图

    image


    date:20210524
    No:329
    title: 判断子序列
    description:

    给定字符串 s 和 t ,判断 s 是否为 t 的子序列。
    
    字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。
    
    进阶:
    
    如果有大量输入的 S,称作 S1, S2, ... , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?
    
    
    

    解题方法:

    • 双指针
    • 动态规划

    解题思路:

    • 双指针:在s和t上各安排一个指针,然后遍历t,只要s的对应字符等于t当前位置字符,s的指针加一,如果s的指针到达末尾,则说明s为t的子序列,就返回true,否则如果一个循环都未返还,说明s中有t中不存在的字符,返回false
    • 动态规划:还是那句话,动态规划的核心是记忆化搜索,我们只要维护一个二维数组,记录从第i(t上)个字符开始,下一个字符(26个字母)出现的位置,遍历s时查询t就可以线性化,复杂度为O(1),至于dp数组,从后面开始遍历记录就好;

    实现代码

    • 双指针:
    class Solution {
        public boolean isSubsequence(String s, String t) {
            if(s.equals("")) return true;
            if(s.length()>t.length()) return false;
    int index_s = 0;
            char temp = s.charAt(index_s);
            for(int i = 0;i<t.length();++i){
                if(temp==t.charAt(i)){
                    index_s++;
                    if(index_s==s.length()) return true;
                    temp = s.charAt(index_s);
                }
            }
            return false;
        }
    }
    
    • 动态规划:
    class Solution {
        public boolean isSubsequence(String s, String t) {
            if(s.length()>t.length()) return false;
            if(s.length()==0)return true;
    int n = s.length();
            int m = t.length();
            int[][] dp = new int[m+1][26];//记录每一个字符开始下一个字符的位置
    
            for (int[] ints : dp) {
                Arrays.fill(ints,m);
            }
    
            for(int i = m-1;i>=0;--i){
                for(int j = 0;j<26;++j){
                    if(t.charAt(i)==j+'a'){
                        dp[i][j] = i;//说明该字符在当前位置出现
                    }else{
                        dp[i][j] = dp[i+1][j];//继承上一个状态
                    }
                }
            }
    
            int next = 0;
            for(int i = 0;i<n;++i){
                if(dp[next][s.charAt(i)-'a']==m){//说明该字符不会出现
                    return false;
                }else{
                    next = dp[next][s.charAt(i)-'a']+1;
                }
            }
            return true;
        }
    }
    

    通过截图

    • 双指针:
      image

    • 动态规划:
      image


    date:20210525
    No:1641
    title: 统计字典序元音字符串的数目
    description:

    给你一个整数 n,请返回长度为 n 、仅由元音 (a, e, i, o, u) 组成且按 字典序排列 的字符串数量。
    
    字符串 s 按 字典序排列 需要满足:对于所有有效的 i,s[i] 在字母表中的位置总是与 s[i+1] 相同或在 s[i+1] 之前。
    
    

    解题方法:

    • 动态规划

    解题思路:

    • 还是那句话:动态规划的核心就是记忆化搜索以及实时更新记忆,该题有如下规律(别的博客学来的,我太菜了/大哭):

    image

    实现代码:

    • 本体使用java语言
    class Solution {
        public int countVowelStrings(int n) {
    	int[] dp = {1,1,1,1,1};
            for(int i = 1;i<n;++i){
                for(int j = 3;j>=0;--j){
                    dp[j] += dp[j+1];
                }
            }
            int sum = 0;
            for (int i : dp) {
                sum += i;
            }
            return sum;
        }
    }
    
    

    通过截图:

    image


    date:20210528
    No:1314
    title:矩阵区域和
    description:

    给你一个 m x n 的矩阵 mat 和一个整数 k ,请你返回一个矩阵 answer ,其中每个 answer[i][j] 是所有满足下述条件的元素 mat[r][c] 的和: 
    
    · i - k <= r <= i + k,
    · j - k <= c <= j + k 且
    · (r, c) 在矩阵内。
    

    解题方法

    • 动态规划(记忆化搜索||二维前缀和)

    解题思路:

    • 动态规划:我们算的res矩阵实质上是在求原矩阵各种的区域和,如果我们能把每个前缀的和记录下来,那么在计算res矩阵的时候,就可以靠这个矩阵(后面称为dp矩阵)来记忆化搜索,可以节省大量时间(如果直接计算区域和,则每次需要算k*k次),具体如图示
      image

    image

    实现代码

    • 本题使用java实现
    class Solution {
        public int[][] matrixBlockSum(int[][] mat, int k) {
            int[][] res = new int[mat.length][mat[0].length];
            int[][] dp = new int[mat.length][mat[0].length];
            for (int i = 0; i < dp.length; ++i) {
                for(int j = 0;j<dp[0].length;++j){
                    dp[i][j] = (j>=1?dp[i][j-1]:0) + mat[i][j];
                }
                for(int j = 0;j<dp[0].length;++j){
                    dp[i][j] += (i>=1?dp[i-1][j]:0);
                }
            }
            for(int i = 0;i<res.length;++i){
                for(int j = 0;j<res[0].length;++j){
                    int temp1 = (i+k>= res.length?res.length-1:i+k);
                    int temp2 = (j+k>= res[0].length?res[0].length-1:j+k);
                    int temp3 = i-k-1;
                    int temp4 = j-k-1;
                    res[i][j] = dp[temp1][temp2] - (temp3<0?0:dp[temp3][temp2])
                            - (temp4<0?0:dp[temp1][temp4]) + (temp3<0||temp4<0?0:dp[temp3][temp4]);
                }
            }
            return res;
        }
    }
    

    通过截图

    image


    date:20210616
    No:5
    title:最长回文子串
    description:

    给你一个字符串 s,找到 s 中最长的回文子串
    

    解题方法

    • 动态规划(记忆化搜索)

    解题思路

    • 动态规划:将字符串的每个字字符串是否为回文串记录下来,这样搜索的时候复杂度就是O(1),而暴力搜索最耗费时间的就是判断字符串是否为回文串(普通方法就是遍历所有可能,然后每一个字符串都通过一次判断方法,这样花销太大)

    实现代码

    • 本题使用java语言实现
    class Solution {
        public String longestPalindrome(String s) {
        if(s.length()==1||s.length()==0)return s;
           boolean[][] dp = new boolean[s.length()][s.length()];
    	//2到8是否为回文串,charAt(2) == charAt(8) && dp[3][7]
    	//1到3是否为回文串,charAt(1)==charAt(3)&& dp[2][2]
    	
    	for(int i = 0;i<s.length();++i){
    		dp[i][0] = true;
    	}
    	Arrays.fill(dp[s.length()-1],true);
    	for(int i = s.length()-2;i>=0;--i){
    		for(int j = 1;j<s.length();++j){
    			dp[i][j] = i>=j?true:((s.charAt(i) == s.charAt(j)) && dp[i+1][j-1]);
    			//填充dp数组,将字符串的状态保存
    		}
    	}
    
    	//for(int i = 0;i<dp.length;++i){
    		//for(int j = 0;j<dp.length;++j){
    			//System.out.print(dp[i][j]);
    		//}
    		//System.out.println();
    	//}
    	int res_i = -1;//表示最长回文序列的起始坐标
    	int res_j = -1;//表示最长回文序列的末尾坐标
    	int sum = 0;//表示最长回文序列的长度
    	for(int i = 0;i<s.length();++i){
    		for(int j = s.length()-1;j> i ;--j){
    			int temp = j-i;
    			if(dp[i][j]&&temp>sum){
    				res_i = i;
    				res_j = j;
    				sum = temp;	
                    //System.out.println(res_i);
                    //System.out.println(res_j);
                    //System.out.println(sum);	
    			}
    		}
    	}
        if(res_j==-1)return s.charAt(0)+"";
    	return s.substring(res_i,res_j+1);
        }
    }
    

    通过截图

    image

  • 相关阅读:
    CoreData
    转场动画
    java基础(8)
    java基础(7)
    java基础(6)
    java基础(5)
    java基础(4)
    java基础(3)
    java基础(2)
    java基础(1)
  • 原文地址:https://www.cnblogs.com/lavender-pansy/p/14789370.html
Copyright © 2011-2022 走看看