贪心算法

121.买卖股票的最佳时机

描述

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0

分析

  • 利润 = 卖出价格 - 买入价格
  • 遍历迭代最低买入价格
  • 不能在买入前卖出

解答

  int maxProfit(vector<int>& prices) {
    int ans = 0, min_val = prices[0];
    for (auto val : prices) {
      ans = max(ans, val - min_val);
      min_val = min(min_val, val);
    }
    return ans;
  }

135.分发糖果

描述

n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。

你需要按照以下要求,给这些孩子分发糖果:

  • 每个孩子至少分配到 1 个糖果。
  • 相邻两个孩子中,评分更高的那个会获得更多的糖果。

请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目

分析

  • 所有孩子皆拥有 1 个
  • 从左向右遍历,若评分比前面高,则更新糖果数
  • 从右向左遍历,若评分比后面高,且糖果数不满足条件则更新糖果数

解答

  int candy(vector<int>& ratings) {
    int len = ratings.size();
    if (len < 2) return len;
    vector<int> nums(len, 1);
    for (int i = 1; i < len; ++i) {
      if (ratings[i] > ratings[i - 1]) {
        nums[i] = nums[i - 1] + 1;
      }
    }
    for (int i = len - 2; i >= 0; --i) {
      if (ratings[i] > ratings[i + 1]) {
        nums[i] = max(nums[i + 1] + 1, nums[i]);
      }
    }
    return accumulate(nums.begin(), nums.end(), 0);
  }

406.根据身高重建队列

描述

假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高大于或等于 hi 的人。

请你重新构造并返回输入数组 people 所表示的队列。返回的队列应该格式化为数组 queue ,其中 queue[j] = [hj, kj] 是队列中第 j 个人的属性(queue[0] 是排在队列前面的人)。

分析

  • 按身高从高到低排序,身高相同则按前后位置排序
  • 按照高个子的位序插入队列
  • 高个子的位序不受矮个子的位序影响

解答

  vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
    int len = people.size();
    sort(people.begin(), people.end(), [](vector<int> a, vector<int> b) {
      return a[0] > b[0] || (a[0] == b[0] && a[1] < b[1]);
    });
    vector<vector<int>> tmp;
    for (auto val : people) {
      tmp.insert(tmp.begin() + val[1], val);
    }
    return tmp;
  }

435.无重叠区间

描述

给定一个区间的集合 intervals ,其中 intervals[i] = [starti, endi] 。返回 需要移除区间的最小数量,使剩余区间互不重叠

注意 只在一点上接触的区间是 不重叠的。例如 [1, 2][2, 3] 是不重叠的。

分析

  • 按照右侧边界进行排序
  • 一个区间左侧边界小于前一区间右侧边界则区间重叠
  • 迭代右侧边界

解答

  int eraseOverlapIntervals(vector<vector<int>>& intervals) {
    if (intervals.empty()) return 0;
    sort(intervals.begin(), intervals.end(),
         [](vector<int> a, vector<int> b) { return a[1] < b[1]; });
    int total = 0, prev = intervals[0][1];
    for (int i = 1; i < intervals.size(); ++i) {
      if (intervals[i][0] < prev) {
        ++total;
      } else {
        prev = intervals[i][1];
      }
    }
    return total;
  }

452.用最少数量的箭引爆气球

描述

有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points ,其中 points[i] = [xstart, xend] 表示水平直径在 xstart 和 xend之间的气球。你不知道气球的确切 y 坐标。

一支弓箭可以沿着 x 轴从不同点 完全垂直 地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstartxend 且满足 xstart ≤ x ≤ xend则该气球会被 引爆 可以射出的弓箭的数量 没有限制 。 弓箭一旦被射出之后,可以无限地前进。

给你一个数组 points ,*返回引爆所有气球所必须射出的 最小 弓箭数 *。

分析

  • 按照区间左侧边界排序
  • 左侧区间小于前一区间的右侧边界则存在区间重叠
  • 未重叠则弓箭数加一,更新下一边界

解答

int findMinArrowShots(vector<vector<int>>& points) {
    sort(points.begin(), points.end(),
         [](vector<int> a, vector<int> b) { return a[0] < b[0]; });
    int count = 1;
    int pre = points[0][1];
    for (int i = 1; i < points.size(); ++i) {
      if (points[i][0] <= pre) {
        pre = min(pre, points[i][1]);
      } else {
        pre = points[i][1];
        count++;
      }
    }
    return count;
  }

455.分发饼干

描述

假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。

对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是满足尽可能多的孩子,并输出这个最大数值。

分析

  • 将孩子和饼干进行排序
  • 依次匹配

解答

  int findContentChildren(vector<int>& g, vector<int>& s) {
    sort(g.begin(), g.end());
    sort(s.begin(), s.end());
    int i = 0, j = 0;
    int glen = g.size();
    int slen = s.size();
    while (i < glen && j < slen) {
      if (g[i] <= s[j]) {
        i++;
      }
      j++;
    }
    return i;
  }

605.种花问题

描述

假设有一个很长的花坛,一部分地块种植了花,另一部分却没有。可是,花不能种植在相邻的地块上,它们会争夺水源,两者都会死去。

给你一个整数数组 flowerbed 表示花坛,由若干 01 组成,其中 0 表示没种植花,1 表示种植了花。另有一个数 n ,能否在不打破种植规则的情况下种入 n ****朵花?能则返回 true ,不能则返回 false

分析

  • 满足 000 则可以种一朵化
  • 数组首尾各补一个 0 使得全部元素适用上述规则
  • 遍历

解答

  bool canPlaceFlowers(vector<int>& flowerbed, int n) {
    flowerbed.insert(flowerbed.begin(), 0);
    flowerbed.push_back(0);
    int len = flowerbed.size();
    int count = 0;
    int flag = -1;
    for (int i = 1; i < len - 1; ++i) {
      if (flowerbed[i] == 0 && flowerbed[i - 1] == 0 && flowerbed[i + 1] == 0) {
        flowerbed[i] = 1;
        count++;
      }
    }
    return count >= n;
  }

665.非递减数列

描述

给你一个长度为 n 的整数数组 nums ,请你判断在 最多 改变 1 个元素的情况下,该数组能否变成一个非递减数列。

我们是这样定义一个非递减数列的: 对于数组中任意的 i (0 <= i <= n-2),总满足 nums[i] <= nums[i + 1]

分析

  • nums[i] <= nums [i +1],不满足则记录
  • 最多只允许存在 1 个不满足点
  • 不满足点位于首尾可以直接修改
  • 不满足点位于中间则分别考虑
    • nums[i] 减小,需满足 nums[i-1] <= nums[i+1]
    • nums[i+1]增大,需满足 nums[i] <= nums[i +2]

解答

  bool checkPossibility(vector<int>& nums) {
    int len = nums.size();
    int count = 0;
    int idx = -1;
    for (int i = 0; i < len - 1; ++i) {
      if (nums[i] > nums[i + 1]) {
        count++;
        idx = i;
      }
      if (count > 1) return false;
    }
    if (count < 1)
      return true;
    else if (idx == 0 || idx == len - 2)
      return true;
    else
      return (nums[idx - 1] <= nums[idx + 1]) || (nums[idx] <= nums[idx + 2]);
  }

763.划分字母区间

描述

给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。例如,字符串 "ababcc" 能够被分为 ["abab", "cc"],但类似 ["aba", "bcc"] 或 ["ab", "ab", "cc"] 的划分是非法的。

注意,划分结果需要满足:将所有划分结果按顺序连接,得到的字符串仍然是 s 。

返回一个表示每个字符串片段的长度的列表。

分析

  • 题意为相同字符需在同一片段,即区间重叠问题
  • 记录字符的结束区间
  • 将区间存在重叠的字符分划为同一片段

解答

  vector<int> partitionLabels(string s) {
    int n = s.size();
    int last[26];
    for (int i = 0; i < n; ++i) {
      last[s[i] - 'a'] = i;
    }
    vector<int> ans;
    int start = 0, end = 0;
    for (int i = 0; i < n; i++) {
      end = max(end, last[s[i] - 'a']);
      if (end == i) {
        ans.push_back(end - start + 1);
        start = i + 1;
      }
    }
    return ans;
  }