今天刷力扣又学会了一种算法----动态规划,经过我查阅不少资料后,这些我总结的分享给大家
动态规划是什么?
动态规划(Dynamic Programming)是一种求解最优化问题的数学方法,它通常用于解决具有重叠子问题和最优子结构性质的问题。动态规划的基本思想是将原问题拆解成若干个子问题,然后通过求解子问题的最优解来得到原问题的最优解。
动态规划有两个关键概念:重叠子问题和最优子结构。
-
重叠子问题: 在动态规划中,原问题可以被拆解成多个具有相同结构的子问题,而这些子问题可能会被多次求解。动态规划通过存储已解决的子问题的解,避免重复计算,提高效率。
-
最优子结构: 问题的最优解可以通过子问题的最优解来构造。也就是说,问题的整体最优解是由各个子问题的最优解组合而成的。这使得我们可以通过求解子问题来逐步推导出原问题的最优解。
动态规划算法的一般步骤如下:
-
定义状态: 描述问题局部最优解的变量,这些变量可以表示问题的不同方面。
-
找到状态转移方程: 定义各个状态之间的关系,即问题的最优解如何由子问题的最优解推导而来。
-
初始化: 给定问题的初始状态,通常是问题规模较小时的情况,然后逐步扩展规模。
-
递推计算: 利用状态转移方程,从初始状态开始逐步计算得到问题的最优解。
-
解决原问题: 根据计算得到的最优解,得到原问题的最优解。
动态规划常被用于解决许多问题,如最短路径问题、背包问题、编辑距离问题等。这种方法在解决那些可以被拆解成子问题,并且这些子问题存在重叠的情况下特别有效。
如果觉得以上说法不是太通俗理解我们可以一边结合题,一边来详解什么是动态规划!
使用动态规划求解最大子数组和
先给出一个算法题(摘自力扣)
给你一个整数数组
nums
,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。子数组是数组中的一个连续部分。
示例 1:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4] 输出:6 解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
示例 2:
输入:nums = [1] 输出:1
示例 3:
输入:nums = [5,4,-1,7,8] 输出:23
提示:
1 <= nums.length <= 105
-104 <= nums[i] <= 104
理解题意:
题目要我们找出和最大的连续子数组的值是多少,「连续」是关键字,连续很重要,不是子序列。
题目只要求返回结果,不要求得到最大的连续子数组是哪一个。这样的问题通常可以使用「动态规划」解决。
如何定义子问题(定义状态)
我们 不知道和最大的连续子数组一定会选哪一个数 ,那么我们可以求出 所有 经过输入数组的某一个数的连续子数组的最大和。
经过分析,我们列出子问题如下:
子问题 1:以 −2 结尾的连续子数组的最大和是多少;
子问题 2:以 1 结尾的连续子数组的最大和是多少;
子问题 3:以 −3 结尾的连续子数组的最大和是多少;
子问题 4:以 4 结尾的连续子数组的最大和是多少;
子问题 5:以 −1 结尾的连续子数组的最大和是多少;
子问题 6:以 2 结尾的连续子数组的最大和是多少;
子问题 7:以 1 结尾的连续子数组的最大和是多少;
子问题 8:以 −5 结尾的连续子数组的最大和是多少;
子问题 9:以 4 结尾的连续子数组的最大和是多少。
我们单独看子问题 1 和子问题 2:
子问题 1:以 −2 结尾的连续子数组的最大和是多少;
以 −2结尾的连续子数组是 [-2],因此最大和就是 −2。
子问题 2:以 1 结尾的连续子数组的最大和是多少;
以 1 结尾的连续子数组有 [-2,1] 和 [1] ,其中 [-2,1] 就是在「子问题 1」的后面加上 1 得到。−2+1=−1<1 ,因此「子问题 2」 的答案是 1。
大家发现了吗,如果编号为 i 的子问题的结果是负数或者 0 ,那么编号为 i + 1 的子问题就可以把编号为 i 的子问题的结果舍弃掉(这里 i 为整数,最小值为 1 ,最大值为 8),这是因为:
一个数 a 加上负数的结果比 a 更小;
一个数 a 加上 0的结果不会比 a 更大;
而子问题的定义必须以一个数结尾,因此如果子问题 i 的结果是负数或者 0,那么子问题 i + 1 的答案就是以 nums[i] 结尾的那个数。
定义状态(定义子问题)
dp[i]:表示以 nums[i] 结尾的连续子数组的最大和。
「结尾」和「连续」是关键字。
状态转移方程(描述子问题之间的联系)
根据状态的定义,由于 nums[i] 一定会被选取,并且以 nums[i] 结尾的连续子数组与以 nums[i - 1] 结尾的连续子数组只相差一个元素 nums[i] 。
假设数组 nums 的值全都严格大于 0,那么一定有 dp[i] = dp[i - 1] + nums[i]。
可是 dp[i - 1] 有可能是负数,于是分类讨论:
如果 dp[i - 1] > 0,那么可以把 nums[i] 直接接在 dp[i - 1] 表示的那个数组的后面,得到和更大的连续子数组;
如果 dp[i - 1] <= 0,那么 nums[i] 加上前面的数 dp[i - 1] 以后值不会变大。于是 dp[i] 「另起炉灶」,此时单独的一个 nums[i] 的值,就是 dp[i]。
以上两种情况的最大值就是 dp[i] 的值,写出如下状态转移方程:
输出:
注意 :这里状态的定义不是题目中的问题的定义,不能直接将最后一个状态返回回去
我们需要将之前的dp全部遍历一遍,然后取最大值后返回
算法实现代码
java
class Solution {
public int maxSubArray(int[] nums) {
int len = nums.length;
int [] dp=new int[len];
dp[0]=nums[0];
for(int i=1;i<len;i++){
if(dp[i-1]<0){
dp[i]=nums[i];
}else{
dp[i]=dp[i-1]+nums[i];
}
}
int res=dp[0];
for(int i=1;i<len;i++){
res=Math.max(res,dp[i]);
}
return res;
}
}
算法思路转载来源:力扣(LeetCode)
作者:liweiwei1419