• 【动态规划】动态规划经典例题 力扣&牛客


    跳台阶 BM63 简单

    题目链接
    在这里插入图片描述
    代码:

    public class Solution {
    
        public int jumpFloor (int number) {
            
            if(number==1||number==2)return number;
            // 跳一步或两步
            else return jumpFloor(number-1) + jumpFloor(number-2);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    跳台阶扩展 JZ71 简单

    题目链接
    描述
    一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶(n为正整数)总共有多少种跳法。
    在这里插入图片描述
    思路:

    • F(n) = F(n-1)+F(n-2)+F(n-3)+…F(1)+1=F(n-1)+F(n-1) = 2*F(n-1)
    • F(3)=F(2)+F(1)+1

    跳上第3阶台阶,可以从第2阶、第1阶跳上去,也可以从第0阶直接1步跳上第3阶。

    代码1:

    public class Solution {
        // 当前无优化,可添加记忆数组优化时间复杂度
        public int jumpFloorII (int number) {
    
            if(number == 1) return 1;
            int count = 1;
            for(int i = 1;i < number;i++){
                count+=jumpFloorII(number-i);
            }
            return count;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    代码2:

    public class Solution {
        
        public int jumpFloorII (int number) {
    
            if(number == 1) return 1;
            return 2*jumpFloorII(number-1);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    打家结舍 LC198 中等

    你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

    给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

    示例 1:
    输入:[1,2,3,1]
    输出:4
    解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
         偷窃到的最高金额 = 1 + 3 = 4 。
    
    示例 2:
    输入:[2,7,9,3,1]
    输出:12
    解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
         偷窃到的最高金额 = 2 + 9 + 1 = 12 。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    代码:

    class Solution {
        int[] memory;// 备忘录
    
        public int rob(int[] nums) {
            int len = nums.length;
            memory = new int[len];
            Arrays.fill(memory,-1);// 注意填充-1
    
            return DP(nums,0,len-1,0);
        }
        // 能打劫的范围[start,end],从i家开始选择是否打劫
        public int DP(int[] nums,int start,int end,int i){
            if(i==end)return nums[i];
            if(i>end)return 0;
    
            if(memory[i]!=-1)return memory[i];// 查找备忘录
    
            memory[i] = Math.max(
                DP(nums,start,end,i+1),// 不选
                nums[i]+DP(nums,start,end,i+2) // 选 隔一家在决定选择
            );
            return memory[i];
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    打家劫舍2 LC213中等

    题目链接

    你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。

    给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。

    示例 1:
    
    输入:nums = [2,3,2]
    输出:3
    解释:你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。
    示例 2:
    
    输入:nums = [1,2,3,1]
    输出:4
    解释:你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。
         偷窃到的最高金额 = 1 + 3 = 4 。
    示例 3:
    
    输入:nums = [1,2,3]
    输出:3
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    思路:1 2 3 4 5围成一圈,即1与5不能同时选,则可看作是从1 2 3 4里选以及从2 3 4 5里选,取两者最大即可。

    代码:

    class Solution {
        int[] memory;// 备忘录
    
        public int rob(int[] nums) {
    
    
            int len = nums.length;
            if(len==1)return nums[0];//base case 
    
            memory = new int[len];
    
            Arrays.fill(memory,-1);// 注意填充-1
            int r1 =  DP(nums,0,len-2,0);
    
            Arrays.fill(memory,-1);// 注意填充-1
            int r2 =  DP(nums,1,len-1,1);
    
            return Math.max(r1,r2);
    
        }
        // 能打劫的范围[start,end],从i家开始选择是否打劫
        public int DP(int[] nums,int start,int end,int i){
            if(i==end)return nums[i];//base
            if(i>end)return 0;//base
    
            if(memory[i]!=-1)return memory[i];// 查找备忘录
    
            memory[i] = Math.max(
                DP(nums,start,end,i+1),// 不选
                nums[i]+DP(nums,start,end,i+2) // 选 隔一家在决定选择
            );
            return memory[i];
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    最长连续递增序列 LC674 简单

    题目链接
    给定一个未经排序的整数数组,找到最长且 连续递增的子序列,并返回该序列的长度。

    连续递增的子序列 可以由两个下标 l 和 r(l < r)确定,如果对于每个 l <= i < r,都有 nums[i] < nums[i + 1] ,那么子序列 [nums[l], nums[l + 1], …, nums[r - 1], nums[r]] 就是连续递增子序列。

    示例 1:
    
    输入:nums = [1,3,5,4,7]
    输出:3
    解释:最长连续递增序列是 [1,3,5], 长度为3。
    尽管 [1,3,5,7] 也是升序的子序列, 但它不是连续的,因为 5 和 7 在原数组里被 4 隔开。 
    示例 2:
    
    输入:nums = [2,2,2,2,2]
    输出:1
    解释:最长连续递增序列是 [2], 长度为1。
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    代码:

    class Solution {
        public int findLengthOfLCIS(int[] nums) {
            if(nums.length==1)return 1;
    
            int finalresult = 0;
            // 以下标 i为结尾的数组的连续递增的子序列长度为 dp[i]
            int[] dp = new int[nums.length]; 
            Arrays.fill(dp,1);
            for(int i = 1; i < nums.length;i++){
                if(nums[i-1]<nums[i]){
                    dp[i] = dp[i-1]+1;
                }
                finalresult = Math.max(finalresult, dp[i]);
            }
            return finalresult;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    class Solution {
        public int findLengthOfLCIS(int[] nums) {
            if(nums.length==1)return 1;
    
            int finalresult = 0;
            int temresult = 1;
            for(int i = 0; i < nums.length-1;i++){
                if(nums[i]<nums[i+1]){
                    temresult++;
                }else{
                    temresult=1;
                }
                finalresult = Math.max(finalresult,temresult);
            }
            return finalresult;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    乘积最大子数组LC152 中等

    题目链接

    给你一个整数数组 nums ,请你找出数组中乘积最大的非空连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。

    测试用例的答案是一个 32-位 整数。

    子数组 是数组的连续子序列。

    示例 1:
    
    输入: nums = [2,3,-2,4]
    输出: 6
    解释: 子数组 [2,3] 有最大乘积 6。
    示例 2:
    
    输入: nums = [-2,0,-1]
    输出: 0
    解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    代码:

    class Solution {
        public int maxProduct(int[] nums) {
            
            int len = nums.length;
            
            // 因为nums中存在负数,乘积最小的值乘以-1可能变为乘积最大
    
            int[] dpmin = new int[len];// 以i结尾的乘积最小子数组的乘积
            int[] dpmax = new int[len];// 以i结尾的乘积最大子数组的乘积
    
    
            dpmin[0] = nums[0];
            dpmax[0] = nums[0];
            for(int i = 1; i < len; i++){
                
                dpmin[i] = min(dpmax[i-1]*nums[i],dpmin[i-1]*nums[i],nums[i]);
                dpmax[i] = max(dpmax[i-1]*nums[i],dpmin[i-1]*nums[i],nums[i]);
            }
            int result = dpmax[0];
            for(int r1:dpmax){
                result = result<r1?r1:result;
            } 
            return result;
        }
    
        int max(int i,int j,int k){
            return Math.max(i,Math.max(j,k));
        }
        int min(int i,int j,int k){
            return Math.min(i,Math.min(j,k));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    最长递增子序列LC300 中等

    题目链接
    给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

    子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

    示例 1:

    输入:nums = [10,9,2,5,3,7,101,18]
    输出:4
    解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
    示例 2:

    输入:nums = [0,1,0,3,2,3]
    输出:4
    示例 3:

    输入:nums = [7,7,7,7,7,7,7]
    输出:1

    代码:

    class Solution {
        public int lengthOfLIS(int[] nums) {
            
            int len = nums.length;
    
            int res = 1;
            //  以下标 i为结尾的数组的递增的子序列长度为 dp[i]
            int[] dp = new int[len];
            Arrays.fill(dp,1);
    
            for(int i = 0; i < len;i++){
                for(int j = 0;j < i;j++){
                    if(nums[i]>nums[j]){
                        dp[i] = Math.max(dp[i],dp[j]+1);
                    }
                }
            }
            for(int i = 0; i < len;i++){
                res = Math.max(res,dp[i]);
            }
            return res;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    最长重复子数组LC718

    题目链接
    给两个整数数组 nums1 和 nums2 ,返回 两个数组中 公共的 、长度最长的子数组的长度 。

    示例 1:

    输入:nums1 = [1,2,3,2,1], nums2 = [3,2,1,4,7]
    输出:3
    解释:长度最长的公共子数组是 [3,2,1] 。
    示例 2:

    输入:nums1 = [0,0,0,0,0], nums2 = [0,0,0,0,0]
    输出:5

    class Solution {
        public int findLength(int[] nums1, int[] nums2) {
    
            int[][] dp = new int[nums1.length+1][nums2.length+1];
            int finalresult = 0;
            // 两个数组,就是两个for
            for(int i = 1; i <= nums1.length; i++){ // 从nums1的第1个数字开始
                for(int j = 1; j <= nums2.length; j++){// 从nums2的第1个数字开始
    
                    if(nums1[i-1]==nums2[j-1]){// 如果相同
                        dp[i][j] = dp[i-1][j-1]+1;
                    }
                    finalresult = Math.max(dp[i][j],finalresult);
                }
            }
            return finalresult;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    最长公共子串NC BM66

    题目链接
    在这里插入图片描述
    题目与LC718相似

    public class Solution {
        /**
         * @param str1 string字符串 the string
         * @param str2 string字符串 the string
         * @return string字符串
         */
        public String LCS (String str1, String str2) {
            // write code here
            if(str1.length()==0||str2.length()==0)return "";
    
            int[][] dp = new int[str1.length()+1][str2.length()+1];
    
            for(int i = 0;i < str1.length();i++){
                dp[i][0] = 0;
            }
            for(int i = 0;i < str2.length();i++){
                dp[0][i] = 0;
            }
            String str = "";
            int maxlen = 0;
            for(int i = 0; i < str1.length();i++){
                for(int j = 0; j < str2.length();j++){
                    if(str1.charAt(i)==str2.charAt(j)){
                        dp[i+1][j+1] = dp[i][j]+1;
                        if(maxlen<dp[i+1][j+1]){
                            maxlen = dp[i+1][j+1];
                            str = str1.substring(i-maxlen+1,i+1);
                        }
                    }
                }
            }
            return str;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    最长公共子序列LC1143 中等

    题目链接
    给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。

    一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。

    例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。
    两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。

    示例 1:
    
    输入:text1 = "abcde", text2 = "ace" 
    输出:3  
    解释:最长公共子序列是 "ace" ,它的长度为 3 。
    示例 2:
    
    输入:text1 = "abc", text2 = "abc"
    输出:3
    解释:最长公共子序列是 "abc" ,它的长度为 3 。
    示例 3:
    
    输入:text1 = "abc", text2 = "def"
    输出:0
    解释:两个字符串没有公共子序列,返回 0 。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    代码:

    class Solution {
        public int longestCommonSubsequence(String text1, String text2) {
            int len1 = text1.length();
            int len2 = text2.length();
    
            int[][] dp = new int[len1+1][len2+1];
            for(int i = 0;i <= len1;i++){
                dp[i][0] = 0;
            }
            for(int i = 0;i <= len2;i++){
                dp[0][i] = 0;
            }
            for(int i = 1; i <= len1;i++){
                for(int j = 1;j <= len2;j++){
                    if(text1.charAt(i-1)==text2.charAt(j-1)){
                        dp[i][j] = dp[i-1][j-1]+1;
                    }else{
                        dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]);
                    }
                }
            }
            return dp[len1][len2];
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    完全平方数LC279

    给你一个整数 n ,返回 和为 n 的完全平方数的最少数量 。

    完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。

    示例 1:
    输入:n = 12
    输出:3 
    解释:12 = 4 + 4 + 4
    
    示例 2:
    输入:n = 13
    输出:2
    解释:13 = 4 + 9
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    代码:

    class Solution {
        public int numSquares(int n) {
            int[] dp = new int[n+1];
            Arrays.fill(dp,Integer.MAX_VALUE);
            dp[0] = 0;
            for(int i = 1; i <= n;i++){
                for(int j = 1; j*j <= i;j++){
                    dp[i] = Math.min(dp[i],dp[i-j*j]+1);
                }
            }
            return dp[n];
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    零钱兑换 LC322 中等

    题目链接
    给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。

    计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。

    你可以认为每种硬币的数量是无限的。

    示例 1:
    输入:coins = [1, 2, 5], amount = 11
    输出:3 
    解释:11 = 5 + 5 + 1
    
    示例 2:
    输入:coins = [2], amount = 3
    输出:-1
    
    示例 3:
    输入:coins = [1], amount = 0
    输出:0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    代码:

    class Solution {
        Map<Integer,Integer> memory = new HashMap<>();
        public int coinChange(int[] coins, int amount) {
            return DP(coins,amount);
        }
        
        public int DP(int[] coins,int target){
    
            if(target==0)return 0;
            if(target<0) return -1;
            // 查询备忘录
            if(memory.containsKey(target))return memory.get(target);
    
            int result = Integer.MAX_VALUE; // 注意此行位置
            for(int coin:coins){
    
                int n = DP(coins,target-coin);
                 // 存储备忘录
                memory.put(target-coin,n);
                if(n == -1) continue;  // 注意此行
                result = Math.min(result,n+1);
                
            }
    
            return result == Integer.MAX_VALUE?-1:result; // 注意此行
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    单词拆分LC139 中等

    题目链接

    给你一个字符串 s 和一个字符串列表 wordDict 作为字典。请你判断是否可以利用字典中出现的单词拼接出 s 。

    注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。

    示例 1:
    输入: s = "leetcode", wordDict = ["leet", "code"]
    输出: true
    解释: 返回 true 因为 "leetcode" 可以由 "leet" 和 "code" 拼接成。
    
    示例 2:
    输入: s = "applepenapple", wordDict = ["apple", "pen"]
    输出: true
    解释: 返回 true 因为 "applepenapple" 可以由 "apple" "pen" "apple" 拼接成。
         注意,你可以重复使用字典中的单词。
    
    示例 3:
    输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
    输出: false
     
    
    提示:
    1 <= s.length <= 300
    1 <= wordDict.length <= 1000
    1 <= wordDict[i].length <= 20
    s 和 wordDict[i] 仅由小写英文字母组成
    wordDict 中的所有字符串 互不相同
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    方法1:遍历所有单词的长度查看是否匹配

    class Solution {
    
        List<String> wordDict;
        int[] memory ;
    
        public boolean wordBreak(String s, List<String> wordDict1) {
            wordDict = wordDict1;
            memory = new int[s.length()+1];
            Arrays.fill(memory,-2);
            return DP(s,0);
        }
    
    
        boolean DP(String s,int i){
            if(i==s.length()){
                return true;
            }
            if(i>s.length()){
                return false;
            }
    
            if(memory[i]!=-2)return memory[i]==1?true:false;
       
            for(String word:wordDict){
                int len = word.length();
                if(i+len>s.length())continue;
                if(s.substring(i,i+len).equals(word)){
                    boolean r1 = DP(s,i+len);
                    if(r1==true){
                        memory[i]=1;
                        return true;
                    }
                }
            }
             memory[i]=0;
            return false;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    方法2:遍历所有长度查看是否匹配

    class Solution {
    
    
        List<String> wordDict1;
    	int[] memory;
        public boolean wordBreak(String s, List<String> wordDict) {
    		wordDict1 = wordDict;
    		memory = new int[s.length()];
    		Arrays.fill(memory,-1);
    		return DP(s,0);
        }
    
    	public boolean DP(String s,int i ){
    		if(i == s.length())return true;
    
    		if(memory[i]!=-1)return memory[i]==1?true:false;
    
    		for (int slen = 1; slen + i <= s.length(); slen++) {
    			String s1 = s.substring(i,i + slen);
    			if(wordDict1.contains(s1)){
    
    				boolean b = DP(s, i + slen);
    				if(b == true){
    					memory[i] = 1;
    					return true;
    				}
    			}
    		}
    		memory[i] = 0;
    		return false;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    编辑距离LC72 困难

    题目链接

    给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数 。

    你可以对一个单词进行如下三种操作:

    • 插入一个字符
    • 删除一个字符
    • 替换一个字符
    示例 1:
    输入:word1 = "horse", word2 = "ros"
    输出:3
    
    解释:
    horse -> rorse (将 'h' 替换为 'r')
    rorse -> rose (删除 'r')
    rose -> ros (删除 'e')
    
    示例 2:
    输入:word1 = "intention", word2 = "execution"
    输出:5
    
    解释:
    intention -> inention (删除 't')
    inention -> enention (将 'i' 替换为 'e')
    enention -> exention (将 'n' 替换为 'x')
    exention -> exection (将 'n' 替换为 'c')
    exection -> execution (插入 'u')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    思路:

    在这里插入图片描述

    在这里插入图片描述
    代码:

    class Solution {
    
        int[][] memory;
    
        public int minDistance(String word1, String word2) {
            memory = new int[word1.length()][word2.length()];
    
            for(int[] m:memory){
                Arrays.fill(m,-1);
            }
    
            return DP(word1,word1.length()-1,word2,word2.length()-1);
        }
    
        public int DP(String w1,int i,String w2,int j){
            if(i == -1) return j+1;
            if(j == -1) return i+1;
    
            if(memory[i][j]!=-1)return memory[i][j];
    
            if(w1.charAt(i) == w2.charAt(j)){
                int r = DP(w1,i-1,w2,j-1);
                memory[i][j] = r;
                return r;
            }
            else{
    
                int r = min(
                    DP(w1,i ,w2,j-1),   // 插入:在w1的i的位置插入,j被匹配,j-1
                    DP(w1,i-1,w2,j ),   // 删除:对w1的i的位置删除,i-1,j未匹配
                    DP(w1,i-1,w2,j-1)   // 替换:对w1的i的位置替换,j被匹配,i替换后被匹配
                    ) + 1;
                memory[i][j] = r;
                return r;
    
            }
    
        }
    
        int min(int i,int j,int x){
            return Math.min(i,Math.min(j,x));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    买卖股票的最佳时机LC121 简单

    题目链接

    给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

    你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

    返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。

    示例 1:
    输入:[7,1,5,3,6,4]
    输出:5
    解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
         注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
    
    
    示例 2:
    输入:prices = [7,6,4,3,1]
    输出:0
    解释:在这种情况下, 没有交易完成, 所以最大利润为 0。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    设置一个二期数组dp[][],第一维表示天数,第二维表示是否持有股票。

    class Solution {
        public int maxProfit(int[] prices) {
             
             int[][] dp = new int[prices.length][2]; // dp[i][1/0]:第i天持有1/不持有0
    
            for(int i = 0; i < prices.length;i++){
                
                if(i-1==-1){
                    dp[i][0] = 0;
                    dp[i][1] = -prices[0];
                    continue;
                }
                
                // 未持有股票=max(昨天未持有股票,昨天持有股票今天卖出)
                dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]+prices[i]);
                // 持有股票=max(昨天持有股票,昨天未股票今天买入)
                dp[i][1] = Math.max(dp[i-1][1],-prices[i]);// 只能买一次,所以第一次买时所赚利润为0-price。
            }
            return dp[prices.length-1][0];
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    买卖股票的最佳时机2 LC122中等

    题目链接

    给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。

    在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。

    返回 你能获得的 最大 利润 。

    示例 1:
    输入:prices = [7,1,5,3,6,4]
    输出:7
    解释:在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。
         随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6 - 3 = 3 。
         总利润为 4 + 3 = 7 。
    
    示例 2:
    输入:prices = [1,2,3,4,5]
    输出:4
    解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。
         总利润为 4 。
    
    示例 3:
    输入:prices = [7,6,4,3,1]
    输出:0
    解释:在这种情况下, 交易无法获得正利润,所以不参与交易可以获得最大利润,最大利润为 0 。
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    代码:

    class Solution {
        public int maxProfit(int[] prices) {
    
            int[][] dp = new int[prices.length][2];
    
            for(int i = 0; i < prices.length;i++){
                if(i-1==-1){
                    dp[i][0] = 0;
                    dp[i][1] = -prices[0];
                    continue;
                }
                    dp[i][0]=Math.max(dp[i-1][0],dp[i-1][1]+prices[i]);
                    dp[i][1]=Math.max(dp[i-1][1],dp[i-1][0]-prices[i]);
                }
            return dp[prices.length-1][0];
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    不同路径LC62 中等

    题目链接

    一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
    机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
    问总共有多少条不同的路径?
    在这里插入图片描述

    示例 1:
    输入:m = 3, n = 7
    输出:28
    
    示例 2:
    输入:m = 3, n = 2
    输出:3
    解释:
    从左上角开始,总共有 3 条路径可以到达右下角。
    1. 向右 -> 向下 -> 向下
    2. 向下 -> 向下 -> 向右
    3. 向下 -> 向右 -> 向下
    
    示例 3:
    输入:m = 7, n = 3
    输出:28
    
    示例 4:
    输入:m = 3, n = 3
    输出:6
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    代码:

    class Solution {
        public int uniquePaths(int m, int n) {
            int[][] dp = new int[m][n];
    
            for(int i = 0;i < m; i++){
                for(int j = 0; j < n; j++){
                    if(i==0||j==0){
                        dp[i][j] = 1;
                        continue;
                    }
    
                    dp[i][j] = dp[i][j-1] + dp[i-1][j];
                }
            }
            return dp[m-1][n-1];
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    最小路径和LC64 中等

    题目链接

    给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

    说明:每次只能向下或者向右移动一步。
    在这里插入图片描述
    代码:

    class Solution {
        public int minPathSum(int[][] grid) {
            int m = grid.length;
            int n = grid[0].length;
    
            int[][] dp = new int[m][n];
            // base case 
            dp[0][0] = grid[0][0];
            for(int i = 1;i < m;i++){
                dp[i][0] = dp[i-1][0]+grid[i][0];
            }
            for(int i = 1;i < n;i++){
                dp[0][i] = dp[0][i-1]+grid[0][i];
            }
            
            for(int i = 1;i < m; i++){
                for(int j = 1; j < n; j++){
                    dp[i][j] = Math.min(dp[i-1][j],dp[i][j-1]) + grid[i][j];
                }
            }
            return dp[m-1][n-1];
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    分割等和子集LC416 中等

    题目链接

    给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

    示例 1:
    输入:nums = [1,5,11,5]
    输出:true
    解释:数组可以分割成 [1, 5, 5] 和 [11] 。
    
    示例 2:
    输入:nums = [1,2,3,5]
    输出:false
    解释:数组不能分割成两个元素和相等的子集。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    题解:labuladong的算法笔记

    /**
    
    引用:Labuladong《算法小抄》 的第 192 页。
    
    对于这个问题,我们可以先对集合求和,得出 sum,然后把问题转化为背包问题:
    给一个可装载重量为 sum / 2 的背包和 N 个物品,每个物品的重量为 nums[i]。现在让你装物品,是否存在一种装法,能够恰好将背包装满?
    第一步要明确两点,「状态」和「选择」,状态就是「背包的容量」和「可选择的物品」,选择就是「装进背包」或者「不装进背包」。
    dp 数组的定义:dp[i][j] = x 表示,对于前 i 个物品,当前背包的容量为 j 时,若 x 为 true,则说明可以恰好将背包装满,若 x 为 false,则说明不能恰好将背包装满。
    根据 dp 数组含义,可以根据「选择」对 dp[i][j] 得到以下状态转移:
    如果不把 nums[i] 算入子集,或者说你不把这第 i 个物品装入背包,那么是否能够恰好装满背包,取决于上一个状态 dp[i-1][j],继承之前的结果。
    如果把 nums[i] 算入子集,或者说你把这第 i 个物品装入了背包,那么是否能够恰好装满背包,取决于状态 dp[i-1][j-nums[i-1]]
     */
    
    class Solution {
        public boolean canPartition(int[] nums) {
            
            int sum = 0; // 数组一半的大小
            for(int n:nums){
                sum+=n;
            }
            if(sum%2!=0)return false;
            sum = sum/2;
    
            boolean[][] dp = new boolean[nums.length+1][sum+1];// dp[i][j] 前i个物品,j大小的包,恰好能装满即为true
            // base case
            for(int i = 0;i < nums.length;i++){
                dp[i][0] = true;
            }
    
            for(int i = 1; i <= nums.length;i++){
                for(int j = 1; j <= sum; j++){
                    if(j-nums[i-1]<0){ // 第i个物品装不下
                        dp[i][j] = dp[i-1][j];
                    }
                    else{ // 能装下
                        dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i-1]];  // 装 or 不装
                    }
                }
            }
            return dp[nums.length][sum];
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    参考:
    Labuladong
    代码随想录
    力扣DP题库
    牛客面试必刷

  • 相关阅读:
    (附源码)ssm考务管理系统 毕业设计 221504
    .NET混合开发解决方案6 检测是否已安装合适的WebView2运行时
    mysql的优化(explain的深度分析)
    JavaScript算法描述【回溯算法】|括号生成|子集|电话号码的字母组合|全排列|单词搜索
    订水商城H5实战教程-04用户注册
    数据结构基础内容-----第三章 线性表
    Redis分布式锁
    牛客网SQL156
    js解析文件
    Github每日精选(第73期):Go 的 JSON 解析器gjson
  • 原文地址:https://blog.csdn.net/AwesomeP/article/details/132922687