代码随想录-贪心算法②|ACM模式

目录

860. 柠檬水找零

题目描述:

输入输出描述:

思路和想法:

406. 根据身高重建队列

题目描述:

输入输出描述:

思路和想法:

452. 用最少数量的箭引爆气球

题目描述:

输入输出描述:

思路和想法:

435. 无重叠区间

题目描述:

输入输出示例:

思路和想法:

763. 划分字母区间

题目描述:

输入输出描述:

思路和想法:

56. 合并区间

题目描述:

输入输出描述:

思路和想法:

738. 单调递增的数字

题目描述:

输入输出描述:

思路和想法:


860. 柠檬水找零

题目描述:

在柠檬水摊上,每一杯柠檬水的售价为 5 美元。顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。

每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。

注意,一开始你手头没有任何零钱。

给你一个整数数组 bills ,其中 bills[i] 是第 i 位顾客付的账。如果你能给每位顾客正确找零,返回 true ,否则返回 false 。

输入输出描述:

示例 1:

输入:bills = [5,5,5,10,20] 输出:true

解释: 前 3 位顾客那里,我们按顺序收取 3 张 5 美元的钞票。 第 4 位顾客那里,我们收取一张 10 美元的钞票,并返还 5 美元。 第 5 位顾客那里,我们找还一张 10 美元的钞票和一张 5 美元的钞票。 由于所有客户都得到了正确的找零,所以我们输出 true。

示例 2:

输入:bills = [5,5,10,10,20] 输出:false

解释: 前 2 位顾客那里,我们按顺序收取 2 张 5 美元的钞票。 对于接下来的 2 位顾客,我们收取一张 10 美元的钞票,然后返还 5 美元。 对于最后一位顾客,我们无法退回 15 美元,因为我们现在只有两张 10 美元的钞票。 由于不是每位顾客都得到了正确的找零,所以答案是 false。

提示:

  • 1 <= bills.length <= 105
  • bills[i] 不是 5 就是 10 或是 20

思路和想法:

只需要记录五美元和十美元个数,遇到20美元时,两种找零方式:①3张5美元 ②一张五美元和一张十美元。

一张五美元和一张十美元这种找零方式---局部优先。

#include 
using namespace std;

bool lemonadeChange(vector& bills) {
    int Fivecount = 0;
    int Tencount = 0;
    for (int i = 0; i < bills.size(); i++)
        {
            if(bills[i] == 5){
                Fivecount++;
            }
            else if(bills[i] == 10){
                Fivecount--;
                Tencount++;
            }
            else if(bills[i] == 20){
                if(Tencount == 0){
                    Fivecount = Fivecount -3;
                }
                if(Tencount > 0){
                    Fivecount--;
                    Tencount--;
                }
            }
            if(Fivecount < 0 || Tencount < 0){
                return false;
            }
        }
    return true;
}

int main(void){
    int num;
    vector bills;
    while (cin >> num) {
        bills.push_back(num);
        if (::getchar() == '\n') {
            break;
        }
    }

    bool result;
    result = lemonadeChange(bills);
    if(result == 1){
        cout << "true" << endl;
    } else{
        cout << "false" << endl;
    }
    return 0;
}
/*
示例1:
5 5 5 10 20
示例2:
5 5 10 10 20
 * */

406. 根据身高重建队列

题目描述:

假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高大于或等于 hi 的人。

请你重新构造并返回输入数组 people 所表示的队列。返回的队列应该格式化为数组 queue ,其中 queue[j] = [hj, kj] 是队列中第 j 个人的属性(queue[0] 是排在队列前面的人)。

输入输出描述:

示例 1:

输入:people = [[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]] 输出:[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]

解释:

编号为 0 的人身高为 5 ,没有身高更高或者相同的人排在他前面。

编号为 1 的人身高为 7 ,没有身高更高或者相同的人排在他前面。

编号为 2 的人身高为 5 ,有 2 个身高更高或者相同的人排在他前面,即编号为 0 和 1 的人。

编号为 3 的人身高为 6 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。

编号为 4 的人身高为 4 ,有 4 个身高更高或者相同的人排在他前面,即编号为 0、1、2、3 的人。

编号为 5 的人身高为 7 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。

因此 [[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]] 是重新构造后的队列。

示例 2:

输入:people = [[6,0],[5,0],[4,0],[3,2],[2,2],[1,4]] 输出:[[4,0],[5,0],[2,2],[3,2],[1,4],[6,0]]

提示:

  • 1 <= people.length <= 2000
  • 0 <= hi <= 106
  • 0 <= ki < people.length
  • 题目数据确保队列可以被重建

思路和想法:

核心思路在二维vector的排序规则上,身高从大到小排(身高相同k小的站前面)。

#include 
using namespace std;

static bool cmp(const vector& a, const vector& b) {
    if (a[0] == b[0]) return a[1] < b[1];   //0,1代表列,当身高相等时,按k升序排列
    return a[0] > b[0];                     //按身高降序排列
}
vector> reconstructQueue(vector>& people) {
    sort(people.begin(), people.end(), cmp);
    list> que;              //list底层是链表实现
    for (int i = 0; i < people.size(); i++)
        {
            int position = people[i][1];   //对应的Ki
            list>::iterator it = que.begin();  //初始位置
            while(position--){              //寻找插入位置
                it++;
            }
            que.insert(it,people[i]);

        }


    return vector>(que.begin(), que.end());

}

int main(void){
    int a;//行数
    int b;//列数
    cin >> a >> b;
    vector> people(a,vector(b));
    for (int i = 0; i < a; ++i) {
        for (int j = 0; j < b; ++j) {
            cin >> people[i][j];
        }
    }

    vector> result;
    result = reconstructQueue(people);
    for (int i = 0; i < a; ++i) {
        for (int j = 0; j < b; ++j) {
            cout << result[i][j] << " ";
        }
        cout << endl;
    }

    return 0;
}
/*
示例1:
6
2
7 0
4 4
7 1
5 0
6 1
5 2
示例2:
6
2
6 0
5 0
4 0
3 2
2 2
1 4
 * */

452. 用最少数量的箭引爆气球

题目描述:

有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points ,其中points[i] = [xstart, xend] 表示水平直径在 xstart 和 xend之间的气球。你不知道气球的确切 y 坐标。

一支弓箭可以沿着 x 轴从不同点 完全垂直 地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足 xstart ≤ x ≤ xend,则该气球会被 引爆 。可以射出的弓箭的数量 没有限制 。 弓箭一旦被射出之后,可以无限地前进。

给你一个数组 points ,返回引爆所有气球所必须射出的 最小 弓箭数

输入输出描述:

示例 1:

输入:points = [[10,16],[2,8],[1,6],[7,12]] 输出:2

解释:气球可以用2支箭来爆破: -在x = 6处射出箭,击破气球[2,8]和[1,6]。 -在x = 11处发射箭,击破气球[10,16]和[7,12]。

示例 2:

输入:points = [[1,2],[3,4],[5,6],[7,8]] 输出:4

解释:每个气球需要射出一支箭,总共需要4支箭。

示例 3:

输入:points = [[1,2],[2,3],[3,4],[4,5]] 输出:2

解释:气球可以用2支箭来爆破: - 在x = 2处发射箭,击破气球[1,2]和[2,3]。 - 在x = 4处射出箭,击破气球[3,4]和[4,5]。

提示:

  • 1 <= points.length <= 105
  • points[i].length == 2
  • -231 <= xstart < xend <= 231 - 1

思路和想法:

局部最优:当气球出现重叠,一起射,所用弓箭最少。全局最优:把所有气球射爆所用弓箭最少。

为了让气球尽可能的重叠,需要对数组进行排序。如果气球重叠了,重叠气球中右边边界的最小值之前的区间一定需要一个弓箭。

#include 
using namespace std;

static bool cmp(vector& a,vector& b){
    return a[0] < b[0];
}
int findMinArrowShots(vector>& points) {
    if(points.size() == 0) return 0;
    sort(points.begin(), points.end(), cmp);

    int result = 1;
    for (int i = 0; i < points.size() - 1; i++)
        {
            if(points[i][1] < points[i + 1][0]){
                result++;
            }
            else{
                points[i + 1][1] = min(points[i][1], points[i + 1][1]);
            }
        }
    return result;
}

int main(void){
    int a;//行数
    int b;//列数
    cin >> a >> b;
    vector> points(a,vector(b));
    for (int i = 0; i < a; ++i) {
        for (int j = 0; j < b; ++j) {
            cin >> points[i][j];
        }
    }

    int result;
    result = findMinArrowShots(points);
    cout << result << endl;
    return 0;
}
/*
示例1:
4
2
10 16
2 8
1 6
7 12
示例2:
4
2
1 2
3 4
5 6
7 8
示例3:
4
2
1 2
2 3
3 4
4 5
 * */

435. 无重叠区间

题目描述:

给定一个区间的集合 intervals ,其中 intervals[i] = [starti, endi] 。返回 需要移除区间的最小数量,使剩余区间互不重叠

输入输出示例:

示例 1:

输入: intervals = [[1,2],[2,3],[3,4],[1,3]] 输出: 1

解释: 移除 [1,3] 后,剩下的区间没有重叠。

示例 2:

输入: intervals = [ [1,2], [1,2], [1,2] ] 输出: 2

解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。

示例 3:

输入: intervals = [ [1,2], [2,3] ] 输出: 0

解释: 你不需要移除任何区间,因为它们已经是无重叠的了。

提示:

  • 1 <= intervals.length <= 105
  • intervals[i].length == 2
  • -5 * 104 <= starti < endi <= 5 * 104

思路和想法:

这道题的思路和用最少的箭射气球非常相似,也是需要进行数组的排序,根据start来进行升序排序,这样易于判断重叠。

它们之间的区别,在于这道题除了判断出重叠外还要进行移除,局部最优---两两比较,发现重叠移除end大的元素,记录去除次数。整体最优---移除区间的最小数量。

#include 
using namespace std;

static const bool cmp(vector& a, vector& b){
    return a[0] < b[0];
}

int eraseOverlapIntervals(vector>& intervals) {
    if(intervals.size() == 1) return 0;
    sort(intervals.begin(), intervals.end(), cmp);

    int count = 0;  //记录移除次数
    for (int i = 1; i < intervals.size(); i++)
        {
            //只需判断右边界大小,来选则留下哪个
            if(intervals[i - 1][1] > intervals[i][0]){
                count++;
                if(intervals[i - 1][1] < intervals[i][1]){
                    intervals[i] = intervals[i - 1];  //移除方式---覆盖
                }
            }
        }
    return count;

}

int main(void){
    int a;//行数
    int b;//列数
    cin >> a >> b;
    vector> intervals(a,vector(b));
    for (int i = 0; i < a; ++i) {
        for (int j = 0; j < b; ++j) {
            cin >> intervals[i][j];
        }
    }

    int result;
    result = eraseOverlapIntervals(intervals);
    cout << result << endl;
    return 0;
}
/*
示例1:
4
2
1 2
2 3
3 4
1 3
示例2:
3
2
1 2
1 2
1 2
示例3:
2
2
1 2
2 3
 * */

763. 划分字母区间

题目描述:

给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。

注意,划分结果需要满足:将所有划分结果按顺序连接,得到的字符串仍然是 s 。

返回一个表示每个字符串片段的长度的列表。

输入输出描述:

示例 1:输入:s = "ababcbacadefegdehijhklij" 输出:[9,7,8]

解释: 划分结果为 "ababcbaca"、"defegde"、"hijhklij" 。 每个字母最多出现在一个片段中。 像 "ababcbacadefegde", "hijhklij" 这样的划分是错误的,因为划分的片段数较少。

示例 2:

输入:s = "eccbbbbdec" 输出:[10]

提示:

  • 1 <= s.length <= 500
  • s 仅由小写英文字母组成

思路和想法:

这道题的重点在于找到每个字母的最远边界---即出现的最右位置。

如果找到之前遍历过的所有字母的最远边界,说明这个边界就是分割点了。此时前面出现过所有字母,最远也就到这个边界了。

可以分为如下两步:

  • 统计每一个字符最后出现的位置
  • 从头遍历字符,并更新字符的最远出现下标,如果找到字符最远出现位置和当前下标相等,这时就找到了分割点。
#include 
using namespace std;

vector partitionLabels(string s) {
    int hash[27] = {0};
    for (int i = 0; i < s.size(); i++) { // 统计每一个字符最后出现的位置
        hash[s[i] - 'a'] = i;
    }

    vector result;
    int left = 0;
    int right = 0;
    for (int i = 0; i < s.size(); i++)
        {
            right = max(right, hash[s[i] - 'a']);
            if(right == i){
                result.push_back(right - left + 1);
                left = right + 1;
            }
        }
    return result;

}

int main(void){
    string s;
    getline(cin,s);


    vector result;
    result = partitionLabels(s);
    int size = result.size();
    for (int i = 0; i < size - 1; ++i) {
        cout << result[i] << ",";
    }
    cout << result[size - 1] << endl;
    return 0;
}
/*
示例1:
ababcbacadefegdehijhklij

示例2:
eccbbbbdec

 * */

56. 合并区间

题目描述:

以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间

输入输出描述:

示例 1:

输入:intervals = [[1,3],[2,6],[8,10],[15,18]] 输出:[[1,6],[8,10],[15,18]]

解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].

示例 2:

输入:intervals = [[1,4],[4,5]] 输出:[[1,5]]

解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。

提示:

  • 1 <= intervals.length <= 104
  • intervals[i].length == 2
  • 0 <= starti <= endi <= 104

思路和想法:

这道题目和射爆气球、无重叠区间类似,需要注意的地方有:

  • 需要进行不断判断重叠并合并,直到不再重叠结束,之后将其放入结果
  • 需要多加一步判断最后一个区间,是否有合并。
#include 
using namespace std;

static bool cmp (const vector& a, const vector& b) {
    return a[0] < b[0];
}
vector> merge(vector>& intervals) {
    vector> result;
    if(intervals.size() == 0) return result;
    sort(intervals.begin(), intervals.end(), cmp);
    bool flag = false; // 标记最后一个区间有没有合并
    int length = intervals.size();

    //合并结束后,将结果放入result
    for (int i = 1; i < length; i++)
        {
            int start = intervals[i - 1][0];
            int end = intervals[i - 1][1];
            while (i < length && intervals[i][0] <= end) { // 合并区间
                end = max(end, intervals[i][1]);    // 不断更新右区间
                if (i == length - 1) flag = true;   // 最后一个区间也合并了
                i++;                                // 继续合并下一个区间
            }
            result.push_back({start, end});
        }
    // 如果最后一个区间没有合并,将其加入result
    if (flag == false) {
        result.push_back({intervals[length - 1][0], intervals[length - 1][1]});
    }
    return result;
}

int main(void){
    int a;
    int b;
    cin >> a >> b;
    vector> intervals(a,vector(b));
    for (int i = 0; i < a; ++i) {
        for (int j = 0; j < b; ++j) {
            cin >> intervals[i][j];
        }
    }

    vector> result;
    result = merge(intervals);
    for (int i = 0; i < result.size(); ++i) {
        for (int j = 0; j < 2; ++j) {
            cout << result[i][j] << " ";
        }
        cout << endl;
    }
    return 0;
}
/*
示例1:
4
2
1 3
2 6
8 10
15 18
示例2:
2
2
1 4
4 5
 * */

738. 单调递增的数字

题目描述:

当且仅当每个相邻位数上的数字 x 和 y 满足 x <= y 时,我们称这个整数是单调递增的。

给定一个整数 n ,返回 小于或等于 n 的最大数字,且数字呈 单调递增

输入输出描述:

示例 1:

输入: n = 10 输出: 9

示例 2:

输入: n = 1234 输出: 1234

示例 3:

输入: n = 332 输出: 299

提示:

  • 0 <= n <= 109

思路和想法:

需要注意的是,①部分递增的情况,②记录从哪开始赋9。

局部最优:遇到strNum[i - 1] > strNum[i]的情况,让strNum[i - 1]--,然后strNum[i]给为9,可以保证这两位变成最大单调递增整数。

全局最优:得到小于等于N的最大单调递增的整数。

#include 
using namespace std;

int monotoneIncreasingDigits(int n) {
    //转换成string字符串操作方便
    string strNum = to_string(n);

    int flag = strNum.size(); //用来标记从哪里开始赋值9
    for (int i = strNum.size() - 1; i > 0; i--) {
        if (strNum[i - 1] > strNum[i] ) {
            flag = i;
            strNum[i - 1]--;
        }
    }
    for (int i = flag; i < strNum.size(); i++) {
        strNum[i] = '9';
    }
    return stoi(strNum);
}

int main(void){
    int n;
    cin >> n;

    int result;
    result = monotoneIncreasingDigits(n);
    cout << result << endl;
    return 0;
}
/*
示例1:
10
示例2:
1234
示例3:
332
 * */

你可能感兴趣的:(代码随想录感想,算法,c++,数据结构,贪心算法)