什么是动态规划
以下是我综合了动态规划的特点给出的动态规划的定义:
动态规划是一种多阶段决策最优解模型,一般用来求最值问题,多数情况下它可以采用自下而上的递推方式来得出每个子问题的最优解(即最优子结构),进而自然而然地得出依赖子问题的原问题的最优解。
划重点:
多阶段决策,意味着问题可以分解成子问题,子子问题,。。。,也就是说问题可以拆分成多个子问题进行求解
最优子结构,在自下而上的递推过程中,我们求得的每个子问题一定是全局最优解,既然它分解的子问题是全局最优解,那么依赖于它们解的原问题自然也是全局最优解。
自下而上,怎样才能自下而上的求出每个子问题的最优解呢,可以肯定子问题之间是有一定联系的,即迭代递推公式,也叫「状态转移方程」,要定义好这个状态转移方程, 我们就需要定义好每个子问题的状态(DP 状态),那为啥要自下而上地求解呢,因为如果采用像递归这样自顶向下的求解方式,子问题之间可能存在大量的重叠,大量地重叠子问题意味着大量地重复计算,这样时间复杂度很可能呈指数级上升(在下文中我们会看到多个这样重复的计算导致的指数级的时间复杂度),所以自下而上的求解方式可以消除重叠子问题。
上面可能很难看到,但重要的是理解状态与策略,状态就是当前的与结果相关的一种定义,策略是改变当前状态的方法,而我们要做到就是遍历所有策略选择最佳策略。物理上的Max或者Min.
LeetCode 62. 不同路径
dp[i][j]代表到i,j位置有多少种走法,所有初始状态dp[i][0]=1,dp[0][i]=1。
因为只能从上往下走或者从左往右走,所有动态转移:
dp[i][j]=dp[i][j-1]+dp[i-1][j]
class Solution {
public int uniquePaths(int m, int n) {
int[][] dp = new int[m][n];
for(int i=0;i<n;i++){
dp[0][i]=1;
}
for(int i=0;i<m;i++){
dp[i][0]=1;
}
for(int i=1;i<m;i++){
for(int j=1;j<n;j++){
dp[i][j]=dp[i][j-1]+dp[i-1][j];
}
}
return dp[m-1][n-1];
}
}
LeetCode 63. 不同路径 II
有条件动态规划,在上一题的基础上处理好障碍条件。
class Solution {
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
int m = obstacleGrid.length;
int n = obstacleGrid[0].length;
int[][] dp = new int[m][n];
for(int i=0;i<n;i++){
if(obstacleGrid[0][i]==1){
break;
}
dp[0][i]=1;
}
for(int i=0;i<m;i++){
if(obstacleGrid[i][0]==1){
break;
}
dp[i][0]=1;
}
for(int i=1;i<m;i++){
for(int j=1;j<n;j++){
if(obstacleGrid[i][j]==1){
dp[i][j]=0;
continue;
}
dp[i][j]=dp[i][j-1]+dp[i-1][j];
}
}
return dp[m-1][n-1];
}
}
LeetCode 64. 最小路径和
dp[i][j]=Math.min(dp[i-1][j],dp[i][j-1])+grid[i][j];
class Solution {
public int minPathSum(int[][] grid) {
int[][] dp = new int[grid.length][grid[0].length];
int temp=0;
for(int i=0;i<grid.length;i++){
temp+=grid[i][0];
dp[i][0]=temp;
}
temp=0;
for(int i=0;i<grid[0].length;i++){
temp+=grid[0][i];
dp[0][i]=temp;
}
for(int i=1;i<grid.length;i++){
for(int j=1;j<grid[0].length;j++){
dp[i][j]=Math.min(dp[i-1][j],dp[i][j-1])+grid[i][j];
}
}
return dp[grid.length-1][grid[0].length-1];
}
}
LeetCode 300. 最长上升子序列
动态规划基础问题,dp[i]表示以num[i]结尾的最长上升子序列是多少。
状态 转移方程为 当num[j]<num[i]时
dp[i]=Math.max(dp[i],dp[j]+1);
class Solution {
public int lengthOfLIS(int[] nums) {
int[] dp = new int[nums.length];
int result = 0;
for(int i=0;i<nums.length;i++){
dp[i]=1;
for(int j=0;j<i;j++){
if(nums[j]<nums[i]){
dp[i]=Math.max(dp[i],dp[j]+1);
}
}
if(dp[i]>result){
result = dp[i];
}
}
return result;
}
}
经典题目编辑距离,自顶而加的思维方式比较容易理解动态转移方程。
当s1[i] == s2[j] 啥也不做 dp(i - 1, j - 1)
其他三种情况为,替换,删除,插入
dp[i-1][j-1]
dp[i][j-1]
dp[i-1][j]
class Solution {
public int minDistance(String word1, String word2) {
int[][] dp = new int[word1.length()+1][word2.length()+1];
for(int i=0;i<=word1.length();i++){
dp[i][0]=i;
}
for(int i=0;i<=word2.length();i++){
dp[0][i]=i;
}
for(int i=1;i<=word1.length();i++){
for(int j=1;j<=word2.length();j++){
if(word1.charAt(i-1)==word2.charAt(j-1)){
dp[i][j]=dp[i-1][j-1];
}else {
dp[i][j]=min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1])+1;
}
}
}
return dp[word1.length()][word2.length()];
}
public int min(int a, int b, int c) {
return Math.min(a, Math.min(b, c));
}
}
找好条件和状态问题。
class Solution {
public int numDecodings(String s) {
int[] dp = new int[s.length()];
if(s.charAt(0)=='0'){
return 0;
}
dp[0]=1;
for(int i=1;i<s.length();i++){
String temp = s.substring(i-1,i+1);
if(s.charAt(i)=='0'){
if(s.charAt(i-1)!='1'&&s.charAt(i-1)!='2'){
return 0;
}
if(i-2<0){
dp[i] = 1;
}else {
dp[i] = dp[i-2];
}
continue;
}
if(Integer.valueOf(temp)<=26&&temp.charAt(0)!='0'){
if(i-2<0){
dp[i] = dp[i - 1] +1;
}else {
dp[i] = dp[i - 1] + dp[i-2];
}
}else {
dp[i]=dp[i-1];
}
}
return dp[s.length()-1];
}
}
LeetCode 56. 合并区间
贪心算法一种,先排序数组,然后取最早开始的。按规则合并。
class Solution {
class Interval {
int startinter;
int endinter;
}
public int[][] merge(int[][] intervals) {
if(intervals==null||intervals.length==0){
return intervals;
}
Arrays.sort(intervals, new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
return o1[0]-o2[0];
}
});
List<Interval> intervalList = new ArrayList<>();
int startinter = intervals[0][0];
int endinter = intervals[0][1];
for(int i=1;i<intervals.length;i++){
if(intervals[i][0]>endinter){
Interval interval = new Interval();
interval.endinter=endinter;
interval.startinter=startinter;
intervalList.add(interval);
startinter = intervals[i][0];
endinter = intervals[i][1];
}else {
if (startinter > intervals[i][0]) {
startinter = intervals[i][0];
}
if (endinter < intervals[i][1]) {
endinter = intervals[i][1];
}
}
}
Interval interval = new Interval();
interval.endinter=endinter;
interval.startinter=startinter;
intervalList.add(interval);
int[][] result = new int[intervalList.size()][2];
for(int i=0;i<intervalList.size();i++){
result[i][0]= intervalList.get(i).startinter;
result[i][1]= intervalList.get(i).endinter;
}
return result;
}
}
class Solution {
class Node {
int fir;
int sec;
}
public boolean PredictTheWinner(int[] nums) {
int n = nums.length;
if(n==1)
return true;
Node[][] dp = new Node[n][n];
for(int i=0;i<n;i++){
dp[i] = new Node[n];
dp[i][i] =new Node();
dp[i][i].fir = nums[i];
}
for(int i = n-2;i>=0;i--){
for (int j = i+1;j<n;j++){
int left = nums[i]+dp[i+1][j].sec;
int right = nums[j]+dp[i][j-1].sec;
dp[i][j] =new Node();
if(left > right){
dp[i][j].fir = left;
dp[i][j].sec = dp[i+1][j].fir;
}else {
dp[i][j].fir = right;
dp[i][j].sec = dp[i][j-1].fir;
}
}
}
return dp[0][n-1].fir >= dp[0][n-1].sec;
}
}