【力扣hot100】动态规划专题|70、118、198、279、322、139、300、152、416、32

动态规划

文章目录

    • 动态规划
      • [70. 爬楼梯](#70. 爬楼梯)
      • [118. 杨辉三角](#118. 杨辉三角)
      • [198. 打家劫舍](#198. 打家劫舍)
      • [279. 完全平方数](#279. 完全平方数)
      • [322. 零钱兑换](#322. 零钱兑换)
      • 139.单词拆分
      • [300. 最长递增子序列](#300. 最长递增子序列)
      • [152. 乘积最大子数组](#152. 乘积最大子数组)
      • [416. 分割等和子集](#416. 分割等和子集)
      • [32. 最长有效括号](#32. 最长有效括号)

核心 是状态定义和状态转移方程

70. 爬楼梯

70. 爬楼梯

f[i] 表示爬到第 i 阶楼梯总共有多少种方法

java 复制代码
class Solution {
    public int climbStairs(int n) {
        int[] f = new int[n+1];
        f[0] = f[1] = 1;
        for(int i = 2;i <= n;i++){
            f[i] = f[i-1] + f[i-2];
        }
        return f[n];
    }
}

118. 杨辉三角

118. 杨辉三角

java 复制代码
class Solution {
    public List<List<Integer>> generate(int numRows) {
        List<List<Integer>> ans = new ArrayList<>();
        for(int i = 0;i < numRows;i++){
            List<Integer> row = new ArrayList<>();
            for(int j = 0;j <= i;j++){
                if(j == 0 || j == i){
                    row.add(1);
                }else{
                    row.add(ans.get(i-1).get(j-1) + ans.get(i-1).get(j));
                }
            }
            ans.add(row);
        }
        return ans;
    }

}

198. 打家劫舍

198. 打家劫舍

java 复制代码
class Solution {
    public int rob(int[] nums) {
        if(nums == null || nums.length == 0){
            return 0;
        }
        int length = nums.length;
        if(length == 1)return nums[0];

        int[] dp = new int[length];
        dp[0] = nums[0];
        dp[1] = Math.max(nums[0],nums[1]);
        for(int i = 2;i < length ;i++){
            dp[i] = Math.max(dp[i-2]+nums[i],dp[i-1]);
        }
        return dp[length-1];
    }
}

279. 完全平方数

279. 完全平方数

f[i] 表示:凑成数字 i 最少需要几个完全平方数

f i = m i n ( f i − j 2 ) + 1 fi=min(fi−j\^2)+1 fi=min(fi−j2)+1

  • 外层循环

    从小到大逐个得出每个数字的最少平方和个数

  • 内层循环

    尝试所有不超过i的完全平方数,找出最优的那个

java 复制代码
class Solution {
    public int numSquares(int n) {
        int[] f = new int[n+1];
        for(int i = 1;i <= n;i++){
            int tmp = Integer.MAX_VALUE;
            for(int j = 1;j*j<=i;j++){
                tmp = Math.min(f[i-j*j],tmp);
            }
            f[i] = tmp + 1;
        }
        return f[n];
    }
}

322. 零钱兑换

322. 零钱兑换

初识化所有格子为无穷大,表示还没找到解

该算法采用自底向上的动态规划 策略,核心逻辑是建立一个数组 dp,其中 dp[i] 代表凑成金额 i 所需的最少硬币数。它从金额 1 开始逐步推导至目标金额 amount,对于每一个金额 i,尝试减去每一种硬币的面值 coin,利用之前计算好的子问题结果 dp[i - coin] 加上当前这一枚硬币(即 1 + dp[i - coin])来更新当前金额的最小硬币数。最终,如果 dp[amount] 仍为初始的无穷大值则说明无法凑出,否则该值即为所求的最少硬币数量。

java 复制代码
class Solution {
    public int coinChange(int[] coins, int amount) {
        //如果金额为0或为负数,直接返回0
        if(amount < 1){
            return 0;
        }

        //dp[i]表示凑成金额i所需的最少硬币数
        //使用amount+1作为无穷大的占位符
        int[] dp = new int[amount + 1];
        Arrays.fill(dp,amount + 1);
        dp[0] = 0;

        //外层循环:从1遍历到目标金额amount
        for(int i = 1;i <= amount;i++){
            //内层循环:尝试每一种硬币
            for(int coin : coins){
                // 只有当硬币面值小于等于当前金额时,才可能使用该硬币
                if(coin <= i){
                    dp[i] = Math.min(dp[i],1 + dp[i - coin]);
                }
            }
        }

        return dp[amount] > amount ? -1 : dp[amount];
    }
}

139.单词拆分

139. 单词拆分

核心思想是将字符串看作一个序列,通过判断"前缀是否可被拆分"以及"剩余后缀是否在字典中"来逐步推导整个字符串是否合法

将输入的 List 转换为 HashSet。这是为了优化查找效率。在 List 中查找一个单词的时间复杂度是 O(N) ,而在 HashSet 中查找的时间复杂度接近 O(1)

dp[i] 表示字符串 s 的前 i 个字符(即索引 0i-1 的子串 s[0...i-1])是否可以被字典中的单词拼接而成

外层循环:遍历字符串长度

内层循环:寻找分割点

java 复制代码
class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {
        Set<String> set = new HashSet<>(wordDict);
        boolean[] dp = new boolean[s.length()+1];
        dp[0] = true;
        for(int i=0;i<=s.length();i++){
            for(int j=0;j<i;j++){
                if(dp[j]&&set.contains(s.substring(j,i))){
                    dp[i] = true;
                    break;
                }
            }
        }
        return dp[s.length()];
    }
}

300. 最长递增子序列

300. 最长递增子序列

核心逻辑是:对于数组中的每一个数字,都尝试把它接在之前所有比它小的数字后面,看看能不能形成更长的递增序列

dp[i] 的含义是:nums[i] 这个元素结尾的最长严格递增子序列的长度

外层循环:遍历数组中的每一个元素numsi

内层循环:遍历i之前所有的元素

状态转移dp[i] = Math.max(dp[i], dp[j] + 1)

  • 尝试把 nums[i] 接在以 nums[j] 结尾的序列后面。
  • 新的长度是 dp[j] + 1
java 复制代码
class Solution {
    public int lengthOfLIS(int[] nums) {
        if(nums.length == 0){
            return 0;
        }
        int[] dp = new int[nums.length];
        dp[0] = 1;
        int ans = 1;
        for(int i = 1;i < nums.length;i++){
            dp[i] = 1;
            for(int j=0;j<i;j++){
                if(nums[i]>nums[j]){
                    dp[i] = Math.max(dp[i],dp[j]+1);
                }
            }
            ans = Math.max(ans,dp[i]);
        }
        return ans;
    }
}

152. 乘积最大子数组

152. 乘积最大子数组

要解决这个问题,必须同时维护两个状态:

  1. maxF :以当前元素结尾的最大乘积。
  2. minF :以当前元素结尾的最小乘积(为了处理负负得正的情况)
java 复制代码
class Solution {
    public int maxProduct(int[] nums) {
        if(nums == null||nums.length == 0)return 0;
        int maxF = nums[0];
        int minF = nums[0];
        int ans = nums[0];

        for(int i=1;i < nums.length;i++){
            if(nums[i] > 0){
                maxF *= nums[i];
                minF *= nums[i];
            }else if(nums[i] < 0){
                int tmp = maxF;
                maxF = minF * nums[i];
                minF = tmp * nums[i];
            }else{
                //当前为0,全部归零
                maxF = 0;
                minF = 0;
            }
            // 如果前面的累积不如当前数本身,就从当前数重新开始
            maxF = Math.max(nums[i],maxF);
            minF = Math.min(nums[i],minF);

            ans = Math.max(ans,maxF);
        }
        return ans;
    }
}

416. 分割等和子集

416. 分割等和子集

子集不要求连续,前缀和只能处理连续片段,所以这个方法从根上就行不通

正确解法

能否从数组中选出若干个数,使它们的和恰好等于总和的一半

java 复制代码
class Solution {
    public boolean canPartition(int[] nums) {
        int sum = 0;
        for(int num : nums){
            sum += num;
        }
        //总和为奇数,不可能平分
        if(sum % 2 == 1)return false;

        int target = sum / 2;
        //dp[i]表示能否凑出和为i的子集
        boolean[] dp = new boolean[target + 1];
        dp[0] = true;

        for(int num : nums){//外层循环:遍历每个数字
            for(int i = target;i >= num;i--){//内层循环:从target倒退num 防止num重复使用
                dp[i] = dp[i] || dp[i - num];//不选或选当前数字
            }
        }

        return dp[target];
    }
}

32. 最长有效括号

32. 最长有效括号

栈 + 配对标记

从左到右遍历字符串 s,对于右括号,找左侧最近的未配对的左括号,然后把这一对括号标记为「已配对」。

为方便找到左侧最近的未配对的左括号,我们可以用一个栈保存遍历过的左括号的下标:

遇到左括号,把其下标入栈。

遇到右括号,且栈非空,那么弹出栈顶。这样遍历到下一个右括号时,栈顶总是最近的未配对的左括号的下标。

最后,最长连续已配对标记的长度,就是最长有效括号的长度

java 复制代码
class Solution {
    public int longestValidParentheses(String s) {
        int n = s.length();
        boolean[] isValid = new boolean[n];
        Deque<Integer> st = new ArrayDeque<>();

        //标记哪些括号是配对的
        for(int i = 0;i < n;i++){
            if(s.charAt(i) == '('){
                st.push(i);//(入栈
            }else if(!st.isEmpty()){
                isValid[i] = isValid[st.pop()] = true;//匹配到(,(出栈,把两个括号都标记为已配对
            }
        }

        int ans = 0;
        int tmp = 0;
        //找到被标记的最长连续
        for(boolean b : isValid){
            if(b){
                tmp++;
                ans = Math.max(ans,tmp);
            }else{
                tmp = 0;
            }
        }

        return ans;
    }
}
相关推荐
渡之1 小时前
ArduPilot(APM)滤波器之 HarmonicNotchFilter 深度解析
算法·无人机
我不会起名字3221 小时前
一天一道算法题(26):栈的简单应用
java·数据结构·python·算法·leetcode·golang·
lvwangshu1 小时前
exBSGS 算法
数学·算法
不会就选b1 小时前
算法日常・每日刷题--<贪心+大根堆>2
数据结构·算法
码艺-Alimjan1 小时前
维吾尔语数字转文本
java·javascript·python·算法·go
AI服务老曹1 小时前
算法任务配置完整流程:明厨亮灶项目从0到1怎么做 | AI视频分析算法实践
人工智能·算法·音视频
hahaha60162 小时前
HLS高层次综合设计--axi之burst纠偏
开发语言·算法·fpga开发·c#
hans汉斯2 小时前
【计算机科学与应用】基于联合熵驱动改进麻雀搜索优化VMD的DAS信号去噪方法
深度学习·算法·yolo·软件工程·汉斯出版社
词却2 小时前
机器学习入门:手写数字识别与算法对比
人工智能·算法·机器学习