455.分发饼干
题目:假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。
对每个孩子i
,都有一个胃口值g[i]
,这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干j
,都有一个尺寸s[j]
。如果s[j] >= g[i]
,我们可以将这个饼干j
分配给孩子i
,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
思路:排序+双指针
通过代码:
class Solution {
public:
int findContentChildren(vector<int>& g, vector<int>& s) {
sort(g.begin(), g.end());
sort(s.begin(), s.end());
int res = 0;
int i = 0, j = 0;
while(i < g.size() && j < s.size())
{
if(s[j] >= g[i])
{
i++;
j++;
res++;
}
else
j++;
}
return res;
}
};
376. 摆动序列
题目:如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为**摆动序列 **。第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。
- 例如,
[1, 7, 4, 9, 2, 5]
是一个摆动序列 ,因为差值(6, -3, 5, -7, 3)
是正负交替出现的。 - 相反,
[1, 4, 7, 2, 5]
和[1, 7, 4, 5, 5]
不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。
子序列 可以通过从原始序列中删除一些(也可以不删除)元素来获得,剩下的元素保持其原始顺序。
给你一个整数数组nums
,返回nums
中作为摆动序列的最长子序列的长度 。
思路:贪心,将差值的正负交替看成序列峰谷起伏。
容易看出要想得到最长子序列要删除的就是峰谷之间的过渡元素,最长子序列的长度就是峰谷个数+1。由于只需要长度,所以统计峰谷个数即可。用prediff表示前一个符合条件的差值,curdiff表示当前差值。峰谷交替即可表示为:prediff <= 0 && curdiff > 0 || prediff >= 0 && curdiff < 0
。等于0是为了处理开头两个元素相等的情况。
通过代码:
class Solution {
public:
int wiggleMaxLength(vector<int>& nums) {
if(nums.size() < 2)
return nums.size();
int prediff = nums[1] - nums[0];
int res = (prediff == 0 ? 1 : 2);
for(int i = 2; i < nums.size(); i++)
{
int curdiff = nums[i] - nums[i - 1];
if(prediff <= 0 && curdiff > 0 || prediff >= 0 && curdiff < 0)
{
res++;
prediff = curdiff;
}
}
return res;
}
};
53. 最大子序和
题目:给你一个整数数组nums
,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组是数组中的一个连续部分。
思路:这道题经典做法应该是动态规划,但是贪心也能做。当我们遍历数组统计和的时候,如果某个值加进去之后,使得总和变成负数了,说明这个值不应该算进去(这一段子序列到头了),应该从下一个开始重新计算总和。为什么是负数,因为但凡数组里有一个正数,最大和至少可以单取这一个正数。如果都是负数,最大和明显就是单取那个最大负数。
通过代码:
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int res = INT_MIN;
int cnt = 0;
for(int i = 0; i < nums.size(); i++)
{
cnt += nums[i];
res = max(res, cnt);
if(cnt < 0)
cnt = 0;
}
return res;
}
};
122.买卖股票的最佳时机 II
题目:给你一个整数数组prices
,其中prices[i]
表示某支股票第i
天的价格。
在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。
返回你能获得的最大利润 。
思路:把利润分解为每天为单位的维度,然后收集每天的正利润。
通过代码:
class Solution {
public:
int maxProfit(vector<int>& prices) {
int res = 0;
for(int i = 1; i < prices.size(); i++)
{
int diff = prices[i] - prices[i - 1];
res += max(diff, 0);
}
return res;
}
};
55. 跳跃游戏
题目:给你一个非负整数数组nums
,你最初位于数组的第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个下标,如果可以,返回true
;否则,返回false
。
思路:不一定非要明确一次究竟跳几步,每次取最大的跳跃步数,这个就是可以跳跃的覆盖范围。这个范围内,别管是怎么跳的,反正一定可以跳过来。那么这个问题就转化为跳跃覆盖范围究竟可不可以覆盖到终点!每次移动取最大跳跃步数(得到最大的覆盖范围),每移动一个单位,就更新最大覆盖范围。
通过代码:
class Solution {
public:
bool canJump(vector<int>& nums) {
int cover = 0;
for(int i = 0; i <= cover; i++)
{
cover = max(cover, i + nums[i]);
if(cover >= nums.size() - 1)
return true;
}
return false;
}
};
45.跳跃游戏 II
题目:给定一个长度为n
的0索引整数数组nums
。初始位置为nums[0]
。
每个元素 nums[i]
表示从索引 i
向前跳转的最大长度。换句话说,如果你在 nums[i]
处,你可以跳转到任意 nums[i + j]
处:
0 <= j <= nums[i]
i + j < n
返回到达nums[n - 1]
的最小跳跃次数。生成的测试用例可以到达nums[n - 1]
。
思路:考虑两个范围,一个是当前范围,一个是下一步范围。当前范围是已经计算过的以某一最少步数可到达的范围,并且当前范围覆盖不到终点,例如:以最少步数0可到达的范围为nums[0]
。通过遍历当前范围,即比较i + nums[i]
,可以得到下一步的最大范围,下一步范围的最少步数即为当前步数+1。接上例:下一步范围为0 + nums[0]
,该范围内的最少步数都为0 + 1
。当遍历到当前范围的边界时,也正好得到下一步最大范围。由于当前范围覆盖不到终点,所以需要继续迭代,此时需要步数+1,并把下一步范围当作新的当前范围。若下一步范围(也就是新的当前范围)能够覆盖终点,即可退出遍历。
通过代码:
class Solution {
public:
int jump(vector<int>& nums) {
if(nums.size() == 1)
return 0;
int ans = 0, cur_cover = 0, next_cover = 0;
for(int i = 0; i < nums.size(); i++)
{
next_cover = max(next_cover, i + nums[i]);
if(i == cur_cover)
{
ans++;
cur_cover = next_cover;
if(next_cover >= nums.size() - 1)
break;
}
}
return ans;
}
};
1005.K次取反后最大化的数组和
题目:给你一个整数数组nums
和一个整数k
,按以下方法修改该数组:
- 选择某个下标
i
并将nums[i]
替换为-nums[i]
。
重复这个过程恰好k
次。可以多次选择同一个下标 i
。
以这种方式修改数组后,返回数组 可能的最大和 。
思路:肯定优先翻转负数,并且是最小的负数。如果负数反转完了,那就翻转最小的正数。
通过代码:
class Solution {
public:
int largestSumAfterKNegations(vector<int>& nums, int k) {
int res = 0, min_index = 0;
sort(nums.begin(), nums.end());
for(int i = 0; i < nums.size(); i++)
{
if(nums[i] < 0 && k > 0)
{
k--;
nums[i] = -nums[i];
}
res += nums[i];
if(nums[i] < nums[min_index])
min_index = i;
}
if(k % 2 == 0)
return res;
return res - 2 * nums[min_index];
}
};
134. 加油站
题目:在一条环路上有 n
个加油站,其中第 i
个加油站有汽油 gas[i]
升。
你有一辆油箱容量无限的的汽车,从第 i
个加油站开往第 i+1
个加油站需要消耗汽油 cost[i]
升。你从其中的一个加油站出发,开始时油箱为空。
给定两个整数数组 gas
和 cost
,如果你可以按顺序绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1
。如果存在解,则 保证 它是 唯一 的。
思路:每个加油站的剩余量rest[i]
为gas[i] - cost[i]
。i从0开始累加rest[i]
,和记为curSum,一旦curSum小于零,说明[0, i]
区间都不能作为起始位置,因为这个区间选择任何一个位置作为起点,到i这里都会断油,那么起始位置从i+1算起,再从0计算curSum。
通过代码:
class Solution {
public:
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
int start = 0, total = 0, rest = 0;
for(int i = 0; i < gas.size(); i++)
{
total += gas[i] - cost[i];
rest += gas[i] - cost[i];
if(rest < 0)
{
start = i + 1;
rest = 0;
}
}
if(total < 0)
return -1;
return start;
}
};
135. 分发糖果
题目:n
个孩子站成一排。给你一个整数数组 ratings
表示每个孩子的评分。
你需要按照以下要求,给这些孩子分发糖果:
- 每个孩子至少分配到
1
个糖果。 - 相邻两个孩子评分更高的孩子会获得更多的糖果。
请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目 。
思路:如果将评分大小看成上升下降的波峰和波谷的话(参考376.摆动序列的图),很明显最佳策略是从波谷向两边的坡上扩展,糖果数依次+1。因此,我们只需按照坡度上升的方向处理即可,先正向遍历一遍,再反向遍历一遍。反向遍历是为了将下降的坡转化为上升坡来处理。
通过代码:
class Solution {
public:
int candy(vector<int>& ratings) {
int n = ratings.size();
vector<int> candies(n, 1);
for(int i = 1; i < n; i++)
{
if(ratings[i] > ratings[i - 1])
candies[i] = candies[i - 1] + 1;
}
for(int i = n - 1; i > 0; i--)
{
if(ratings[i - 1] > ratings[i])
candies[i - 1] = max(candies[i - 1], candies[i] + 1);
}
int res = 0;
for(int num : candies)
res += num;
return res;
}
};
860.柠檬水找零
题目:在柠檬水摊上,每一杯柠檬水的售价为 5
美元。顾客排队购买你的产品,(按账单 bills
支付的顺序)一次购买一杯。
每位顾客只买一杯柠檬水,然后向你付 5
美元、10
美元或 20
美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5
美元。
注意,一开始你手头没有任何零钱。
给你一个整数数组 bills
,其中 bills[i]
是第 i
位顾客付的账。如果你能给每位顾客正确找零,返回 true
,否则返回 false
。
思路:模拟即可,注意给20找零的时候优先给10块的。
通过代码:
class Solution {
public:
bool lemonadeChange(vector<int>& bills) {
int cash[2] = {0, 0};
for(int i = 0; i < bills.size(); i++)
{
if(bills[i] == 5)
cash[0]++;
else if(bills[i] == 10)
{
cash[1]++;
cash[0]--;
}
else
{
if(cash[1] > 0)
{
cash[1]--;
cash[0]--;
}
else
cash[0] -=3;
}
if(cash[0] < 0)
return false;
}
return true;
}
};
406.根据身高重建队列
题目:假设有打乱顺序的一群人站成一个队列,数组people
表示队列中一些人的属性(不一定按顺序)。每个people[i] = [hi, ki]
表示第i
个人的身高为 hi
,前面正好有ki
个身高大于或等于hi
的人。
请你重新构造并返回输入数组 people
所表示的队列。返回的队列应该格式化为数组 queue
,其中 queue[j] = [hj, kj]
是队列中第 j
个人的属性(queue[0]
是排在队列前面的人)。
思路:由于高个子的位置会影响矮个子,所以应该先确定高个子。对people数组排序,高个子在前,矮个子在后,一样高的按ki升序。排完序之后,每个人前面都是比他高的,所以下标i就能表示前面有i个人比他高。所以,为了让k回到正确的位置,只需要按照k为下标重新插入队列就可以了(这时前面正好有k个比他高的)。另外,为了减少插入移动开销,采用链表存储。
通过代码:
class Solution {
public:
static bool cmp(vector<int> &a, vector<int> &b){
if(a[0] == b[0])
return a[1] < b[1];
return a[0] > b[0];
}
vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
sort(people.begin(), people.end(), cmp);
list<vector<int>> li;
for(int i = 0; i < people.size(); i++)
{
int pos = people[i][1];
auto iter = li.begin();
advance(iter, pos);
li.insert(iter, people[i]);
}
return vector<vector<int>>(li.begin(), li.end());
}
};
452. 用最少数量的箭引爆气球
题目:有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组points
,其中points[i] = [x_start, x_end]
表示水平直径在x_start
和x_end
之间的气球。你不知道气球的确切 y 坐标。
一支弓箭可以沿着x轴从不同点完全垂直地射出。在坐标x
处射出一支箭,若有一个气球的直径的开始和结束坐标为x_start
,x_end
, 且满足x_start ≤ x ≤ x_end
,则该气球会被引爆 。可以射出的弓箭的数量没有限制 。 弓箭一旦被射出之后,可以无限地前进。
给你一个数组 points
,返回引爆所有气球所必须射出的最小弓箭数 。
思路:很明显优先射重叠的气球,并且重叠要尽可能多。所以先对气球排序,排序之后遍历气球,并维护一个重叠气球的最小右边界。在最小右边界之前肯定是要射一箭的。当遍历到某个气球的左边界大于最小右边界,说明这个气球不和前面的气球重叠,这时射出这一箭,同时要开始维护新的重叠气球右边界。
通过代码:
class Solution {
public:
static bool cmp(vector<int> &a, vector<int> &b){
return a[0] < b[0];
}
int findMinArrowShots(vector<vector<int>>& points) {
sort(points.begin(), points.end(), cmp);
int res = 0, min_right = INT_MAX;
for(int i = 0; i < points.size(); i++)
{
if(points[i][0] <= min_right)
min_right = min(min_right, points[i][1]);
else
{
res++;
min_right = points[i][1];
}
}
return res + 1;
}
};
435. 无重叠区间
题目:给定一个区间的集合intervals
,其中intervals[i] = [starti, endi]
。返回 需要移除区间的最小数量,使剩余区间互不重叠 。
思路:如果能求出无重叠区间的数量,再用总数去剪就是需要移除区间的数量。求无重叠区间类似上一题。因为上一题每遇到一个不重叠的区间就需要对之前重叠的射出一箭。
通过代码:
class Solution {
public:
static bool cmp(vector<int> &a, vector<int> &b){
if(a[0] == b[0])
return a[1] < b[1];
return a[0] < b[0];
}
int eraseOverlapIntervals(vector<vector<int>>& intervals) {
sort(intervals.begin(), intervals.end(), cmp);
int cnt = 1, min_right = INT_MAX;
for(int i = 0; i < intervals.size(); i++)
{
if(intervals[i][0] < min_right)
min_right = min(min_right, intervals[i][1]);
else
{
cnt++;
min_right = intervals[i][1];
}
}
return intervals.size() - cnt;
}
};
763.划分字母区间
题目:给你一个字符串s
。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。
注意,划分结果需要满足:将所有划分结果按顺序连接,得到的字符串仍然是s
。
返回一个表示每个字符串片段的长度的列表。
思路:由于同一个字母只能出现在一个片段里,所以需要找该字母出现的最远距离,也就是string的find_last_of函数。在这个片段里,还有其他字母,其他字母可能有更远的距离,所以遍历一下找到最远的即可。再用一个unordered_map避免重复操作。
通过代码:
class Solution {
public:
vector<int> partitionLabels(string s) {
vector<int> res;
int start = 0;
while(start < s.size())
{
unordered_set<char> hash;
int end = start;
for(int i = start; i < end + 1; i++)
{
if(hash.find(s[i]) != hash.end())
continue;
hash.insert(s[i]);
int pos = s.find_last_of(s[i]);
end = max(end, pos);
}
res.push_back(end - start + 1);
start = end + 1;
}
return res;
}
};
56. 合并区间
题目:以数组intervals
表示若干个区间的集合,其中单个区间为intervals[i] = [starti, endi]
。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。
思路:先排序,让所有的相邻区间尽可能的重叠在一起。然后依次遍历,若重叠就合并。
通过代码:
class Solution {
public:
static bool cmp(vector<int> &a, vector<int> &b){
return a[0] < b[0];
}
vector<vector<int>> merge(vector<vector<int>>& intervals) {
if(intervals.size() == 0)
return {};
vector<vector<int>> res;
sort(intervals.begin(), intervals.end(), cmp);
int left = intervals[0][0], max_right = intervals[0][1];
for(int i = 1; i < intervals.size(); i++)
{
if(intervals[i][0] <= max_right)
max_right = max(max_right, intervals[i][1]);
else
{
res.push_back({left, max_right});
left = intervals[i][0];
max_right = intervals[i][1];
}
}
res.push_back({left, max_right});
return res;
}
};
738.单调递增的数字
题目:当且仅当每个相邻位数上的数字x
和y
满足x <= y
时,我们称这个整数是单调递增的。
给定一个整数n
,返回小于或等于n
的最大数字,且数字呈单调递增 。
思路:从左往右遍历,找到第一个下降的数字的位置,将这个数字减一,其后数位都变成9。如果这个数字前面还有连续的一样的数字,得找到最左边的。例如332,第一个下降的数字为3,但3前面还有相邻的一样的3,所以得找到最左边的3。将3减一,其后数位变成9,得到299。
通过代码:
class Solution {
public:
int monotoneIncreasingDigits(int n) {
string num = to_string(n);
int pos = num.size();
for(int i = 0; i < num.size() - 1; i++)
{
if(num[i] > num[i + 1])
{
pos = i;
break;
}
}
if(pos != num.size())
{
while(pos > 0 && num[pos - 1] == num[pos])
pos--;
num[pos]--;
for(int i = pos + 1; i < num.size(); i++)
num[i] = '9';
}
return stoi(num);
}
};
968.监控二叉树
题目:给定一个二叉树,我们在树的节点上安装摄像头。
节点上的每个摄影头都可以监视其父对象、自身及其直接子对象。
计算监控树的所有节点所需的最小摄像头数量。
思路:头节点不放摄像头只能省一个,而叶节点不放摄像头省下的是指数级的。所以应该从下往上来安排摄像头,即采用后续遍历。而且,最好隔一个放摄像头,避免覆盖范围重合。用0表示本节点无覆盖,1表示本节点有摄像头,2表示本节点有覆盖。单次递归根据左右孩子的状态来决定本节点的返回状态。如果左右孩子但凡有一个没有覆盖,那么本节点就必须要放了;如果左右孩子都有覆盖了,并且其中有一个还有摄像头,那么本节点已经被覆盖到了;如果左右孩子都只是有覆盖,那么为了隔一个安放,本节点不安排摄像头,返回无覆盖状态。
通过代码:
class Solution {
public:
int res = 0;
int traverse(TreeNode *root){
//0: 本节点无覆盖
//1: 本节点有摄像头
//2: 本节点有覆盖
if(!root)
return 2;
int left = traverse(root -> left);
int right = traverse(root -> right);
if(left == 0 || right == 0)
{
res++;
return 1;
}
if(left == 1 || right == 1)
return 2;
return 0;
}
int minCameraCover(TreeNode* root) {
int r = traverse(root);
if(r == 0)
res++;
return res;
}
};