[算法数据结构] 继续总结贪心的题目规律

62 阅读4分钟

昨天共做以下几道题目:股票问题、跳跃游戏、跳跃游戏2、加油站、K次取反、分发糖果、柠檬水找零、根据身高重建队列。其中独立思考做出来的时股票问题、K次取反、柠檬水找零。 有点儿难度的题目:跳跃游戏、跳跃游戏2、加油站。前两个属于区间类型的题目。 中等题目:两个维度权衡的问题:分发糖果、根据身高重建队列。

  1. 区间问题
  • 跳跃游戏

一开始想的是每一步都跳最远,在遇到0的情况下,如何解决,思考的是回退。关键思路是:记录最大的cover的值,然后再在0-cover区间内进行遍历,找到再大的cover值。

给定一个非负整数数组,你最初位于数组的第一个位置。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个位置。 假如为[2, 5, 0, 0]

  • 跳跃游戏2

给你一个非负整数数组 nums ,你最初位于数组的第一个位置。数组中的每个元素代表你在该位置可以跳跃的最大长度。你的目标是使用最少的跳跃次数到达数组的最后一个位置。假设你总是可以到达数组的最后一个位置。

要统计次数,就需要考虑什么时候必须加一次。

class Solution {
public:
    int jump(vector<int>& nums) {
        if (nums.size() == 1) return 0;
        int curDistance = 0;
        int ans = 0;
        int nextDistance = 0;
        for (int i = 0; i < nums.size(); i++) {
            nextDistance = max(nums[i] + i, nextDistance);
            if (i == curDistance) {
                if (curDistance != nums.size() - 1) {
                    ans++;
                    curDistance = nextDistance;
                } else break;
            }
        } 
        return ans;
    }
};

依次遍历数组的数据,若走到当前最大距离(curDistance)还没有走到终点,那么就必须走下一步,把在当前这段(curDistance)中记录的下一步nextDistance更新为curDistance。并且步数加1。

  • 加油站

    加油站一开始的思路是模拟每一站的消耗与补充,没有整体的思考其中的规律。首先是记录每一次经过加油站的rest,并且将其累加,如果累加起来是负数,那么绝无可能绕一圈,消耗的油比补充的油少。并且在这个过程可以维护累积透支的油cur_sum。如果cur_sum是>=0的,那么说明,每一次都刚好能到下一个加油站,并且接受补给。开始的地点为0。 假如cur_sum是负数呢?从最后一个加油站往前看是否能有攒下来的油弥补cur_sum,即透支的油。假如能将其弥补,意味着能够从这个加油站出发,攒油,并且供后面的加油站消耗。返回这个位置i。

          class Solution {
      public:
          int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
              int curSum = 0;
              int min = INT_MAX;
              for (int i = 0; i < gas.size(); i++) {
                  int rest = gas[i] - cost[i];
                  curSum += rest;
                  if (curSum < min) {
                      min = curSum;
                  }
              }
    
              if (curSum < 0) return -1;
              if (min >= 0) return 0;
    
              std::cout << min << std::endl;
              for (int i = gas.size() - 1; i >= 0; i--) {
                  int rest = gas[i] - cost[i];
                  min += rest;
                  if (min >= 0) {
                      return i;
                  }
              }
              return -1;
          }
      };
      
    
  1. 两个维度权衡问题
  • 分发糖果问题

n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。 你需要按照以下要求,给这些孩子分发糖果: 每个孩子至少分配到 1 个糖果。 相邻两个孩子评分更高的孩子会获得更多的糖果。 请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目 。

问题的难点在于没法重新为孩子的评分排序,因为涉及到规则:相邻两个孩子评分更高的孩子会获得更多的糖果。那就要考虑左右孩子的评分。能不能逐一的去判断确定给每个孩子多少糖果呢? 应该要思考的是什么情况下需要考虑两种情况。左右两边的情况。先从第二个节点开始遍历,判断ratings[i] 比ratings[i - 1] 多的话,ratings[i]加1。但是当ratings是不断减小的,即右边的孩子比左边的孩子分数低,那么怎么确保左边的孩子得到的糖果比右边的孩子得到的糖果多呢?从右往左遍历,假如遇到左边的孩子得分比右边的孩子分数高,那么取max(candyVec[i], candyVec[i + 1] + 1)。最后统计结果。

  • 根据身高重建队列

假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高大于或等于 hi 的人。 请你重新构造并返回输入数组 people 所表示的队列。返回的队列应该格式化为数组 queue ,其中 queue[j] = [hj, kj] 是队列中第 j 个人的属性(queue[0] 是排在队列前面的人)。

因为[h,k]中的k是代表了前面比自己高的人有几个。因此先将身高从高到低排序,然后再依次根据k来安排他们的位置。

总的来说,难题还挺多的,套路不唯一,要想清楚怎么做,在什么情况应该怎么做。