算法——动态规划篇

算法——动态规划篇

视频地址:https://www.bilibili.com/video/BV1ix411f7aNjava

课程说明算法

  • 前导技能编程

    • 递归,基本的暴力搜索(必会)
  • 动态规划(Dynamic Programming)数组

    • 名字并没有多大意义
  • 目标ide

    • 分析->Coding->AC
  • 课程开始函数

    • Leetcode 198

198. 打家劫舍

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

给定一个表明每一个房屋存放金额的非负整数数组,计算你在不触动警报装置的状况下,可以偷窃到的最高金额。spa

示例 1:设计

输入: [1,2,3,1]
输出: 4
解释: 偷窃 1 号房屋 (金额 = 1) ,而后偷窃 3 号房屋 (金额 = 3)。
     偷窃到的最高金额 = 1 + 3 = 4 。

示例 2:code

输入: [2,7,9,3,1]
输出: 12
解释: 偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
     偷窃到的最高金额 = 2 + 9 + 1 = 12 。

思路:暴力破解法(递归算法)

public class Solution {
    public int solve(int idx,int[] nums){
        if(idx<0){
            return 0;
        }
        return Math.max(nums[idx]+solve(idx-2,nums),solve(idx-1,nums));
    }
    public int  rob(int[] nums){
        return solve(nums.length-1,nums);
    }
}

【注】代码超时——\(O(2^n)\)

n-1->(n-3,n-4,n-5...)

n-2->(n-4,n-5...)

深度为 \(n\)

\(O(2^n)\)

定义

  • 本质:递归

  • 原问题(N)->子问题(N-1)->原问题(N)

  • 最优子结构

    • 子问题最优决策可导出原问题最优决策
    • 无后效性(对后面的解(决策)不形成影响)
  • 重叠子问题

    • 去冗余(重复计算)
    • 空间换时间(注意分析时空复杂度

递归的意义:递归不是追求效率,递归追求的是咱们怎么看待、拆解这个问题。

处理重叠子问题:(用一个数组来存结果)——计划搜索

public class Solution {
    public static int[] result;//数组存结果

    public int solve(int idx,int[] nums){
        if(idx<0){
            return 0;
        }

        if(result[idx]>=0){
            return result[idx];
        }//算过了直接从数组取值
        
        result[idx]=Math.max(nums[idx]+solve(idx-2,nums),solve(idx-1,nums));
        return result[idx];
    }
    public int  rob(int[] nums){
        result=new int[nums.length];
        for (int i = 0; i < nums.length; i++) {
            result[i]=-1;
        }//初始化数组置为-1
        return solve(nums.length-1,nums);
    }
}

解:每一个状态只算了一遍—— \(O(n)\)

问题共性

  • 套路

    • 最优、最大、最小、最长、计数
  • 离散问题

    • 容易设计状态(整数0/1背包问题)
  • 最优子结构

    • N-1能够推到出N

基本步骤

  • 四个步骤
    • 设计暴力算法,找到冗余
    • 设计并存储状态(一维,二维,三维数组,甚至用Map)
    • 递归式(状态转移方程)
    • 自底向上计算最优解(编程方式)

递归改递推(非递归):

自顶向下的解法

public class Solution {
    public static int[] result;//数组存结果

    public int solve(int idx,int[] nums){
        if(idx<0){
            return 0;
        }

        if(result[idx]>=0){
            return result[idx];
        }//算过了直接从数组取值
        result[idx]=Math.max(nums[idx]+solve(idx-2,nums),solve(idx-1,nums));
        return result[idx];
    }
    public int  rob(int[] nums){
        if(nums.length==0){
            return 0;
        }
        if(nums.length==1){
            return nums[0];
        }
        result=new int[nums.length];
        result[0]=nums[0];
        result[1]=Math.max(nums[0],nums[1]);
        for (int idx = 2; idx < nums.length; ++idx) {
            result[idx]=Math.max(nums[idx]+result[idx-2],result[idx-1]);
        }
        return result[nums.length-1];
    }
}

实例

  • 斐波那契数列

    • 暴力递归
    • F(n)表示斐波那契第n个
    • F(n)=F(n-1)+F(n-2),if n>=2,otherwise F(n)=1
    • for i<- 2 to n
  • N!

    • 暴力递归
    • F(n)表示n!的值
    • F(n)=F(n-1)*n,if n>=1,otherwise F(n)=1
    • for i<- 2 to n

小兵向前冲

  • N*M的棋盘上,小兵要从左下角走到右上角,只能向上或者向右走,稳有多少种走法
  • 套路:计数问题
    • 暴力搜索(回溯法)
    • F(n,m)表示棋盘大小为n*m时走法数量
    • F(n,m)=F(n-1,m)+F(n,m-1) if n*m>0 , otherwise F(n,m)=1
    • for i<- 1 to n
    • for j<- i to n

暴力破解法:

public class Solution {
   public int f(int n,int m){
       if(n==0 || m==0){
            return 0;
       }
       if(n==1 || m==1){
           return 1;
       }
       return f(n-1,m)+f(n,m-1);
   }
}

01背包问题

  • 小偷有一个容量为W的背包,有n件物品,第i个物品价值vi,且重wi

  • 目标:找到xi是的对于全部的xi={0,1}​

  • \(sum(wi*xi)<=W\),而且\(sum(xi*vi)\)最大

  • 套路:最大

    • 暴力回溯法怎么写?
    • F(i,W)表示前i件物品的体积为w,最大价值
    • F(i,W)=max{F(i-1,W),F(i-1,W-wi)+vi}//后一项当W<wi时为0
    • for i<- 1 to W
    • for j<- 1 to n

Leetcode 322.零钱兑换

322.零钱兑换

给定不一样面额的硬币 coins 和一个总金额 amount。编写一个函数来计算能够凑成总金额所需的最少的硬币个数。若是没有任何一种硬币组合能组成总金额,返回 -1。

示例 1:

输入: coins = [1, 2, 5], amount = 11
输出: 3 
解释: 11 = 5 + 5 + 1

示例 2:

输入: coins = [2], amount = 3
输出: -1

暴力破解法:

public class Solution {
    public static int maxValue=10000000;
   public int search(int idx,int amount,int[] coins){
       if(amount==0){
           return 0;
       }
       if(amount<0){
           return maxValue;
       }
       if(idx>=coins.length){
           return maxValue;
       }
       return Math.min(search(idx,amount-coins[idx],coins)+1,search(idx+1,amount,coins));
   }
   public int coinChange(int[] coins,int amount){
       int res=search(0,amount,coins);
       if(res<maxValue){
           return res;
       }else{
           return -1;
       }
   }
}

超时——优化(空间换时间)

public class Solution {
    public static int maxValue=10000000;
    public static int[][] f=new int[1000][10000];
   public int search(int idx,int amount,int[] coins){
       if(amount==0){
           return 0;
       }
       if(amount<0){
           return maxValue;
       }
       if(idx>=coins.length){
           return maxValue;
       }
       if(f[idx][amount]>=0){
           return f[idx][amount];
       }

       f[idx][amount]=Math.min(search(idx,amount-coins[idx],coins)+1,search(idx+1,amount,coins));
       return f[idx][amount];
   }
   public int coinChange(int[] coins,int amount){
       for (int i = 0; i < 1000; i++) {
           for(int j=0;j<10000;j++){
               f[i][j]=-1;
           }
       }
       
       int res=search(0,amount,coins);
       if(res<maxValue){
           return res;
       }else{
           return -1;
       }
   }
}

递归转递推(非递归)省空间

 

最长公共子序列

  • 一个给定序列的子序列是在该序列中删去若干元素后获得的序列
  • 给定两个序列X和Y,当另外一个序列Z既是X的子序列又是Y的子序列时,称Z是序列X和Y的公共子序列
  • 最长公共子序列
  • X=(A,B,C,B,D,A,B)Y=(B,D,C,A,B,A)
  • (B,C,B,A) (B,D,A,B)

相似LeetCode 72

72. 编辑距离

给你两个单词 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')

伪代码

X[n] 取哪些
Y[m] 取哪些

int search(int xi,int yi){
	if(xi>n || yi>m){
		return -1;
	}
	if(xi==n && yi==m){
		return 0;
	}
	return  Math.max(
	if(X[xi]==Y[yi])
		search(xi+1,yi+1)+1,
	search(xi,yi+1),
	search(xi+1,yi)
	);
	//search(xi,yi);
}

旅行商问题

  • 一个商人要不重复的访问N个城市,容许从任意城市出发,在任意城市结束。现已知任意两个城市之间的道路长度
  • 求城市的访问序列,使得商人走过的路程最短
  • 例:N=4,访问序列3,4,1,2
  • NP问题,最优解无多项式时间算法
  • 时间复杂度?空间复杂度?
  • 状态压缩
    • 时间复杂度
    • 空间复杂度

暴力搜索

map[n][n];
map[i][j]-> i-j距离
boolean visit[n];
int search(int idx,int count){
	if(count==n){
	return 0;
	}
	int min=1000000;
	for(int i=0;i<n;i++){
        if(!visit[i]){
        	visit[i]=true;
            int t=search(i,count+1)+map[idx][i];
                if(t<min){
                    min=t;
                }
        }
        visit[i]=false;//复原
	}
	return min;
}

总结

  • 动态规划 算法用到的题目存在不少套路

  • 滚动数组,状态压缩,升维,单调性,四边形不等式(高级套路)

  • 先学套路,跳出套路

  • 本质:先暴力,找冗余,去冗余

相关文章
相关标签/搜索