455.分发饼干
自解代码如下:
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 index=s.size()-1;
if(s.size() == 0) return res;
//先遍历胃口,再遍历饼干
for(int i=g.size()-1; i>=0; i--){
//int sum=0;
for(int j=index; j>=0; j--){
//一个饼干即可满足
//sum+=s[j];
if(s[j] >= g[i]){
res++;
index=j-1;
break;
}
}
}
return res;
}
};
刚开始整体代码包括注释部分,但是测试用例没有全部通过,经过修改发现是自己的理解出了问题,自己的理解是认为一个人可以得到多块饼干,但最后发现一个孩子只能得到一块饼干。根据贪心策略,应该尽可能用大饼干去满足胃口大的孩子,故先对两个数组进行排序,从大到小开始喂。看了Carl哥的答案发现代码可以继续优化,只使用一个for循环即可完成,代码如下:
//仅用一个for循环
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 index=s.size()-1;
if(s.size() == 0) return res;
//先遍历胃口,再遍历饼干
for(int i=g.size()-1; i>=0; i--){
if(index>=0 && s[index]>=g[i]){
res++;
index--;
}
}
return res;
}
};
376.摆动序列*(贪心或动态)
卡哥代码如下:
class Solution {
public:
int wiggleMaxLength(vector<int>& nums) {
if(nums.size() == 1) return 1;
int pre=0; //前一对差值
int cur=0; //当前一对差值
int res=1; // 记录峰值个数,默认序列最右边有一个峰值
for(int i=0; i<nums.size()-1; i++){
cur=nums[i+1]-nums[i];
if((pre<=0&&cur>0) || (pre>=0&&cur<0)){
res++;
pre=cur;
}
}
return res;
}
};
本题中三大坑点,需要思考:
1.上下坡中有平坡;
2.数组首尾两端;
3.单调坡中有平坡。
将pre=cur放在if中就是为了排除第三种情况。
在讨论区又看到了一种神仙解法,本质应该是动态规划,代码如下:
class Solution {
public int wiggleMaxLength(int[] nums) {
int n = nums.length;
if (n < 2) {
return n;
}
int up = 1;
int down = 1;
for (int i = 1; i < n; i++) {
if (nums[i] > nums[i - 1]) {
up = down + 1;
}
if (nums[i] < nums[i - 1]) {
down = up + 1;
}
}
return Math.max(up, down);
}
}
53.最大子数组和
代码如下:
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int res=INT32_MIN; //返回最大和
if(nums.size() == 1){
return nums[0];
}
int count=0;
for(int i=0; i<nums.size(); i++){
count+=nums[i];
//res=res<count?count:res;
if(res < count){
res=count;
}
if(count <= 0){
count=0;
}
}
return res;
}
};
面对这道题,我们首先可能会想到有暴力搜索的方法解决,即需要两个for循环,但是很有可能会遇到超时的问题。上面的方法则是在 O ( n ) O(n) O(n)的时间复杂度进行解决。当我们求累加和的时候,当加上下一个数时,和的值为负数,我们就可以考虑将起始位置延后了。另一个需要注意的地方是,对res进行初始赋值的时候,我们设置了res=INT32_MIN,因为当参数数组里的元素全为-1时候,我们给res设置成0的话,返回的结果仍为0,这个结果是不对的。
122.买卖股票的最佳时机ii(可贪心可动态)
代码如下:
class Solution {
public:
//可动态规划,可贪心
//计算每隔一天的利润,最后累加利润只为正数的结果,即是最大利润
int maxProfit(vector<int>& prices) {
int res=0;
for(int i=1; i<prices.size(); i++){
res+=max(prices[i]-prices[i-1],0);
}
return res;
}
};
局部最优为每天的正利润,通过正利润推出全局最优。 局部最优可以推出全局最优,找不出反例,试一试贪心!
55.跳跃游戏
代码如下:
class Solution {
public:
bool canJump(vector<int>& nums) {
int cover=0;
//注意是<=cover
//每次都取最大覆盖范围,每移动一个单位则更新最大覆盖范围
//局部最优:每次取最大跳跃步数(即最大覆盖范围) 整体最优:得到整体最大覆盖范围
for(int i=0; i<=cover; i++){
cover=max(i+nums[i], cover);
if(cover >= nums.size()-1){
return true;
}
}
return false;
}
};
45.跳跃游戏ii(**)
代码如下:
class Solution {
public:
int jump(vector<int>& nums) {
int res=0;
int nextdistance=0;
int curdistance=0;
for(int i=0; i<nums.size()-1; i++){
nextdistance=max(i+nums[i], nextdistance);//下一步最大覆盖距离
if(i == curdistance){ //当前移动下标等于当前最大覆盖距离
curdistance=nextdistance;
res++;
}
}
return res;
}
};
1005.K次取反后最大化的数组和(一道题中两次贪心的思想)
代码如下:
class Solution {
static bool cmp(int a, int b){
return abs(a)>abs(b);
}
public:
int largestSumAfterKNegations(vector<int>& nums, int k) {
int res=0;
//按照绝对值的的大小, 从大到小排序
sort(nums.begin(), nums.end(), cmp);
for(int i=0; i<nums.size(); i++){
if(k>0 && nums[i]<0){
nums[i] *= -1;
k--;
}
}
//如果剩余k值为奇数则转变
if(k % 2==1){
nums[nums.size()-1] *= -1;
}
for(auto i:nums){
res+=i;
}
return res;
}
};
第一步:按照绝对值的大小,从大到小排序;
第二步:从前向后遍历,遇到负数将其变正,同时k–;
第三步:如果k值仍大于0,将数组中最小的元素,反复转变
第四步:求和
134加油站
暴力法代码如下(容易运行超市AC不了),应注意while的使用,环形循环结构应考虑使用while
//暴力法容易超时
class Solution {
public:
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
for(int i=0; i<cost.size(); i++){
int leave=gas[i] - cost[i];//记录剩余油量
int index=(i+1)%cost.size();
//如果leave>=0 则答案不唯一了
while(leave>0 && index!=i){
leave+=gas[index]-cost[index];
index=(index+1)%cost.size();
}
//如果以i为起点跑一圈,剩余油量>=0,返回该起始位置i
if(leave>=0 && index==i){
return i;
}
}
return -1;
}
};
以上代码中,应注意index的使用,同时也应注意%的特性,一个数%另一个数,如a%b,则得值永远不会大于b,在0-(b-1)区间。
第二种方法:贪心代码如下:
//贪心
class Solution {
public:
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
int start=0;
int curnum=0;
int totalnum=0;
for(int i=0; i<cost.size(); i++){
curnum+=gas[i]-cost[i];
totalnum+=gas[i]-cost[i];
if(curnum<0){
start=i+1;
curnum=0;
}
}
if(totalnum<0) return -1;
return start;
}
};
可以换一个思路,首先如果总油量减去总消耗大于等于零那么一定可以跑完一圈,说明 各个站点的加油站 剩油量rest[i]相加一定是大于等于零的。每个加油站的剩余量rest[i]为gas[i] - cost[i]。i从0开始累加rest[i],和记为curSum,一旦curSum小于零,说明[0, i]区间都不能作为起始位置,因为这个区间选择任何一个位置作为起点,到i这里都会断油,那么起始位置从i+1算起,再从0计算curSum。
135.分发糖果(hard)
代码如下:
class Solution {
public:
int candy(vector<int>& ratings) {
vector<int> sweet(ratings.size(),1);
//从前向后,确定右孩子
for(int i=1; i<ratings.size(); i++){
if(ratings[i]>ratings[i-1]){
sweet[i]=sweet[i-1]+1;
}
}
//从后向前,确定左孩子
for(int i=ratings.size()-2; i>=0; i--){
if(ratings[i]>ratings[i+1]){
sweet[i]=max(sweet[i],sweet[i+1]+1);
}
}
int res=0;
for(int i:sweet){
res+=i;
}
return res;
}
};
本题难点:
需要进行两次遍历进行贪心选择,第一次从左向右遍历,确定右孩子分得的糖果数;第二次从右像左遍历确定左孩子的糖果数,如果评分出现左孩子大于右孩子的情况,则sweet[i]=max(sweet[i],sweet[i+1]+1),确保最后确定的糖果数即大于它自己的左边,也大于他自己的右边。
860.柠檬水找零(自解)
本题虽然是easy,但是是我自己解决的思路和Carl哥一样,有点开心。代码如下:
class Solution {
public:
bool lemonadeChange(vector<int>& bills) {
//sort(bills.begin(), bills.end());
int num5=0;
int num10=0;
int num20=0;
for(int i=0; i<bills.size(); i++){
if(bills[i] == 5){
num5++;
}
if(bills[i] == 10){
if(num5<=0) return false;
num10++;
num5--;
}
if(bills[i] == 20){
if(num10>0 && num5>0){
num20++;
num10--;
num5--;
}
else if(num10==0 && num5>0){
num5-=3;
if(num5<0){
return false;
}
num20++;
}
else{
return false;
}
}
}
return true;
}
};
本题的贪心思想就是给20元找零时,有10元则优先用10元,没10元再用5元,因为5元是“万能”的。
406.根据身高重建队列(vector底层扩容原理)
代码如下,本题注意使用list时间效率将会提高,同时应注意vector底层扩容原理,导致实际时间复杂度要高的多,所以考虑使用底层实现原理为链表的list。
//注意使用list<vector<int>> 时间效率要比使用二维vector高的多
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>> res; //list 底层实现是链表
for(int i=0; i<people.size(); i++){
int position=people[i][1];
list<vector<int>>::iterator it=res.begin();
while(position--){
it++;
}
res.insert(it,people[i]);
}
return vector<vector<int>>(res.begin(), res.end());
}
};
本题思路是,应先按照身高大小,进行从大到小排列,再跟据k值进行插入。
452.用最少数量的箭引爆气球
代码如下:
class Solution {
public:
//参数用引用,用形参会超时
static bool cmp(vector<int>& a, vector<int>& b){
return a[0]<b[0];
}
int findMinArrowShots(vector<vector<int>>& points) {
if(points.size() == 0){
return 0;
}
sort(points.begin(), points.end(), cmp);
int res=1;
for(int i=1; i<points.size(); i++){
//不重叠 箭+1
if(points[i][0] > points[i-1][1]){
res++;
}
//重叠 更新最小重叠右边界
//如果气球重叠了,重叠气球中右边边界的最小值之前的区间一定需要一个弓箭。
else{
points[i][1]=min(points[i-1][1],points[i][1]);
}
}
return res;
}
};
435.无重叠区间
本题其实和452.用最少数量的箭引爆气球非常像,弓箭的数量就相当于是非交叉区间的数量,只要把弓箭那道题目代码里射爆气球的判断条件加个等号(认为[0,1][1,2]不是相邻区间),然后用总区间数减去弓箭数量 就是要移除的区间数量了。
但一开始我做这道题却没有考虑到还可以这样,我的思路是按vector[0]大小,从小到大排列,即从左到右排列,有重叠区间即更新end(也就是下次循环需跟他比较大小),代码如下:
class Solution {
public:
static bool cmp(vector<int>& a, vector<int>& b){
return a[0]<b[0];
}
int eraseOverlapIntervals(vector<vector<int>>& intervals) {
if(intervals.size() == 1) return 0;
sort(intervals.begin(), intervals.end(), cmp);
int res=0;
//只判断重叠情况即可
for(int i=1; i<intervals.size(); i++){
//重叠情况
if(intervals[i][0] < intervals[i-1][1]){
intervals[i][1]=min(intervals[i][1],intervals[i-1][1]);
res++;
}
}
return res;
}
};
763.划分字母区间
代码如下:
class Solution {
public:
vector<int> partitionLabels(string s) {
int hash[27]={0};
//记录每个英文字母最后出现的位置
for(int i=0; i<s.size(); i++){
hash[s[i]-'a']=i;
}
vector<int> res;
int right=0;
int left=0;
for(int i=0; i<s.size(); i++){
right=max(right,hash[s[i]-'a']); //找到字符出现的最远边界
if(i==right){
res.push_back(right-left+1);
left=i+1;
}
}
return res;
}
};
56.合并区间
代码如下:
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) {
vector<vector<int>> res;
sort(intervals.begin(), intervals.end(), cmp);
res.push_back(intervals[0]);
for(int i=1; i<intervals.size(); i++){
//有重叠
if(res.back()[1]>=intervals[i][0]){
res.back()[1]=max(res.back()[1],intervals[i][1]);
}
else{
res.push_back(intervals[i]);
}
}
return res;
}
};
738.单调递增的数字
代码如下:
class Solution {
public:
int monotoneIncreasingDigits(int n) {
string str=to_string(n);
int flag=str.size();
for(int i=str.size()-1; i>=1; i--){
if(str[i-1]>str[i]){
str[i-1]--;
flag=i;
}
}
for(int i=flag; i<str.size(); i++){
str[i]='9';
}
return stoi(str);
}
};
注意对flg初始化的时候,不要让flag=0,因为这样遇到n=1234的情况,得到的答案会变为9999。本题的思路就是从后向前遍历,当遇到前一位大于当前位的时候,让前一位减减,当前位变位9,例如当n=98时,9>8,则9变为8,当前位的8变为9,输出答案89。
968.监控二叉树
代码如下:
class Solution {
private:
//0:无覆盖
//1:本节点有摄像头
//2:本节点有覆盖
int res=0;
int travesal(TreeNode* root){
if(root == NULL){
return 2;
}
int left=travesal(root->left);
int right=travesal(root->right);
//情况1 左右孩子都有覆盖 本节点则为0
if(left==2 && right==2){
return 0;
}
//3:左右孩子至少有一个无覆盖
if(left==0 || right==0){
res++;
return 1;
}
//2:左右孩子至少有一个摄像头
if(left==1 || right==1){
return 2;
}
return -1;
}
public:
int minCameraCover(TreeNode* root) {
res=0;
//需要判断头节点是不是无覆盖的情况
if(travesal(root) == 0){
res++;
}
return res;
}
};