文章目录
- 455.分发饼干
- 376. 摆动序列
- 53. 最大子数组和
- 122. 买卖股票的最佳时机 II
- 55. 跳跃游戏
- 1005. K 次取反后最大化的数组和
- 134. 加油站
- 860. 柠檬水找零
- 135. 分发糖果
- 406. 根据身高重建队列
- 452. 用最少数量的箭引爆气球
- 435. 无重叠区间
- 763. 划分字母区间
- 56. 合并区间
- 738. 单调递增的数字
题目链接
C++代码:
class Solution {
public:
int findContentChildren(vector<int>& g, vector<int>& s) {
sort(g.begin(), g.end());
sort(s.begin(), s.end());
int res = 0;
for (int i = 0, j = 0; i < g.size(); i++) {
while (j < s.size() && s[j] < g[i]) j++;
if (j < s.size()) {
res++;
j++;
}
}
return res;
}
};
题目链接
解题思路
第 1 步:摆动序列的连续数字之间的差需要在正数和负数之间交替,因此首先需要去除连续相同的数;
第 2 步:连续数字的收尾值一定在摆动序列中,同时找出数组中的局部最大值和局部最小值放入摆动序列中。
C++代码:
class Solution {
public:
int wiggleMaxLength(vector<int>& nums) {
nums.erase(unique(nums.begin(), nums.end()), nums.end());
int res = 2;
if (nums.size() <= 2) return nums.size();
for (int i = 1; i < nums.size() - 1; i++) {
if (nums[i] > nums[i - 1] && nums[i] > nums[i + 1]) res++;
if (nums[i] < nums[i - 1] && nums[i] < nums[i + 1]) res++;
}
return res;
}
};
题目链接
解题思路
局部最优:当前连续和为负数的时候立刻放弃,从下一个元素重新计算连续和,因为负数加上下一个元素连续和只会越来越小。
全局最优:选取最大连续和。
局部最优的情况下,并记录最大的“连续和”,可以推出全局最优。
从代码角度上来讲:
遍历 nums
,从头开始用 sum
累积,如果 sum
一旦加上 nums[i]
变为负数,那么就应该从 nums[i+1]
开始从 0
累积 sum
了,因为已经变为负数的 sum
,只会拖累总和。
这相当于是暴力解法中的不断调整最大子序和区间的起始位置。
C++代码:
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int sum = 0, res = INT_MIN;
for (int i = 0; i < nums.size(); i++) {
sum += nums[i];
if (sum > res) res = sum; // 取区间累计的最大值(相当于不断确定最大子序终止位置)
if (sum < 0) sum = 0; // 相当于重置最大子序起始位置,因为遇到负数一定是拉低总和
}
return res;
}
};
题目链接
解题思路
题意是获得利润的最大值,可把这个问题看作:当天买入股票,后一天卖出。若后一天卖出时出现亏损,则头一天也就不买入。这样在算利润时,只把每天买卖股票收益为正值时,计入最终总利润。
C++代码:
class Solution {
public:
int maxProfit(vector<int>& prices) {
int res = 0;
if (prices.size() == 1) return res;
for (int i = 1; i < prices.size(); i++)
res += max(0, prices[i] - prices[i - 1]);
return res;
}
};
题目链接
解题思路
题意:数组中的每个元素代表你在该位置可以跳跃的最大长度,判断最终是否能够到达最后一个下标。
使用head
来记录能够到达的最远的位置,如果当前位置能够到达的最远位置比head
要远,则更新head
。通过head
可判断最终能否到达最后一个下标。
那么这个问题就转化为跳跃覆盖范围究竟可不可以覆盖到终点!
每次移动取最大跳跃步数(得到最大的覆盖范围),每移动一个单位,就更新最大覆盖范围head
。
C++代码:
class Solution {
public:
bool canJump(vector<int>& nums) {
int head = 0;
if (nums.size() == 1) return true; // 只有一个元素,就是能达到
for (int i = 0; i <= head; i++) { // 注意这里是小于等于head
head = max(head, i + nums[i]);
if (head >= nums.size() - 1) return true; // 说明可以到达到终点了
}
return false;
}
};
题目链接
解题思路
题意:对数组中的元素进行k次取反,使得最终数组中的元素和最大。
首先将元素从小到大进行排序,依次让数组中较小的负数变为正数,可使元素和达到最大。
如果将所有的负数都转变为正数了,而k依然大于0,此时判断k是否为奇数,若k为奇数,则从小将数组进行排序,将排序后的最小非负数转变为负值。最后求得数组中元素和即可。
C++代码:
class Solution {
public:
int largestSumAfterKNegations(vector<int>& nums, int k) {
int res = 0;
sort(nums.begin(), nums.end());
for (int i = 0; i < nums.size(); i++) {
if (nums[i] < 0 && k > 0) { // 依次将较小的负数转变为正数
nums[i] *= -1;
k--;
}
}
if (k % 2 == 1) { // 此时将最小的非负数乘上-1
sort(nums.begin(), nums.end());
nums[0] *= -1;
}
for (auto c: nums) res += c;
return res;
}
};
题目链接
解题思路
由于环路上有 n
个加油站,那么我们可以从 n
个加油站中依次选取第 i
个加油站作为起点进行讨论
如果把第 i
个加油站作为起点,到达第 i + j
个加油站时,发现当前汽车剩余油量cur_gas
与第i + j
个加油站的汽油总和小于 cost[i + j]
时,也就意味着此时汽车无法到达第i + j + 1
个加油站,那么可推出第 i
个加油站到第 i + j
个加油站中任意一个加油站作为起点都无法到达第i + j + 1
个加油站,因此接下来将第i + j + 1
个加油站作为起点继续讨论。
C++代码:
class Solution {
public:
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
int n = gas.size(); // 加油站的个数
for (int i = 0, j; i < n; ) {
int cur_gas = 0; // 处于起点的汽车剩余油量为0
for (j = 0; j < n; j++) { // 从第i个加油站出发,观察汽车能够走到哪个加油站
int k = (i + j) % n;
cur_gas += gas[k]; // 当汽车到达第 k 个加油站时,加上gas[k]的汽油量
if (cur_gas - cost[k] < 0) break; // 当前汽车的油量无法到达下一个加油站
}
if (j == n) return i; // 表明此时汽车可以绕环行驶一周
i = i + j + 1; // 此时表明第i个加油站到第i+j个加油站作为起点都无法到达第i+j+1个加油站
// 因此把第i+j+1个加油站作为起点继续讨论
}
return -1;
}
};
题目链接
解题思路
用five
、ten
两个变量来存储钱包里的5美元和10美元分别有多少张,接着分情况讨论:
five++
C++代码:
class Solution {
public:
bool lemonadeChange(vector<int>& bills) {
int five = 0, ten = 0;
for (auto x: bills) {
if (x == 5) five++;
else if (x == 10) {
if (!five) return false;
five--;
ten++;
}else {
if (ten && five) ten--, five--;
else if (five >= 3) five -= 3;
else return false;
}
}
return true;
}
};
题目链接
解题思路
该题采用贪心的策略,重点是一定要先确定一边之后,再确定另一边,如果在考虑局部的时候想两边兼顾,就会顾此失彼。
该题采用两次贪心的策略:
C++代码:
class Solution {
public:
int candy(vector<int>& ratings) {
vector<int> nums(ratings.size(), 1);
// 从前向后
for (int i = 1; i < nums.size(); i++) {
if (ratings[i] > ratings[i - 1]) nums[i] = nums[i - 1] + 1;
}
// 从后向前
for (int i = nums.size() - 2; i >= 0; i--) {
if (ratings[i] > ratings[i + 1]) nums[i] = max(nums[i], nums[i + 1] + 1);
}
// 统计结果
int res = 0;
for (auto c: nums) res += c;
return res;
}
};
题目链接
解题思路
这道题与上一题分发糖果有一点点像,都涉及到两个维度,其技巧就是先确定一边然后贪心另一边,两边一起考虑,就会顾此失彼。
首先我们可以将序列按照身高h从大到小来排序,身高相同的话则k小的站前面,让高个子在前面。
这样做的目的主要是为了后边可以按照第二个维度k来将元素逐个重新插入到结果队列中。
优先按身高高的people的k来插入结果队列,后续插入的节点也不会影响前面已经插入的节点,最终按照k的规则完成了结果对列。
整个插入过程如下:
排序完的people: [[7,0], [7,1], [6,1], [5,0], [5,2],[4,4]]
插入的过程:
[[7,0]]
[[7,0],[7,1]]
[[7,0],[6,1],[7,1]]
[[5,0],[7,0],[6,1],[7,1]]
[[5,0],[7,0],[5,2],[6,1],[7,1]]
[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]
此时就按照题目的要求完成了重新排列。
C++代码:
class Solution {
public:
// 身高从大到小排(身高相同k小的站前面)
static bool cmp(const vector<int>& a, const 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>> que; // list底层是链表实现,插入效率比vector高的多
for (int i = 0; i < people.size(); i++) {
int position = people[i][1]; // 插入到下标为position的位置
std::list<vector<int>>::iterator it = que.begin();
while (position--) { // 寻找在插入位置
it++;
}
que.insert(it, people[i]);
}
return vector<vector<int>>(que.begin(), que.end());
}
};
题目链接
解题思路
该题为区间选点的问题
首先将区间按照右端点进行排序,然后将弓箭的位置初始化为第一个区间的右端点。
从前往后遍历所有区间,如果当前区间与弓箭所处位置不想交,这时就更新弓箭,将弓箭的位置重置为当前区间的右端点,并把弓箭数+1。
C++代码:
class Solution {
public:
int findMinArrowShots(vector<vector<int>>& points) {
// 首先将区间按右端点进行排序
sort(points.begin(), points.end(), [](vector<int>& a, vector<int>& b) {
return a[1] < b[1];
});
int res = 1, ed = points[0][1]; // 弓箭的位置ed初始化为第一个气球的右端点
for (int i = 1; i < points.size(); i++) {
if (points[i][0] > ed) { // 新增一支弓箭,并把位置初始化为当前气球的右端点处
res++;
ed = points[i][1];
}
}
return res;
}
};
题目链接
解题思路
本题的题意是从给定的区间集合中移除区间,使得剩余区间互不重叠,求需要移除区间的最小数量。
可以先求该问题的对偶问题:在一个区间集合中选择若干个区间,使得选中的区间之间互不重叠,求可选取区间的最大数量 。
C++代码:
class Solution {
public:
static bool cmp(vector<int>& a, vector<int>& b) {
return a[1] < b[1];
}
int eraseOverlapIntervals(vector<vector<int>>& q) {
sort(q.begin(), q.end(), cmp);
int res = 1, ed = q[0][1];
for (int i = 1; i < q.size(); ++i) {
if (ed <= q[i][0]) {
res++;
ed = q[i][1];
}
}
return q.size() - res;
}
};
题目链接
解题思路
首先需要记录下每个字母最后一次出现的位置,然后从前往后遍历整个数组,不断更新片段的末尾位置,当走到片段末尾时,将当前片段放入结果中,更新片段不断循环下去。
C++代码:
class Solution {
public:
vector<int> partitionLabels(string s) {
unordered_map<int, int> last;
for (int i = 0; i < s.size(); i++) last[s[i]] = i;
vector<int> res;
int start = 0, end = 0;
for (int i = 0; i < s.size(); i++) {
end = max(end, last[s[i]]);
if (i == end) {
res.push_back(end - start + 1);
start = end = i + 1;
}
}
return res;
}
};
题目链接
解题思路
首先将区间按照左端点从小到大进行排序,然后将第一个区间作为需要合并的维护区间,依次判断后续的区间是否和维护区间存在交集。
如果当前的区间与维护区间相交,则更新维护区间的右端点;如果当前区间与维护区间不相交,则两个区间不能合并,把维护区间保存到结果数组中,并将维护区间更新为当前区间,继续进行循环判断。
C++代码:
class Solution {
public:
vector<vector<int>> merge(vector<vector<int>>& q) {
sort(q.begin(), q.end());
int st = q[0][0], ed = q[0][1];
vector<vector<int>> res;
for (int i = 1; i < q.size(); i++) {
if (q[i][0] <= ed) ed = max(ed, q[i][1]);
else {
res.push_back({st, ed});
st = q[i][0];
ed = q[i][1];
}
}
res.push_back({st, ed});
return res;
}
};
题目链接
解题思路
本题需要找到小于或等于n的最大数字,且数字呈单调递增。也就是说如果数字出现了单调递减,我们需要找到数字从高位到低位查找首次出现单调递减的位置k
,并且判断位置k
前面的数字str[k - 1]
是否等于第k
位的数字str[k]
,找到等于str[k]
的最高位,将该位的值减1
,并将其后边的低位全部置为9
,这样才能保障数字单调递增。
C++代码:
class Solution {
public:
vector<vector<int>> merge(vector<vector<int>>& q) {
sort(q.begin(), q.end());
int st = q[0][0], ed = q[0][1];
vector<vector<int>> res;
for (int i = 1; i < q.size(); i++) {
if (q[i][0] <= ed) ed = max(ed, q[i][1]);
else {
res.push_back({st, ed});
st = q[i][0];
ed = q[i][1];
}
}
res.push_back({st, ed});
return res;
}
};