文章目录
- 455.分发饼干
- 376. 摆动序列
- 53. 最大子数组和
- 122. 买卖股票的最佳时机 II
- 55. 跳跃游戏
- 1005. K 次取反后最大化的数组和
- 134. 加油站
- 860. 柠檬水找零
- 135. 分发糖果
- 406. 根据身高重建队列
455.分发饼干
题目链接
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;
}
};
376. 摆动序列
题目链接
解题思路
第 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;
}
};
53. 最大子数组和
题目链接
解题思路
局部最优:当前连续和为负数的时候立刻放弃,从下一个元素重新计算连续和,因为负数加上下一个元素连续和只会越来越小。
全局最优:选取最大连续和。
局部最优的情况下,并记录最大的“连续和”,可以推出全局最优。
从代码角度上来讲:
遍历 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;
}
};
122. 买卖股票的最佳时机 II
题目链接
解题思路
题意是获得利润的最大值,可把这个问题看作:当天买入股票,后一天卖出。若后一天卖出时出现亏损,则头一天也就不买入。这样在算利润时,只把每天买卖股票收益为正值时,计入最终总利润。
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;
}
};
55. 跳跃游戏
题目链接
解题思路
题意:数组中的每个元素代表你在该位置可以跳跃的最大长度,判断最终是否能够到达最后一个下标。
使用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;
}
};
1005. K 次取反后最大化的数组和
题目链接
解题思路
题意:对数组中的元素进行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;
}
};
134. 加油站
题目链接
解题思路
由于环路上有 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;
}
};
860. 柠檬水找零
题目链接
解题思路
用five
、ten
两个变量来存储钱包里的5美元和10美元分别有多少张,接着分情况讨论:
- 如果顾客支付的是5美元,则
five++
- 如果顾客支付的是10美元,则先要判断当前钱包中是否有5美元的纸币,然后在进行找零
- 如果顾客支付的是20美元,则首先判断当前钱包中是否同时拥有一张10美元和一张5美元,或者钱包中包含三张5美元,只有这两种情况才能进行找零。
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;
}
};
135. 分发糖果
题目链接
解题思路
该题采用贪心的策略,重点是一定要先确定一边之后,再确定另一边,如果在考虑局部的时候想两边兼顾,就会顾此失彼。
该题采用两次贪心的策略:
- 首先从左向右遍历,比较右边孩子评分比左边高的情况,使得右边孩子的糖果比相邻的左边孩子糖果数多一。
- 然后从右向左遍历,比较左边孩子评分比右边高的情况,那么如果左边孩子的评分高,在确定左边孩子糖果数时,需要将当前左边孩子的糖果数与右边孩子糖果数+1进行比较,取两者的最大值作为左边孩子的糖果数,这样就能满足相邻两个孩子评分更高的孩子会获得更多的糖果。
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;
}
};
406. 根据身高重建队列
题目链接
解题思路
这道题与上一题分发糖果有一点点像,都涉及到两个维度,其技巧就是先确定一边然后贪心另一边,两边一起考虑,就会顾此失彼。
首先我们可以将序列按照身高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],[7,1]]
- 插入[6,1]:
[[7,0],[6,1],[7,1]]
- 插入[5,0]:
[[5,0],[7,0],[6,1],[7,1]]
- 插入[5,2]:
[[5,0],[7,0],[5,2],[6,1],[7,1]]
- 插入[4,4]:
[[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());
}
};