动规算法题记录

目录

贪心算法与动规算法:

剑指offer14-1 剪绳子

剑指offer 14-II

剑指offer 46 把数字翻译成字符串

剑指offer47-礼物的最大价值

剑指49-丑数

剑指offer-60 n个骰子的点数

剑指63 股票的最大利润

HOT100

32.最长的有效括号


贪心算法与动规算法:

剑指offer14-1 剪绳子

给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]...k[m-1] 。请问 k[0]*k[1]*...*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。

长度为n是固定的,剪成几段是随意的。三步:dp数组的定义,base case ,状态转移

  1. dp数组:dp[i]:长度为i的绳子分成m段后的最大乘积。
  2. base case: dp[0],dp[1]没有意义,dp[2]设为1,即分成1*1两个段
  3. 状态转移:先分出一段来,长度为j,那么剩下的要么不分要么分,不分的话剩下i-j,长度为:j*(i-j);分的话就是长度为i-j的绳子的最大乘积再乘上j,即j*dp[i-j];j是不定的,由于j是1的话相当于白切,所以从2开始一直到i,找其中的最大值,即dp[i]=max(dp[i],max(j*(i-j),j*dp[i-j]));
class Solution {
public:
//dp[i] 表示长度为i的绳子剪成m段后的最大乘积
    int cuttingRope(int n) {
     
    vector<int> dp(n+1);
    dp[2]=1;    
    
    for(int i=3;i<=n;i++){
        for(int j=2;j<i;++j)
        dp[i]=max(dp[i],max(j*(i-j),j*dp[i-j]));//
    }

    return dp[n];
    }
};

剑指offer 14-II

题目与上题一致,但是n会增大到1000,这样结果可能会溢出,用动态规模就不能处理了,改用贪心策略:

贪心规则就再与尽可能的将绳子切成3大小的段,这样会使得乘积最大,并对大数取模。

class Solution {
public:
    int cuttingRope(int n) {
    if(n<4){
        return n-1;
    }
    long res=1;
    while(n>4){
     res=res*3%1000000007;
     
     n-=3;
    }
    return res*n%1000000007;
    }
};

所以能用动规解还是用动规解,因为贪心规则不好解释。

剑指offer 46 把数字翻译成字符串

给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。

示例 1:

输入: 12258
输出: 5
解释: 12258有5种不同的翻译,分别是"bccfi", "bwfi", "bczi", "mcfi"和"mzi"

第一版:

class Solution {
public:
    //dp[i] 以i为结尾的数组有多少种不同的翻译方法
    int translateNum(int num) {
  
    string str=to_string(num);
  
    vector<int> dp(str.size()+1);
    dp[0]=1; //第0位相当于空位,因为dp[2]如果是可以组合的,那么要使得i-2+i-1=2,所以初始化dp[0]为1
    dp[1]=1; //第一位只有一个元素,故为1
    for(int i=2;i<=str.size();++i){
        string temp=str.substr(i-2,2);
        if(temp<="25"&&temp>="10"){
            dp[i]=dp[i-2]+dp[i-1];
        }
        else
        dp[i]=dp[i-1];
    }

    return dp[str.size()];

    }
};
  1. dp数组:dp[i]代表以i结尾的数组有多少种不同的翻译方法
  2. base case :图上
  3. 状态转移: 若不组合,则就是不组合的常规方式,即dp[i-1],不加1;若组合,则有dp[i-2]和dp[i-1]两种情况,将两者相加;

第二版:状态压缩

class Solution {
public:
    //dp[i] 以i为结尾的数组有多少种不同的翻译方法
    int translateNum(int num) {
  
    string str=to_string(num);
  
    vector<int> dp(str.size()+1);
   // dp[0]=1; //第0位相当于空位,因为dp[2]如果是可以组合的,那么要使得i-2+i-1=2,所以初始化dp[0]为1
    //dp[1]=1; //第一位只有一个元素,故为1
   int pre2=1,pre1=1;
    for(int i=2;i<=str.size();++i){
        string temp=str.substr(i-2,2);
        if(temp<="25"&&temp>="10"){
         int  cur=pre2+pre1;
            pre2=pre1;
            pre1=cur;
        }
        else{
        int cur=pre1;
        pre2=pre1;
        pre1=cur;
        }
    }

    return pre1;

    }
};

剑指offer47-礼物的最大价值

在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?

基础的动规题,思路:因为是二维网格,所以用一个二维dp数组,

  1. dp数组:dp[i][j]表示到i,j位置可以得到的最大价值
  2. base case:dp[0][0]=grid[0][0]
  3. 状态转移:dp[i][j]只跟它上面和它左边的有关,选取两者较大的一个加上自己的权重即可
class Solution {
public:
    //dp[i][j]: 走到(i,j)所拿到的最大价值
    int maxValue(vector<vector<int>>& grid) {
    int n=grid.size(),m=grid[0].size();
     vector<vector<int>> dp(n,vector<int>(m,0));
     dp[0][0]=grid[0][0];
    
    for(int i=0;i<n;++i){
         for(int j=0;j<m;++j){
            if(i==0&&j==0){continue;}
             if(i==0){
                 dp[i][j]=dp[i][j-1]+grid[i][j];
             }
             else if(j==0){
                 dp[i][j]=dp[i-1][j]+grid[i][j];
             }
             else{dp[i][j]=max(dp[i-1][j],dp[i][j-1])+grid[i][j];}
         }
     }

    return dp[n-1][m-1];
    

    
    }
};

注意处理边界情况,当上侧或者左侧没有值,说明只跟另一个有关。

但还可以优化,让dp数组多申请一行和一列,作为边界行列,初始值为0,这样可以减少逻辑判断。返回值由dp[n-1][m-1]变为dp[n][m]

class Solution {
public:
    //dp[i][j]: 走到(i,j)所拿到的最大价值
    int maxValue(vector<vector<int>>& grid) {
    int n=grid.size(),m=grid[0].size();
  
     vector<vector<int>> dp(n+1,vector<int>(m+1,0));
    for(int i=1;i<=n;++i){
        for(int j=1;j<=m;++j){
            dp[i][j]=max(dp[i-1][j],dp[i][j-1])+grid[i-1][j-1];
        }
    }

     return dp[n][m];
    }
};

剑指49-丑数

我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。

示例:

输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。

这是一道非典型的动态规划,其实说是动规不如说借用动规的归纳思想。因为一个丑数必然从一个比它更小的丑数*2 *3 *5而来,所以和前序的丑数有关。动规三步如下:

  1. dp数组:dp[i]表示第i+1个丑数
  2. base case: dp[0]=1,1是最小的丑数
  3. 状态转移: 既然是从一个更小的丑数*2 *3 *5而来,那么可以设定a,b,c都等于0,分别代表该位*2 *3 *5并选择其中最小的,然后移动该位。这里需要注意三个if,而不是if else ,因为有可能当前有多个值是相等的,那么dp中只保留一次,所以要将结果相等的都移动当前指针(比如dp[a]*2,dp[b]*3都等于6,那么a b都应该移动)
class Solution {
public:
    //dp[i]:第i+1个丑数
    int nthUglyNumber(int n) {
     vector<int> dp(n);
     dp[0]=1;
     int a=0,b=0,c=0;

     for(int i=1;i<n;++i){
         dp[i]=min(min(dp[a]*2,dp[b]*3),dp[c]*5);
         //cout<<dp[i]<<endl;
         if(dp[i]==dp[a]*2){a++;}
         if(dp[i]==dp[b]*3){b++;}
         if(dp[i]==dp[c]*5){c++;}
     }
     return dp[n-1];
    }
};

剑指offer-60 n个骰子的点数

把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。

你需要用一个浮点数数组返回答案,其中第 i 个元素代表这 n 个骰子所能掷出的点数集合中第 i 小的那个的概率。

示例 1:

输入: 1
输出: [0.16667,0.16667,0.16667,0.16667,0.16667,0.16667]
示例 2:

输入: 2
输出: [0.02778,0.05556,0.08333,0.11111,0.13889,0.16667,0.13889,0.11111,0.08333,0.05556,0.02778]

要得出点数第i个小的概率,可以转变为第i个小的数出现的次数,最后除以出现的总次数即可

状态有n个色子,以及出现的次数,所以是二维数组

  1. dp数组:dp[i][j]表示置个数i个色子时出现次数为j
  2. base case: dp[1][j]=1,1个色子6个数字出现次数都为1
  3. 状态转移: dp[i][j]表示置个数i个色子时出现次数为j,那么新投一个色子时还会投出1~6的点数,就是 投i-1个色子的时候的出现的点数加上投这一次时出现的点数刚好等于j时的次数,例如dp[2][3] 就是投1个色子时出现1~6,加上1~6可以等于3的次数,就是1+2,2+1两种,所以 dp[i][j]+=dp[i-1][j-s];
  4. 最后输出投n次时出现的次数再除以总次数即可
class Solution {
public:
    //dp[i][j]:置个数i个色子时出现次数为j
    vector<double> dicesProbability(int n) {
     vector< vector<int>> dp(n+1,vector<int>(n*6+1,0));
      for(int j=1;j<=6;++j){
          dp[1][j]=1;
      }

      for(int i=2;i<=n;++i){
          for(int j=i;j<=n*6+1;++j){
             for(int s=1;s<=6;s++) {             
              if(j-s<0){
                  break;
              }
              dp[i][j]+=dp[i-1][j-s];
             }
          }
      }
      vector<double> res;
      for(int i=1;i<=n*6+1;++i){
          if(dp[n][i]!=0){
              res.emplace_back(dp[n][i]*1.0/pow(n,6));
          }
      }
      return res;
    }
};

剑指63 股票的最大利润

假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?

示例 1:

输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
     注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
示例 2:

输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

思路:状态是每天的股价,选择是当天买进还是不买进,如果dp表示的是第i天买进的利润,那么到最后就无法得到最大股票差值,所以用dp[i]表示i天之前的股票最大差值。

  1. dp数组:dp[i]表示i天之前的股票最大差值,
  2. base case: dp[0]=0
  3. 状态转移:第i天时,买进和不买进主要决定于前一天股票差值和当天差值的最大值,所以需要维持一个变量维持前序的最小值,由于只跟dp[i-1]有关,所以状态压缩,直接用两个变量pre,cur表示dp[i-1]和dp[i]即可。
class Solution {
public:
    //dp[i]:i天之前最大利润
    int maxProfit(vector<int>& prices) {
    if(prices.empty())return 0;
    int n=prices.size()-1;
    
    int minN=INT_MAX;
    int pre=0;
    int cur;
    for(int i=0;i<=n;++i){
        minN=min(minN,prices[i]);
        cur=max(pre,prices[i]-minN);
        pre=cur;
    }
    return cur;
    }
};

HOT100

32.最长的有效括号

给你一个只包含 '(' 和 ')' 的字符串,找出最长有效(格式正确且连续)括号子串的长度。

有效括号,要满足匹配规则;

  1. dp数组:dp[i]表示i结尾的有效字串长度,这样需要一个max记录最大的
  2. base case: dp[0]=0 单个括号算无效
  3. 状态转移:当s[i]为‘(’时,无法跟前面的匹配,所以dp[i]=0;当s[i]为‘)’时,如果s[i-1]是'(',那么可以匹配,dp[i]=dp[i-2]+2; 否则,有可能是((...))这样,需要向前找看看有没有和s[i]匹配的,若dp[i-1]匹配的长度是x,那么可能匹配的地方就是i-x-1,所以如果这个位置是'(',那么就可以匹配,dp[i]=dp[i-1]+2;然后原来i-x-1这个位置是单独的'(',可能不匹配,但是加了i位的’)‘,就匹配了,所以还要加上i-x-1位置前一位是不是有匹配的,连在一起dp[i]=dp[i-dp[i-1]-2]+dp[i-1]+2; 最后注意边界条件即可
class Solution {
public:
    //dp[i]: i结尾的最长有效字串长度
    int longestValidParentheses(string s) {
      if(s.empty()){
          return 0;
      }
      vector<int> dp(s.size());
      dp[0]=0;
      int maxX=0;
      for(int i=1;i<s.size();++i){
         if(s[i]=='('){
             dp[i]=0;
         }
         else{
             if(i==1){
              dp[1]=  (s[0]=='('?2:0);
             }
             else{
           if(s[i-1]=='('){
               
               dp[i]=dp[i-2]+2;
               
           }
           else if(i-dp[i-1]-1>=0&&s[i-dp[i-1]-1]=='('){
               if(i-dp[i-1]-2>=0)
               dp[i]=dp[i-dp[i-1]-2]+dp[i-1]+2;
               else{
                   dp[i]=dp[i-1]+2;
               }
           }
             }
         }
         maxX=max(maxX,dp[i]);
      }
      return maxX;
    }
};

### 华为算法题:分积木问题的详细解法及思路 #### 问题概述 在华为OD机试中,“分积木”问题是考察数据组合、匹配以及优化的经典题目之一。该类问题通常涉及给定一系列不同长度的积木,目标是在满足特定条件下构建尽可能多的层次结构。 #### 输入输出说明 对于此类问题的具体形式化定义如下: - **输入**:一组整数表示各个积木的长度,这些数值之间由单个空格隔开; -1`表明无法完成任务[^3]。 #### 关键约束条件分析 为了有效解决这个问题,需注意几个重要的限制因素: - 所有使用的积木宽度和高度均一致而仅有长度存在差异; - 构建过程中每一层仅允许放置一块单独的砖头或是若干块连续排列并紧密相连的小型砖头构成的整体; - 同一水平面内的总跨度应当保持恒定不变; - 至少需要形成两层以上的建筑体才视为有效的解答方案[^4]。 #### 解决策略探讨 针对上述特性设计了一套较为通用性的处理框架来应对这类挑战: ##### 特殊情况快速响应机制 首先考虑极端情形下的即时判定则: - 当只有一根柱子的时候显然只能竖立起单一层面; - 若恰好拥有两件物品,则依据它们尺寸是否吻合决定能否建立双层构造——完全相同的格意味着可行,否则不行。 ##### 动态规划核心理念阐述 面对更复杂的情况即三者以上的情形下采用动态规划方法论进行探索。具体操作流程包括但不限于以下几个方面: - 对原始序列执行降序排序以便后续计算过程更加直观高效; - 设定变量用于记当前最优解对应的累积厚度值域区间端点位置; - 运用双重循环枚举可能的一级单位扩展量直至触及边界为止,在此期间不断更新全局最佳纪状态直到遍历结束为止。 ```python def max_levels(blocks): blocks.sort(reverse=True) if not blocks or sum(blocks[:2]) == 0: return -1 n = len(blocks) # Special cases handling if n == 1: return 1 elif n == 2 and blocks[0] != blocks[1]: return 1 elif n >= 2 and all(x==blocks[0] for x in blocks[:2]): return min(2, n) dp = [[False]*(sum(blocks)+1) for _ in range(n+1)] count = [0]*(sum(blocks)+1) target_range_start = blocks[0] target_range_end = blocks[0]+blocks[1] result = float('inf') for length in range(target_range_start,target_range_end+1): dp[0][length]=True for i in range(1,n+1): current_block=blocks[i-1] for j in reversed(range(current_block,length+1)): if dp[i-1][j-current_block]: dp[i][j]|=dp[i-1][current_block] if j>=target_range_start<=target_range_end: count[j]+=count[j-current_block]+1 temp_result=min([c for c,l in zip(count,range(len(dp[-1])))if l>=target_range_start and l<=target_range_end],default=float('inf')) result=min(result,temp_result) return int(-1) if math.isinf(result) else int(result//2) print(max_levels([int(i.strip())for i in "3 6 6 3".split()])) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值