算法基础 典型题(九)动态规划

记录算法基础题思路
step1:
爬楼梯:https://leetcode-cn.com/problems/climbing-stairs/solution/pa-lou-ti-by-leetcode/

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

public:
    int climbStairs(int n) {
        /* 动态规划
        第 i 阶 的 方法数量 = 第 i - 1 方法数量(走一步) + 第 i - 2 阶方法数量(走两步)
        dp[i]=dp[i-1]+dp[i-2] */
        if (n < 3) {
            return n;
        }
        int dp_pre = 1;
        int dp_pos = 2;
        for (int i = 3; i <= n; i++) {
            int dp_next = dp_pre + dp_pos;
            dp_pre = dp_pos;
            dp_pos = dp_next;
        } 
        return dp_pos;
    }

打家劫舍:https://leetcode-cn.com/problems/house-robber/submissions/

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。

    int rob(vector<int>& nums) {
        /* 动态规划
            是否选择i,取决与i-1中最大财宝数量(不选i) 和 i-2最大财宝数量+当前位置财宝数nums[i - 1]哪个大(选i),
            dp[i] = max(dp[i - 1], dp[i - 2] + nums[i - 1]) */
        if (nums.size() < 1) {
            return 0;
        }
        int dp_i_2 = nums[0];
        if (nums.size() < 2) {
            return dp_i_2;
        }
        int dp_i_1 = nums[1] > nums[0] ? nums[1] : nums[0];
        if (nums.size() < 3) {
            return dp_i_1;
        }
        int dp_i = 0;
        for (int i = 3; i <= nums.size(); i++) {
            dp_i = dp_i_2 + nums[i - 1] > dp_i_1 ? dp_i_2 + nums[i - 1] : dp_i_1;
            dp_i_2 = dp_i_1;
            dp_i_1 = dp_i;
        }
        return dp_i;
    }

求最大子段和:https://leetcode-cn.com/problems/maximum-subarray/submissions/

给定一个整数数组nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

    int maxSubArray(vector<int>& nums) {
        /* 动态规划
         如果dp[i]定义为0~i个数最大子段和,起始结束点都没有稳定关系,dp[i] 和 dp[i-1]没有明显的推导关系
         换个思路,如果dp[i]定义为固定以i结束的数最大子段和,那么dp[i-1]和dp[i]就有稳定的推导关系,
            dp[i] = dp[i-1] > 0 ? dp[i-1]+nums[i] : nums[i],
            然后我们求dp[0]到dp[i]遍历过程最大值,即为最终结果 */
        if (nums.size() == 0) {
            return 0;
        }
        if (nums.size() == 1) {
            return nums[0];
        }
        int dp_i_1 = nums[0];
        int max_sum = nums[0];
        for (int i = 1; i < nums.size(); i++) {
            int dp_i = dp_i_1 > 0 ? dp_i_1 + nums[i] : nums[i];
            if (dp_i > max_sum) {
                max_sum = dp_i;
            }
            dp_i_1 = dp_i;
        }
        return max_sum;
    }

找零钱:https://leetcode-cn.com/problems/coin-change/solution/322-ling-qian-dui-huan-by-leetcode-solution/

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
示例:
输入: coins = [1, 2, 5], amount = 11
输出: 3 
解释: 11 = 5 + 5 + 1

    int coinChange(vector<int>& coins, int amount) {
        /* 动态规划 例如coins = [1, 2, 5], amount = 11
           反推法,如果假设可以到11且dp[11] = n,
           dp[11]只能通过dp[10] + 1(coins[0]), dp[9] + 1(coins[2]), dp[6] + 1(coins[5])中可到达情况之一完成,
           相当于dp[i] = min(dp[i-nums[0], dp[i-nums[1]]...dp[i-nums[j]]) + 1,
           注意min如果dp[i-nums[0]~dp[i-nums[j]如果均不能到达即-1,dp[i]为-1,否则为其中最小,
            */
        vector<int> dp;
        for (int i = 0; i <= amount; i++) {
            dp.push_back(-1); // 最开始每个位置都不可达到
        }
        dp[0] = 0; // 金额0为0
        for (int i = 1; i <= amount; i++) {
            for (int j = 0; j < coins.size(); j++) {
                 //如果可以通过上一个达到
                if (i - coins[j] >= 0 && dp[i - coins[j]] != -1) {
                    //没有达到过或者可以更快达到
                    if (dp[i] == -1 || dp[i] > dp[i - coins[j]] + 1) {
                        dp[i] = dp[i - coins[j]] + 1;
                    }
                }
            }
        }
        return dp[amount];
    }

三角形:https://leetcode-cn.com/problems/triangle/

给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。
例如,给定三角形:
[
     [2],
    [3,4],
   [6,5,7],
  [4,1,8,3]
]
自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。

    int minimumTotal(vector<vector<int>>& triangle) {
        /* 动态规划,从底部往上推导
            设置dp[i][j]为从底部走到dp[i][j]出的最优解即最短路径和,
            dp[i][j] = min(dp[i+1][j], dp[i+1][j+1]) + triangle[i][j],
            最底部的一层最优值即为最底一层对应数值本身,
            遍历的结果为dp[0][0]; */
        if (triangle.size() == 0) {
            return 0;
        }
        vector <vector<int>> dp;
        /* 构建初始dp数组 */
        for (int i = 0; i < triangle.size(); i++) {
            dp.push_back(vector<int>());
            for (int j = 0; j < triangle[i].size(); j++) {
                dp[i].push_back(0);
            }
        }
        /* 初始化最底部的最优解 */
        for (int i = 0; i < dp.size(); i++) {
            dp[dp.size() - 1][i] = triangle[dp.size() - 1][i];
        }
        /* 从倒数第二层往上遍历推导 */
        for (int i = dp.size() - 2; i >= 0; i--) {
            for (int j = 0; j < dp[i].size(); j++) {
                dp[i][j] = dp[i+1][j] > dp[i+1][j+1] ? 
                    dp[i+1][j+1] + triangle[i][j] :
                    dp[i+1][j] + triangle[i][j];
            }
        }
        return dp[0][0];
    }

 最长上升子序列:https://leetcode-cn.com/problems/longest-increasing-subsequence/submissions/

给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入: [10,9,2,5,3,7,101,18]
输出: 4 
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。

    int lengthOfLIS(vector<int>& nums) {
        /* 动态规划
            如果dp[i]设置为i处的最长序列长度,很难推导dp[i]和dp[i-1]之间的关系,
            切换思路,如果dp[i]为以位置i为结尾的子序列长度,其推导过程如下,
            dp[i](初值为1至少当前这个)=
                 在dp[0]~dp[i-1]中,d[i] = max(d[j] + 1)[满足条件nums[i] > nums[j]]*/
        if (nums.size() == 0) {
            return 0;
        }
        vector <int> dp(nums.size(), 1); //每个位置至少为1
        int max_len = 1;
        for (int i = 1; i < dp.size(); i++) {
            /* 在j为已经遍历的0~i-1中对比,只要nums[i] > nums[j], dp[i]=dp[j]+1*/
            for (int j = 0; j < i; j++) {
                if (nums[i] > nums[j] && dp[i] < dp[j] + 1) {
                    dp[i] = dp[j] + 1;
                }
            }
            max_len = max_len < dp[i] ? dp[i] : max_len;
        }
        return max_len;
    }

最小路径和:https://leetcode-cn.com/problems/minimum-path-sum/submissions/

给定一个包含非负整数的 m x n 网格,
请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。

    int minPathSum(vector<vector<int>>& grid) {
        /* 动态规划
            设置dp[i][j]为到每个点的最小距离,dp[m-1][n-1]为解
            d[i][j] = min(d[i-1][j], d[i][j-1]) + grid[i][j] */
        vector<vector<int>> dp;
        int row = grid.size();
        int column = grid[0].size();
        if (row == 0 || column == 0) {
            return 0;
        }
        /* 构建dp数据结构 */
        for (int i = 0; i < row; i++) {
            dp.push_back(vector<int>(column, 0));
        }
        /* 遍历 */
        dp[0][0] = grid[0][0];
        for (int i = 1; i < column; i++) {
            dp[0][i] = dp[0][i-1] + grid[0][i];
        }
        for (int i = 1; i < row; i++) {
            dp[i][0] = dp[i - 1][0] + grid[i][0];
        }
        for (int i = 1; i < row; i++) {
            for (int j = 1; j < column; j++) {
                dp[i][j] = dp[i - 1][j] > dp[i][j - 1] ?
                    dp[i][j - 1] + grid[i][j] : dp[i - 1][j] + grid[i][j];
            }
        }
        return dp[row - 1][column - 1];
    }

地牢游戏:https://leetcode-cn.com/problems/dungeon-game/submissions/

地下城是由 M x N 个房间组成的二维网格。
公主关在了地下城的右下角。骑士在左上角,穿过地下城拯救公主。
为了尽快到达公主,骑士决定每次只向右或向下移动一步。
骑士的初始健康点数为一个正整数。如果他的健康点数在某一时刻降至 0 或以下会立即死亡。
有些房间由恶魔守卫,进入会失去健康点数(值为负表示骑士将损失健康点数);
其他房间要么是空的(值为 0),要么包含增加健康点魔法球(值为正表将增加健康点数)。
计算确保骑士能够拯救到公主所需的最低初始健康点数。

public:
    int calculateMinimumHP(vector<vector<int>>& dungeon) {
        /* 动态规划
            设置dp[i][j]为在i,j位置至少需要多少血量,从右下角开始遍历,
            起始dp[m-1][n-1] = max(1, 1 - dungeon[m-1][n-1]),
            dp[i][j] = max(1, min(dp[i+1][j], dp[i][j+1]) - dungeon[i][j])
            dp[0][0]为返回值
        */
        vector<vector<int>> dp;
        int row = dungeon.size();
        int column = dungeon[0].size();
        if (row == 0 || column == 0) {
            return 0;
        }
        /* 构建dp数据结构 */
        for (int i = 0; i < row; i++) {
            dp.push_back(vector<int>(column, 0));
        }
        /* 遍历 */
        dp[row - 1][column - 1] = 1 - dungeon[row - 1][column - 1];
        if (dp[row - 1][column - 1] < 1) {
            dp[row - 1][column - 1] = 1;
        }
        cout << row - 1 <<" " << column - 1 << " "<< dp[row - 1][column - 1] << endl;
        for (int i = column - 2; i >= 0; i--) {
            dp[row - 1][i] = dp[row - 1][i + 1] - dungeon[row - 1][i];
            if (dp[row - 1][i] < 1) {
                dp[row - 1][i] = 1;
            }         
        }
        for (int i = row - 2; i >= 0; i--) {
            dp[i][column - 1] = dp[i + 1][column - 1] - dungeon[i][column - 1];
            if (dp[i][column - 1] < 1) {
                dp[i][column - 1] = 1;
            }        
        }
        for (int i = row - 2; i >= 0; i--) {
            for (int j = column - 2; j >= 0; j--) {
                dp[i][j] = dp[i + 1][j] > dp[i][j + 1] ?
                    dp[i][j + 1] - dungeon[i][j] : dp[i + 1][j] - dungeon[i][j];
                if (dp[i][j] < 1) {
                    dp[i][j] = 1;   
                }
            }
        }
        return dp[0][0];
    }

猜你喜欢

转载自blog.csdn.net/runafterhit/article/details/106009405