LeetCode动态规划的解题思路

动态规划

动态规划,其实就是找规律,总结公式/方程。
动态规划,类似于数学归纳法。
关键的思想在「自底向上」和「空间换时间」。
动态规划,可以使用一维数组,有时也会用到二维数组。

应用场景

“动态规划”可以用于子序列、最大/小值问题、回文子串的求解。

一维数组dp[i] 的动态规划的几个步骤:
  • 确定数组dp[i]的下标i以及dp[i]值的含义,比如经典的LeetCode70爬楼梯, 爬到第i层楼梯,有dp[i]种方法;
  • 确定动态规划的状态转移方程(递推公式)。比如,爬楼梯的公式:dp[i] = dp[i-1] + dp[i-2];
  • dp数组的初始化:初始化值,dp[0]的值是多少 , dp[1]的值又是多少;
  • 确定遍历顺序:分析递推顺序应该是从前往后,还是从后往前。还有就是,要从哪一个下标开始遍历;
LeetCode70. 爬楼梯

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

public int climbStairs(int n) {
	if (n <= 0) {
	    return 0;
	}
        //dp数组的初始化
        //爬到第i层楼梯,有dp[i]种方法;
	int[] dp = new int[n + 2];
        //dp数组的初始化
	dp[0] = 0;
	dp[1] = 1;
	dp[2] = 2;
	for (int i = 3; i <= n; i++) {
                //确定动态规划的状态转移方程(递推公式)
		dp[i] = dp[i - 1] + dp[i - 2];
	}
	return dp[n];
}

LeetCode53. 最大子数组和

  • 给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
    子数组 是数组中的一个连续部分。

示例 1:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。

class Solution {
    public int maxSubArray(int[] nums) {
        int length=nums.length;
        int[] dp=new int[length];
        //初始值
        dp[0]=nums[0];
        int maxSum=nums[0];
        for(int i=1;i<length;i++) {
            //dp[i]表示以第 i 个数结尾的「连续子数组的最大和」
            //由于子数组是以nums[i]结尾,
            //如果dp[i-1]是正数,那么dp[i]最大值是dp[i-1]+nums[i]
            //如果dp[i-1]是负数,那么dp[i]最大值是nums[i]
            //状态转移方程如下:
            dp[i]=Math.max(dp[i-1]+nums[i],nums[i]);
            //找出不同的数组元素结尾的最大值。
            maxSum=Math.max(dp[i],maxSum);
        }
        return maxSum;
    }
}

用二维数组的动态规划:

二维数组的动态规划,跟一维数组的动态规划,基本是一样的。

  • 设定状态。
    二维 dp 问题,可以使用二维数组 dp[i][j],第一维的下标i可以表示A事物的状态,第二维的下标j可以表示B事物的状态。
    比如LeetCode122的买卖股票,题中有两个状态,一个是天数,一个是是否持有股票,
    定义dp[i][0]表示第 i天交易完后手里没有股票的最大利润,
    dp[i][1] 表示第 i天交易完后手里持有一支股票的最大利润。
  • 思考状态转移方程(也就是公式)。
    找规律,找出 dp[i][j]是怎么由dp[i-1][j]、 dp[i-1][j-1] 推导得到的
  • 考虑初始值。
    也就是 dp[0][0] 、 dp[0][1] 之类的初始值。
  • 考虑输出。
    求出 dp[len - 1][j] (也可能是其他的如dp[len - 1][j]) 的值。

二维数组动态规划:122. 买卖股票的最佳时机 II

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

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

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

链接: https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/

解答:定义状态 dp[i][0] 表示第 i 天交易完后手里没有股票的最大利润,dp[i][1] 表示第 i 天交易完后手里持有一支股票的最大利润(i 从 0 开始)。

考虑 dp[i][0] 的转移方程,如果这一天交易完后手里没有股票,那么可能的转移状态为前一天已经没有股票,即 dp[i−1][0],或者前一天结束的时候手里持有一支股票,即 dp[i−1][1],这时候我们要将其卖出,并获得 prices[i]的收益。
dp[i][0]=max{dp[i−1][0],dp[i−1][1]+prices[i]}

对于dp[i][1],按照同样的方式考虑转移状态,那么可能的转移状态为前一天已经持有一支股票,即 dp[i−1][1],或者前一天结束时还没有股票,即 dp[i−1][0],这时候我们要将其买入,并减少 prices[i] 的收益。可以列出如下的转移方程:
dp[i][1]=max⁡{dp[i−1][1],dp[i−1][0]−prices[i]}

    public int maxProfit(int[] prices) {
        if (prices==null) {
            return 0;
        }
        int length = prices.length;
        //定义状态 dp[i][0]  表示第 i 天交易完后手里没有股票的最大利润,
        //dp[i][1] 表示第 i 天交易完后手里持有一支股票的最大利润(i 从 0 开始)。
        int[][] dp = new int[length][2];
        dp[0][0]=0;
        dp[0][1]=-prices[0];
        for (int i=1;i<length;i++) {
            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[length-1][0];
    }

LeetCode5 :最长回文子串

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

如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。

    /**
     * 对于一个子串而言,如果它是回文串,并且长度大于 2,那么将它首尾的两个字母去除之后,它仍然是个回文串。
     *
     * P(i,j) 表示字符串 s 的第 i 到 j 个字母组成的串。(s[i:j] 类似)
     *
     * (1)状态转移方程:
     *
     * P(i,j)=P(i+1,j−1)∧(Si==Sj)
     * 也就是:只有 s[i+1:j−1] 是回文串,并且 s 的第 i 和 j 个字母相同时,s[i:j] 才会是回文串。
     *
     * (2)初始值:
     * 对于长度为 1 的子串,它显然是个回文串;
     * 也就是 P(i,i)=true;
     *
     * 对于长度为 2 的子串,只要它的两个字母相同,它就是一个回文串。因此我们就可以写出动态规划的边界条件:
     * 也就是P(i,i+1)=  (Si==Si+1)
     *
     * 详情见: https://leetcode.cn/problems/longest-palindromic-substring/solution/zui-chang-hui-wen-zi-chuan-by-leetcode-solution/
     *
     * @param s
     * @return
     */
    public String longestPalindrome(String s) {
        int length = s.length();
        if (length < 2) {
            return s;
        }
        int maxLength = 1;
        int begin = 0;

        // dp[i][j] 表示从字符数组的下标i开始到下标j的子序列 s[i,i+1,..j] 是否是回文串
        boolean[][] dp = new boolean[length][length];
        // 初始化:所有长度为 1 的子串都是回文串
        for (int i = 0; i < length; i++) {
            dp[i][i] = true;
        }

        char[] charArray = s.toCharArray();
        // 递推开始
        // 先枚举子序列的长度
        for (int subLength = 2; subLength <= length; subLength++) {
            // 枚举左边界,左边界的上限设置可以宽松一些
            for (int left = 0; left < length; left++) {
                // 由 subLength 和 left 可以确定右边界,即 right - left + 1 = subLength 得
                int right = subLength + left - 1;
                // 如果右边界越界,就可以退出当前循环
                if (right >= length) {
                    break;
                }

                if (charArray[left] != charArray[right]) {
                    //子序列的左边界和右边界的值,如果不相同,就不可能是回文串
                    dp[left][right] = false;
                } else {
                    //左边界和右边界相同时,子序列的长度为1或为2,都是回文串。
                    if (right - left < 3) {
                        dp[left][right] = true;
                    } else {
                        //状态转移方程,左边界和右边界相同时,
                        // 从left到right的子序列,跟从left + 1 到 right - 1的子序列,要么都是回文串,要么都不是回文串。
                        dp[left][right] = dp[left + 1][right - 1];
                    }
                }

                // 只要 dp[left][subLength] == true 成立,就表示子串 s[left..subLength] 是回文,此时记录回文长度和起始位置
                if (dp[left][right] && right - left + 1 > maxLength) {
                    //找出最大子序列的长度
                    maxLength = right - left + 1;
                    begin = left;
                }
            }
        }
        return s.substring(begin, begin + maxLength);
    }

常见题目

动态规划常见题:LeetCode70,LeetCode121,LeetCode122,LeetCode5

参考资料:

《labuladong的算法小抄》
https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/solution/bao-li-mei-ju-dong-tai-gui-hua-chai-fen-si-xiang-b/

  • 28
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: class Solution: def twoSum(self, nums, target): """ :type nums: List[int] :type target: int :rtype: List[int] """ # 解题思路:使用哈希表,遍历每个元素x,查找target-x是否在哈希表中,如果存在,则返回下标;如果不存在,则将x加入哈希表中。 hashmap = {} for i, num in enumerate(nums): if target - num in hashmap: return [hashmap[target - num], i] hashmap[num] = i ### 回答2: 题目是LeetCode第1题:两数之和,要求给定一个整数数组和一个目标值,找出数组中和为目标值的两个数。 解题思路: 1. 创建一个空字典:key为数组中的数字,value为对应数字的索引 2. 遍历数组,对于每一个数字num,计算出目标值与num的差值target 3. 判断差值target是否在字典中 - 如果存在,则返回差值的索引和当前数字的索引 - 如果不存在,则将当前数字和索引添加到字典中 4. 如果遍历完整个数组,都没有找到符合条件的数字,则返回空列表 代码如下: def twoSum(nums, target): # 创建空字典 dic = {} # 遍历数组 for i, num in enumerate(nums): # 计算差值 diff = target - num # 判断差值是否在字典中 if diff in dic: # 返回差值的索引和当前数字的索引 return [dic[diff], i] # 将当前数字和索引添加到字典 dic[num] = i # 如果没有找到符合条件的数字,则返回空列表 return [] ### 回答3: 题目描述: 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那两个整数,并返回他们的数组下标。 解题思路: 题目要求找出数组中和为目标值的两个数,我们可以使用哈希表来解决这个问题。遍历数组,对于每一个元素,在哈希表中查找是否存在与目标值相减的另一个数。如果存在,则返回两个数的下标。如果不存在,则将当前元素加入哈希表中,继续遍历。 代码实现: ```python def twoSum(nums, target): hashmap = {} for i, num in enumerate(nums): complement = target - num if complement in hashmap: return [hashmap[complement], i] hashmap[num] = i return [] ``` 首先创建一个空的哈希表,用于存储每个元素及其对应的下标。然后遍历数组,对于每个元素,求出它与目标值的差值。如果差值存在于哈希表中,则返回差值的下标和当前元素的下标。如果差值不存在于哈希表中,则将当前元素添加到哈希表中,并继续遍历。如果遍历完整个数组都没有找到符合条件的两个数,则返回空列表。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值