动态规划经典案例

1.主要参考

http://blog.csdn.net/uestclr/article/details/50760563
②《程序员代码面试指南-IT名企算法与数据结构题目最优解》——左程云

2.经典案例

案例一:上楼梯

有n级台阶,一个人每次上一级或者两级,问有多少种走完n级台阶的方法,当n为1时,f(n) = 1,n为2时,f(n) =2,就是说当台阶只有一级的时候,方法数是一种,台阶有两级的时候,方法数为2。那么当我们要走上n级台阶,必然是从n-1级台阶迈一步或者是从n-2级台阶迈两步,所以到达n级台阶的方法数必然是到达n-1级台阶的方法数加上到达n-2级台阶的方法数之和。即f(n) = f(n-1)+f(n-2)

function goUpstairs(n) {
    let temp=[1,2];
    for(let i=3;i<=n;i=i+2){
        temp[0]=temp[0]+temp[1];
        temp[1]=temp[0]+temp[1];
    }
    if(n%2===0){
        return temp[1];
    }else{
        return temp[0];
    }
}

案例二:最长递增子序列

给定数组arr,返回arr的最长递增子序列的长度,比如arr=[2,1,5,3,6,4,8,9,7],最长递增子序列为[1,3,4,8,9]返回其长度为5。dp[i]表示以必须arr[i]这个数结束的情况下产生的最大递增子序列的长度。对于第一个数来说,很明显dp[0]为1,当我们计算dp[i]的时候,我们去考察i位置之前的所有位置,找到i位置之前所有比arr[i]小的位置中dp最大的那个值,记为dp[j],dp[j]代表以arr[j]结尾的最长递增序列,而dp[j]又是之前计算过的最大的那个值,则dp[i]=dp[j]+1。计算完dp之后,我们找出dp中的最大值,即为这个串的最长递增序列

function maxStr(arr) {
    let temp=[1];
    for(let i=1;i<arr.length;i++){
        let temp_arr=arr.slice(0,i);
        let flag=[];
        for(let j=0;j<temp_arr.length;j++){
            if(arr[j]<arr[i]){
                flag.push(temp[j])
            }
        }
        if(flag.length===0){
            temp.push(1)
        }else{
            temp.push(Math.max(...flag)+1)
        }
    }
    return Math.max(...temp)
}

案例三:矩阵对角问题

给定一个矩阵m,从左上角开始每次只能向右走或者向下走,最后达到右下角的位置,路径中所有数字累加起来就是路径和,返回所有路径的最小路径和。dp[i][j]表示的是从原点到i,j位置的最短路径和。我们首先计算第一行和第一列,直接累加即可,那么对于其他位置,要么是从它左边的位置达到,要么是从上边的位置达到,我们取左边和上边的较小值,然后加上当前的路径值,就是达到当前点的最短路径。然后从左到右,从上到下依次计算即可。

function minMatrixDistance(arr) {
    for(let i=1;i<arr.length;i++){
        arr[i][0]=arr[i][0]+arr[i-1][0]
    }
    for(let i=1;i<arr[0].length;i++){
        arr[0][i]=arr[0][i]+arr[0][i-1]
    }
    for(let i=1;i<arr.length;i++){
        for(let j=1;j<arr[0].length;j++){
            arr[i][j]=Math.min(arr[i][j]+arr[i-1][j],arr[i][j]+arr[i][j-1])
        }
    }
    return arr
}

案例四:最长公共子序列

给定两个字符串str1和str2,返回两个字符串的最长公共子序列,例如:str1=”1A2C3D4B56”,str2=”B1D23CA45B6A”,”123456”和”12C4B6”都是最长公共子序列,则最长公共子序列长度为6,dp[i][j]的值必然和dp[i-1][j],dp[i][j-1],dp[i-1][j-1]相关,dp[i][j]取三者之间的最大值

function maxTogetherStr(str1, str2) {
    let m=str2.length+1;
    let n=str1.length+1;
    let arr=new Array(m).fill(0).map(v=>new Array(n).fill(0));
    for(let i=1;i<m;i++){
        for(let j=1;j<n;j++){
            arr[i][j]=Math.max(arr[i-1][j],arr[i][j-1]);
            if(str2[i-1]===str1[j-1]){
                arr[i][j]=arr[i-1][j-1]+1;
            }
        }
    }
    return arr
}

案例五:最少血量问题

地牢是由M*N个方格构成的网格,骑士位于地牢左上角,公主位于地牢右下角,骑士必须从地牢左上角到达右下角去消灭魔鬼拯救公主,初始时骑士有一正数表示血量,当其血量不大于0时,拯救失败,同时每一步只能向下或向右,请写一个函数求出骑士最少的血量是多少才能救出公主。
dp[i][j] 的含义是如果骑士要走上位置(i,j),并且从该位置选一条最优路径,最后走到右下角,骑士起码应该具备的血量。骑士还要面临向下还是向右的选择,dp[i][j+1] 是骑士选择当前向右走并最终到右下角的血量要求。同理,dp[i+1][j] 是向下走的要求。如果骑士决定向右走,那么骑士在当前位置加完血或者扣完血之后的血量只要等于dp[i][j+1] 即可。那么骑士在加血或扣血之前的血量要求(也就是没有踏上(i,j)位置之前的血量要求),就是dp[i][j+1]-map[i][j]。同时,骑士血量要随时不少于1,所以向右的要求为max{dp[i][j+1]-map[i][j],1}。如果骑士决定向下走,分析方式相同,向下的要求为max{dp[i+1][j]-map[i][j],1}。

let arr=[[-2,-3,3],[-5,-10,1],[0,30,-5]];
function search(arr) {
    let m=arr.length;
    let n=arr[0].length;
    let dp=new Array(m).fill(0).map(v=>new Array(n).fill(0));
    dp[m-1][n-1]=Math.max(1-arr[m-1][n-1],1);
    for(let i=n-2;i>=0;i--){
        dp[i][n-1]=Math.max(dp[i+1][n-1]-arr[i][n-1],1)
    }
    for(let i=n-2;i>=0;i--){
        dp[n-1][i]=Math.max(dp[n-1][i+1]-arr[n-1][i],1)
    }
    for(let i=n-2;i>=0;i--){
        for(let j=n-2;j>=0;j--){
            let right=Math.max(dp[i][j+1]-arr[i][j],1);
            let down=Math.max(dp[i+1][j]-arr[i][j],1);
            dp[i][j]=Math.min(right,down);
        }
    }
    return dp;
}
search(arr);
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值