1. 题号860. 柠檬水找零
本题贪心之处在于面对20块钱优先找10快钱付
这个代码真简洁,只要进行每次的找零操作,把判断放在了最后,只要5块钱个数小于零(说明不够用了)返回false,没有那么多冗余代码
bool lemonadeChange(vector<int>& bills) {
int n=bills.size(),count5=0,count10=0;
for(int m:bills){
if(m==5){
count5++;
}else if(m==10){
count5--;
count10++;
}else if(count10>0){
count10--;
count5--;
}else{
count5-=3;
}
if(count5<0){
return false;
}
}
return true;
}
2. 题号455. 分发饼干
本题贪心之处在于用最小的饼干满足需求最小的孩子,所以要先排序
别人的代码真简洁,用孩子索引表示满足的孩子个数,当有一个数组遍历完就跳出循环
孩子只有在满足条件时候动,保证是需求最小的孩子,不断遍历饼干元素给孩子
int findContentChildren(vector<int>& g, vector<int>& s) {
sort(g.begin(),g.end());
sort(s.begin(),s.end());
int i=0,j=0,n=g.size(),m=s.size();
while(i<n && j<m){
if(g[i]<=s[j]){
i++;
}
j++;
}
return i;
}
3. 题号1217. 玩筹码
奇数到奇数不消耗,偶数到偶数不消耗,把偶数都移动到0,奇数都移动到1,此时奇偶数中小的数量就是消耗的代价(需要整体移动1)
int minCostToMoveChips(vector<int>& position) {
int j=0,o=0;
for(int i:position){
if(i%2==0){
o++;
}else{
j++;
}
}
return j>o ? o:j;
}
4. 题号1046. 最后一块石头的重量
用优先队列,当剩余元素大于等于2时继续,出队列两个元素,如果粉碎结果不唯一就放结果到队列
出循环时元素数量小于等于1,没有元素时返回0,有元素返回top元素即可
int lastStoneWeight(vector<int>& stones) {
priority_queue<int> q;
for(int i:stones){
q.push(i);
}
while(q.size()>1){
int a=q.top();
q.pop();
int b=q.top();
q.pop();
if(a-b!=0){
q.push(a-b);
}
}
return q.empty() ? 0:q.top();
}
5. 题号23. 合并K个升序链表
第一道手打的困难题,没想到一次就通过了,开心
需要自定义比较函数,小顶堆要用大于号
dummy哑结点,p用于遍历
把每个链表头结点都放入优先队列(注意判空),弹出堆顶元素接入p后面,将堆顶元素的后继节点加入队列(注意判空),直到队列空为止
struct cmp{ 比较函数,小顶堆要用大于号
bool operator()(ListNode* a,ListNode* b){
return a->val > b->val;
}
};
ListNode* mergeKLists(vector<ListNode*>& lists) {
priority_queue<ListNode*,vector<ListNode*>,cmp> q;
ListNode* dummy=new ListNode(-1);
ListNode* p=dummy;
for(auto p:lists){
if(p){
q.push(p);
}
}
while(!q.empty()){
ListNode* cur=q.top(); 获取堆顶元素
q.pop(); 弹出
if(cur->next){ 如果下一个元素不为空就加入队列
q.push(cur->next);
}
p->next=cur; 接入答案链表
p=cur;
}
return dummy->next;
}
6. 题号605. 种花问题
贪心在尽可能多种花
bool canPlaceFlowers(vector<int>& flowerbed, int n) {
int length=flowerbed.size(),i=0;
while(i<length && n>0){ 超范围和不符合条件进循环
if(flowerbed[i]==1){
这个位置已经有花了,所以他的右边也种不了,直接+2
i+=2;
}else if(i==length-1 || flowerbed[i+1]==0){
这个位置没种花,他的右边也没有花(包含花在右边界的位置)
n--;
i+=2;
}else{ 这个位置没花,同时他的右边也有花,直接+3
i+=3;
}
}
return n==0;
}
7. 题号944. 删列造序
就是找存在降序的列(就是需要删除的),找到count++就好
int minDeletionSize(vector<string>& A) {
if(!A.size()) return 0;
int count = 0;
for(int i=0;i<A[0].size();i++){ //列,由第一个字符串长度决定
for(int j=0;j<A.size()-1;j++){ //行,由数组内元素个数决定
if(A[j][i] > A[j+1][i]){
count++;
break;
}
}
}
return count;
}
8. 题号435. 无重叠区间
贪心在尽可能排列最多的活动数量
就是排列活动问题,最少取消多少活动,就是求总数-最多能举办的活动
先根据右区间排序,这样所剩时间最多,count=1
第一个活动肯定能举办,记录下右区间,遍历数组,如果元素左区间大于right,说明不重复,count++,更新right为元素右区间。最后返回n-count就是需要删除的区间数
bool static cmp(const vector<int> a,const vector<int> b){
return a[1]<b[1];
}
int eraseOverlapIntervals(vector<vector<int>>& intervals) {
if(intervals.size()==0){
return 0;
}
sort(intervals.begin(),intervals.end(),cmp);
int count=1,temp=intervals[0][1],n=intervals.size();
for(int i=1;i<n;i++){
if(intervals[i][0]>=temp){
count++;
temp=intervals[i][1];
}
}
return n-count;
}
9. 题号452. 用最少数量的箭引爆气球
一支箭可以社保所有重叠的区间的气球,所以问题还是找有几个不重叠的区间,即举办最多的活动
bool static cmp(vector<int> a,vector<int> b){
return a[1]<b[1];
}
int findMinArrowShots(vector<vector<int>>& points) {
int n=points.size();
if(n==0){
return 0;
}
sort(points.begin(),points.end(),cmp);
int temp=points[0][1],count = 1;
for(int i=1;i<n;i++){
if(points[i][0]>temp){
count++;
temp=points[i][1];
}
}
return count;
}
10. 题目121. 买卖股票的最佳时机
数组元素小于等于1直接返回0
遍历数组,更新价格最小值,与收益最大值
int maxProfit(vector<int>& prices) {
if(prices.size()<=1) return 0;
int pmax=0,pmin=prices[0],n=prices.size();
for(int i=1;i<n;i++){
pmin=min(pmin,prices[i]);
pmax=max(pmax,prices[i]-pmin);
}
return pmax;
}
11. 题号122. 买卖股票的最佳时机 II
只要今天比昨天价高就卖,即使以后又增值了,也可以接着卖
如137不需要等到最高值再卖,因为3时相当于又买了
把握每一次机会就好
int maxProfit(vector<int>& prices) {
int n=prices.size(),ans=0;
for(int i=0;i<n-1;i++){
if(prices[i]<prices[i+1]){
ans+=(prices[i+1]-prices[i]);
}
}
return ans;
}
12. 题号1005. K 次取反后最大化的数组和
有负数先转负数,没有负数转最小的正数
先按绝对值从大到小排序,为了最小的正数会出现在末尾,这样只需要排序一次就好
遍历数组找负数反转,出循环如果k为偶数,不用动(反转同一个数偶数次,还为本身,到这步数组里已经没有负数或者次数不够用了),奇数反转最小正数就好(在末尾)
static bool cmp(const int a,const int b){
return abs(a)>abs(b);
}
int largestSumAfterKNegations(vector<int>& A, int K) {
sort(A.begin(),A.end(),cmp);
for(int i=0;i<A.size();i++){
if(A[i]<0 && K){
A[i]=-A[i];
K--;
}
}
if(K%2==1){
A[A.size()-1]=-A[A.size()-1];
}
int sum=0;
for(int i:A){
sum+=i;
}
return sum;
}
13. 1518. 换酒问题
方法一:
一瓶一瓶换,一次减3个空瓶,加一个空瓶,多喝一瓶
注意初值要为初始酒数
int numWaterBottles(int numBottles, int numExchange) {
int ans = numBottles;
while(numBottles >= numExchange){
ans++;
numBottles -= numExchange;
numBottles++;
}
return ans;
}
方法二:
一批一批换,注意初值也要为初始酒数
int numWaterBottles(int numBottles, int numExchange) {
int ans = numBottles,empty_num = numBottles;
while(empty_num >= numExchange){
ans += empty_num / numExchange;
empty_num = empty_num % numExchange + empty_num / numExchange;
}
return ans;
}
14. 1578. 避免重复字母的最小删除成本
保留删除代价大的,删除代价小的
因为只比较前后两个元素,所以保存代价大的值
int minCost(string s, vector<int>& cost) {
int ans = 0;
for(int i = 1; i < s.size(); i++){
if(s[i] == s[i - 1]){
if(cost[i] > cost[i - 1]){
ans += cost[i - 1];
}
else{
ans += cost[i];
cost[i] = cost[i - 1];
}
}
}
return ans;
}
15. 874. 模拟行走机器人
unordered_set不能装pair类型,所以用set
用两个方向数组表示方向
int robotSim(vector<int>& commands, vector<vector<int>>& obstacles) {
set<pair<int,int>> obset;
int dirx[] = {0,1,0,-1};
int diry[] = {1,0,-1,0};
int curx = 0, cury = 0, dir = 0, ans = 0;
for(auto vec : obstacles){
obset.insert(make_pair(vec[0],vec[1]));
}
for(int c : commands){
if(c == -1){
dir = (dir + 1) % 4;
}
else if(c == -2){
dir = (dir + 3) % 4;
}
else{
for(int j = 0; j < c; j++){
int dx = curx + dirx[dir];
int dy = cury + diry[dir];
if(!obset.count(make_pair(dx,dy))){
curx = dx;
cury = dy;
ans = max(ans,curx * curx + cury * cury);
}
else{
break;
}
}
}
}
return ans;
}
16. 1403. 非递增顺序的最小子序列
桶记录元素个数,从后向前遍历桶,就可以实现优先找最大值,大于sum/2返回
vector<int> minSubsequence(vector<int>& nums) {
vector<int> bucket(100,0),ans;
int sum = 0;
for(int num : nums){
sum += num;
bucket[num - 1]++;
}
int ans_sum = 0;
for(int i = 99; i >= 0; i--){
while(bucket[i] != 0){
ans.push_back(i + 1);
ans_sum += (i + 1);
bucket[i]--;
if(ans_sum > sum / 2){
return ans;
}
}
}
return {};
}
17. 406. 根据身高重建队列
根据身高排序,然后插入相应位置即可,此时他前面的人都比他高
因为要频繁插入,所以用list,list底层用I链表实现,效率高
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;
for(auto vec : people){
int idx = vec[1];
auto it = que.begin();
while(idx--){
it++;
}
que.insert(it,vec);
}
return vector<vector<int>>(que.begin(),que.end());
}
18. 134. 加油站
遍历每个站点,判断能否绕一圈,在此基础上进行优化
如果1到达不了4,那么2、3也到达不了4,1到3汽油大于等于0,比直接从3出发汽油只多不少,所以如果1到达不了4,那么2、3也到达不了4,直接跳到1能到达的最远站点即可(即 i = j)
注意:如果 j < i 直接返回-1,因为这样证明 i 之后的点已经都饶不了一圈了,
如果不直接返会造成死循环(1到3,3到1,永远无法退出循环)
由于相当于一个圈,所以要(j + 1)% n,不能直接++
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
int n = gas.size();
for(int i = 0; i < n; i++){
int j = i;
int num = gas[i];
while(num >= cost[j]){
num = num - cost[j] + gas[(j + 1) % n];
j = (j + 1) % n;
if(j == i) return i;
}
if(j < i) return -1;
i = j;
}
return -1;
}
19. 881. 救生艇
方法一:
最大值与最小值能一个船就走,如果最小值也与最大值走不了,那么最大值只能自己走了
1,2,5 limit = 7,2,5走显然是最合适的,但太麻烦,1,5走不耽误2,2如果能和5走,那他能和任何一个人走(其他人都比5轻),所以和最小值可以得到最大值
用 i<=j 是因为,奇数时总有个人自己一条船
int numRescueBoats(vector<int>& people, int limit) {
sort(people.begin(),people.end());
int ans =0, i = 0, j = people.size() - 1;
while(i <= j){
ans++;
if(people[i] + people[j] <= limit){
i++;
}
j--;
}
return ans;
}
方法二:
用桶记录每个元素出现个数,双指针遍历桶,空间换时间
注意:当 i > j 时要退出循环,否则会多加一次
int numRescueBoats(vector<int>& people, int limit) {
vector<int> bucket(limit + 1, 0);
int ans = 0, i = 1, j = limit;
for(int p : people){
bucket[p]++;
}
while(i <= j){
while(i <= j && bucket[i] <= 0){
i++;
}
while(i <= j && bucket[j] <= 0){
j--;
}
if(i > j) break;
if(i + j <= limit){
bucket[i]--;
}
bucket[j]--;
ans++;
}
return ans;
}
收获与体会
- 优先队列可以当大/小顶堆来用,每次从优先队列中取出元素需要花费O(logn) 的时间
- 只需要排序一次用sort,每次都需要排序用优先队列
- 自定义比较函数时,小顶堆要用大于号
- 排序想每次找最大最小值可以考虑用桶代替,左端为最小值,右端为最大值