zoukankan      html  css  js  c++  java
  • (六)动态规划【C++刷题】

    圆环回原点问题

    1.问题描述

    一个环,有n个点,编号从0增加,从原点0出发,每次只能走一步,每步可以顺时针到下一个点,也可以逆时针到上一个点,经过k步回到原点有多少种方法?

    2.输入输出

    • Input:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], k=1/2
    • Output:0/2

    3.算法分析

    *【动态规划】回到0点可以从右面回来,也可以从左面回来,有多少种方法。

    • dp[k][j]表示从j点走k步到达原点0的方法树,转化为相邻的点经过k-1步回到原点的问题:

    \[dp[k][j]=dp[k-1][j+1]+dp[k-1][j-1] \\ 【注:j-1和j+1会超出0至n-1范围】,因而转化为下式:\\ dp[k][j]=dp[k-1][(j-1+n)\%n]+dp[k-1][(j+1)\%n] \]

    4.编程实现

    #include <iostream>
    #include <vector>
    using namespace std;
    
    class Solution {
    public:
        int getStepNum(vector<int> &nums, int k) {
            int n = nums.size();  
            int dp[k+1][n];  //  dp[i][j]表示经过i步到达k点的方法数
            dp[0][0] = 1;  // 经过0步到达0点的方法数为1
            
            for (int i = 1; i < n; i++) {
                dp[0][i] = 0;
            }
            
            for (int i = 1; i <= k; i++) {
                for (int j = 0; j < n; j++) {
                    dp[i][j] = dp[i-1][(j+1) % n] + dp[i-1][(j-1+n) % n];
                }
            }
            return dp[k][0];
        }
    };
    
    int main() {
        vector<int> nums = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
        int k = 2;
        Solution sol;
        
        cout << sol.getStepNum(nums, k);
    }
    

    接雨水

    leetcode:https://leetcode-cn.com/problems/trapping-rain-water/
    Nowcoder:https://www.nowcoder.com/practice/31c1aed01b394f0b8b7734de0324e00f?tpId=188

    1.问题描述

    给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

    2.输入输出

    • Input:height = [0,1,0,2,1,0,1,3,2,1,2,1]
    • Output:6

    3.算法分析

    【解法一:动态规划】对于下标i,下雨后水能到达的最大高度等于下标i两边的最大高度的最小值,下标i处能接到的雨水量等于下标i处的水能到达的最大高度减去height[i]。
    思路:从左向右扫描得到每个height获得到左边最大高度,从右到左扫描得到每个height获得右边最大高度,最后遍历一遍每个下标位置即可得到能接的雨水总量。即两个dp数组。

    • 时间复杂度:\(O(n)\)
    • 空间复杂度:\(O(n)\)
      【解法二:单调栈】维护一个单调栈,单调栈存储的是下标,满足从栈底到栈顶的下标对应的数组height中的元素递减。
      (1)从左到右遍历数组,遍历到下标i时,如果栈内至少有两个元素,记栈顶元素为top,top的下面一个元素是left,则一定有height[left]≥height[top]。如果height[i]>height[top],则得到一个可以接雨水的区域,该区域的宽度是i-left-1,高度是min(height[left], height[i]-height[top]),根据宽度和高度计算得到该区域能接的雨水量。
      (2)为了得到left,需要将top出栈。对top计算能接的雨水量之后,left变成新的top,重复上述操作,直到栈为空,或者栈顶下标对应的hegiht中的元素大于或等于height[i]。
      (3)在对下标i处计算能接的雨水量之后,将i入栈,继续遍历后面的下标,计算能接的雨水量。遍历结束后得到能接的雨水量。
    • 时间复杂度:\(O(n)\)
    • 空间复杂度:\(O(n)\)
      【解法三:双指针】动态规划中,将leftMax和rightMax最小值决定,从左往右和从右往左,用双指针和两个变量代替两个数组。
    • 时间复杂度:O(n)
    • 空间复杂度:O(1)

    4.编程实现

    // #include <bits/stdio.h>
    #include <iostream>
    #include <vector>
    #include <stack>
    using namespace std;
    
    class Solution {
    public:
      int trap1(vector<int> &heights) {
          // 动态规划法
          int n = heights.size();
          if (n == 0) return 0;
          
          vector<int> leftMax(n, 0);
          leftMax[0] = heights[0];
          for (int i = 1; i < n; i++) {
              leftMax[i] = max(leftMax[i-1], heights[i]);
          }
          
          vector<int> rightMax(n, 0);
          rightMax[n-1] = heights[n-1];
          for (int i = n-2; i >= 0; i--) {
              rightMax[i] = max(rightMax[i+1], heights[i]);
          }
          
          int ans = 0;
          for (int i = 0; i < n; i++) {
              ans += min(leftMax[i], rightMax[i]) - heights[i];
          }
          
          return ans;
      }  
      int trap2(vector<int> &heights) {
          // 单调栈法
          int n = heights.size(), ans = 0;
          stack<int> stk;
          
          for (int i = 0; i < n; i++) {
              while (!stk.empty() && heights[i] > heights[stk.top()]) {
                  int top = stk.top();
                  stk.pop();
                  if (stk.empty()) break;
                  
                  int left = stk.top();
                  int currWidth = i - left - 1;
                  int currHeight = min(heights[left], heights[i]) - heights[top];
                  ans += currWidth * currHeight;
              }
              stk.push(i);
          }
          
          return ans;
      }
      int trap3(vector<int> &heights) {
          // 双指针——dp空间压缩法
          int ans = 0, left = 0, right = heights.size() - 1;
          int leftMax = 0, rightMax = 0;
          
          while (left < right) {
              leftMax = max(leftMax, heights[left]);
              rightMax = max(rightMax, heights[right]);
              
              if (heights[left] < heights[right]) {
                  ans += leftMax - heights[left];
                  ++left;
              } else {
                  ans += rightMax - heights[right];
                  --right;
              }
          }
          
          return ans;
      }
    };
    int main() {
        vector<int> heights;
        int val;
        Solution sol;
        
        getchar();
        while (cin >> val) {
            heights.push_back(val);
            if (cin.get() == ']') break;
        }
        cout << sol.trap3(heights) << endl;
        
        return 0;
    }
    

    最大子序和

    Leetcode:https://leetcode-cn.com/problems/maximum-subarray/)

    1.问题描述

    给定一个整数数组nums,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

    2.输入输出

    Input:nums=[-2, -1, -3, 4, -1, 2, 1, -5, 4]
    Output:6

    3.算法分析

    (1)划分子问题:每个数字都可以选择加入前一位的序列或者当前序列重新开始求和。
    (2)定义子问题:定义一维数组dp[n],结果定义sum
    (3)确定边界:dp[0] = nums[0]。
    (4)状态转移方程确定:dp[i]=max(dp[i-1]+nums[i], nums[i]),sum为max(sum, dp[i])

    4.编程实现

    #include <iostream> 
    #include <string> 
    #include <vector> 
    using namespace std; 
    
    
    class Solution{
    public:
        int maxSubArray(vector<int> & nums) {
            int n = nums.size();
            if (n == 1) return nums[0];
            int pre = 0, sum = nums[0];
            
            for (int i = 0; i < n; i++) {
                pre = max(pre+nums[i], nums[i]);
                sum = max(sum, pre);
            }
            
            return sum;
        }  
    };
    
    int main() { 
        int get;
        Solution sol;
        vector<int> nums; 
        
        getchar();
        while (cin >> get) { 
            nums.push_back(get); 
            if (cin.get() == ']') break; 
        } 
        
        cout << sol.maxSubArray(nums) << endl; 
        return 0; 
    
    } 
    

    买卖股票的最佳时机

    Leetcode:121,https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/

    1.问题描述

    给定一段时间每天的股票价格,已知你只可以买卖各一次,求最大的收益。

    2.输入输出

    Input:[7, 1, 5, 3, 6, 4]

    Output:5

    3.算法分析

    1)划分子问题

    今天的最大收益。

    2)定义问题

    sell的价格。

    3)确定动态规化函数

    前i天的最大收益=max{前i-1天的最大收益,第i天的价格-前i-1天中的最小价格}。

    4.编程实现

    #include <iostream> 
    #include <vector> 
    #include <limits.h> 
    using namespace std; 
    
    int maxProfit(vector<int>& prices) { 
      	int sell = 0, buy = INT_MIN; 
    
      	for (int i = 0; i < prices.size(); i++) { 
            buy = max(buy, -prices[i]); 
            sell = max(sell, buy + prices[i]); 
      	}
      	return sell; 
    } 
    
    int main() { 
    	int n; 
    	vector<int> prices; 
        getchar();
      	while (cin >> n) { 
            prices.push_back(n); 
            if (cin.get() == ']') break; 
      	} 
      	cout << maxProfit(prices) << endl; 
      	return 0; 
    } 
    
    本文为博主原创文章,未经博主允许禁止转载,需转载请注明出处,欢迎指正!
  • 相关阅读:
    欧几里得算法
    匈牙利算法找二分图最大匹配
    hdu3374 String Problem(最小值表示法 + KMP)
    hdu6704 K-th occurrence(后缀数组+RMQ+主席树)
    洛谷 P3809 【模板】后缀排序
    hdu2222 【AC自动机】Keywords Search
    2019杭电多校十 1011 Make Rounddog Happy(rmq + 分治)
    Separate String(Ac自动机+dp)
    2019杭电多校二 I Love Palindrome String(回文自动机)
    HDU2451 Simple Addition expression(数位dp/找规律)
  • 原文地址:https://www.cnblogs.com/caoer/p/15722425.html
Copyright © 2011-2022 走看看