代码随想录刷题笔记9——贪心算法

贪心算法理论基础

贪心的本质是选择每一阶段的局部最优,从而达到全局最优

这一类的题目也没什么固定套路,理论上的求解过程如下:

贪心算法一般分为如下四步:

  • 将问题分解为若干个子问题
  • 找出适合的贪心策略
  • 求解每一个子问题的最优解
  • 将局部最优解堆叠成全局最优解

做题的时候,一般就是想想看本题的局部最优是怎么求解的,解出来看看是否满足全局最优,举几个例子如果都不是反例,那就可以用贪心算法做出来了。

分发饼干

例题455(简单)分发饼干

注意要点:

  1. 我的思路是,用最小的饼干满足胃口,所以从最小的饼干开始遍历,直到有一块饼干满足了胃口最小的人,就换下一个人来,饼干则继续遍历;
  2. 就是说,for来控制饼干的遍历,而满足了能喂饱,人的序号就移动到下一个
  3. 为了满足这种算法,两个数组都要先经过排序

文字的叙述不是很好说,简单而言就是小饼干先去喂饱小胃口!

下面贴出代码:

CPP版本

class Solution {
public:
    int findContentChildren(vector<int>& g, vector<int>& s) {
        sort(g.begin(), g.end());
        sort(s.begin(), s.end());
        int index = 0;
        for (int i = 0; i < s.size(); i++)
        {
            if (index < g.size() && g[index] <= s[i]) {index++;}
        }
        return index;
    }
};

C版本

int cmp(const int* a, const int* b)
{
    return *a - *b;
}

int findContentChildren(int* g, int gSize, int* s, int sSize){
    //先排序
    qsort(g, gSize, sizeof(int), cmp);
    qsort(s, sSize, sizeof(int), cmp);

    int index = 0;
    for (int i = 0; i < sSize; i++)
    {
        if (index < gSize && s[i] >= g[index]) {index++;}
    }
    return index;
}

摆动序列

例题376(中等)摆动序列

注意要点:

  1. 如果序列中是单调的,那么就只有首尾需要记录,中间的都可以删除;
  2. 可通过cur和pre来记录当前和之前的元素差只有pre和cur相反才需要记录和更新(pre可以=0)

纯文字叙述可能不是一下子能模拟出来,我把代码随想录上面这道题的精华图拉到这里,可以看一下,再配合文字和代码进行理解:
代码随想录刷题笔记9——贪心算法_第1张图片
下面贴出代码:

CPP版本

class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        if (nums.size() == 1) {return nums.size();}
        int curdiff = 0;
        int prediff = 0;
        int ans = 1;
        for (int i = 1; i < nums.size(); i++)
        {
            curdiff = nums[i] - nums[i - 1];
            if ((prediff <= 0 && curdiff > 0) || (prediff >= 0 && curdiff < 0))
            {
                ans++;
                prediff = curdiff;
            }
        }
        return ans;
    }
};

C版本

int wiggleMaxLength(int* nums, int numsSize){
    if (numsSize < 2) {return numsSize;}
    int prediff = 0, curdiff = 0;
    int result = 1;
    for (int i = 0; i < numsSize - 1; i++)
    {
        curdiff = nums[i + 1] - nums[i];
        if ((prediff <= 0 && curdiff > 0) || (prediff >= 0 && curdiff < 0))
        {
            result++;
            prediff = curdiff;
        }
    }
    return result;
}

最大子数组和

例题53(中等)最大子数组和

注意要点:

  1. 通过count来记录当前的连续数组的和,如果比我的结果result大就更新进去;
  2. count如果<0,那么就可以新选择一个子数组的头,也就是更新count=0。

这道题其实不是很好想,记一下这个代码的逻辑会用就可以了,一下子没想出来很正常的。

下面贴出代码:

CPP版本

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int ans = INT32_MIN;
        int count = 0;
        for (const int& num : nums)
        {
            count += num;
            if (count > ans) {ans = count;}
            if (count < 0) {count = 0;}
        }
        return ans;
    }
};

C版本

int maxSubArray(int* nums, int numsSize){
    int count = 0;
    int result = INT_MIN;
    for (int i = 0; i < numsSize; i++)
    {
        count += nums[i];
        result = count > result ? count : result;
        if (count < 0) {count = 0;}
    }
    return result;
}

买卖股票的最佳时机II

例题122(中等)买卖股票的最佳时机II

注意要点:

  1. 题目强调了当天可以买进卖出同时进行,暗示可以直接贪心法结题;
  2. 买卖股票的利润就是i+1与i对应的价格的差,如果i+1的>i的价格,就可以统计,否则就跳过即可。

下面贴出代码:

CPP版本

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int ans = 0;
        for (int i = 0; i < prices.size() - 1; i++)
        {
            int now = prices[i + 1] - prices[i];
            ans += now > 0 ? now : 0;
        }
        return ans;
    }
};

C版本

int maxProfit(int* prices, int pricesSize){
    int result = 0;
    for (int i = 1; i < pricesSize; i++)
    {
        if (prices[i] > prices[i - 1])
        {
            result += prices[i] - prices[i - 1];
        }
    }
    return result;
}

跳跃游戏

例题55(中等)跳跃游戏

注意要点:

  1. 本题的重点不在于跳跃方式,而是跳跃的成功与否,所以能否达到才是关键;
  2. 重点就是当前格子跳跃后能覆盖到的范围,能覆盖到才需要继续遍历,不然就相当于失败了(没有覆盖到最后面,那就是跳不到)。

下面贴出代码:

CPP版本

class Solution {
public:
    bool canJump(vector<int>& nums) {
        int n = nums.size();
        int cover = 0;
        if (n == 1) {return 1;}
        for (int i = 0; i <= cover; i++)
        {
            cover = max(nums[i] + i, cover);
            if (cover >= n - 1) {return 1;}
        }
        return 0;
    }
};

C版本

bool canJump(int* nums, int numsSize){
    int cover = 0;
    if (numsSize < 2) {return true;}
    for (int i = 0; i <= cover; i++)
    {
        int now = nums[i] + i;
        cover = now > cover ? now : cover;
        if (cover >= numsSize - 1) {return true;}
    }
    return false;
}

例题45(中等)跳跃游戏II

注意要点:

  1. 同样也是通过覆盖距离来完成代码逻辑;
  2. 当且仅当当前位置来到了之前覆盖距离的边界,才刷新下一步的最远覆盖距离

下面贴出代码:

CPP版本

class Solution {
public:
    int jump(vector<int>& nums) {
        int n = nums.size();
        if (n == 1) {return 0;}
        int ans = 0;
        int cur = 0, next = 0;
        for (int i = 0; i < n; i++)
        {
            next = max(nums[i] + i, next);
            if (i == cur)
            {
                ans++;
                if (next >= n - 1) {break;}
                cur = next;
            }
        }
        return ans;
    }
};

C版本

int jump(int* nums, int numsSize){
    if (numsSize == 1) {return 0;}
    int cur = 0;
    int ans = 0;
    int next = 0;
    for (int i = 0; i < numsSize; i++)
    {
        next = (nums[i] + i) > next ? (nums[i] + i) : next;
        if (i == cur)
        {
            cur = next;
            ans++;
            if (next >= numsSize - 1) {break;}
        }
    }
    return ans;
}

k次取反后最大化的数组和

例题1005(简单)k次取反后最大化的数组和

注意要点:

  1. 这道题应该很好想,就是每次都先翻转绝对值最大的负数
  2. 翻转完了还有剩,就重复翻转现在最小的元素直到达到翻转次数
  3. 要注意的是,C++里面sort的cmp要加static!

下面贴出代码:

CPP版本

class Solution {
private:
    static int cmp(const int a, const int b)
    {
        return abs(a) > abs(b);
    }
public:
    int largestSumAfterKNegations(vector<int>& nums, int k) {
        sort(nums.begin(), nums.end(), cmp);
        int n = nums.size();
        for (int i = 0; i < n; i++)
        {
            if (nums[i] < 0 && k)
            {
                nums[i] = -nums[i];
                k--;
            }
        }
        if (k % 2) {nums[n - 1] = -nums[n - 1];}
        int ans = 0;
        for (const int& num : nums) {ans += num;}
        return ans;
    }
};

C版本

int cmp(const int* a, const int* b)
{
    return abs(*b) - abs(*a);
}

int largestSumAfterKNegations(int* nums, int numsSize, int k){
    qsort(nums, numsSize, sizeof(int), cmp);
    int ans = 0;
    for (int i = 0; i < numsSize; i++)
    {
        if (nums[i] < 0 && k)
        {
            nums[i] = -nums[i];
            k--;
        }
    }
    if (k % 2) {nums[numsSize - 1] = -nums[numsSize - 1];}
    for (int i = 0; i < numsSize; i++) {ans += nums[i];}
    return ans;
}

加油站

例题134(中等)加油站

注意要点:

  1. 先说暴力解法(leetcode超时):通过for循环遍历起始加油站,然后通过index=(i + 1)%size来完成从起点开始的遍历,如果能遍历回到当前i且剩余油量>=0那么现在的i就是所求;
  2. 贪心法非常巧妙:统计每一个加油站点的剩余油量,如果站点i油量<0,那么只可能从i+1开始作为起始站点
  3. 最后的判断,就是总的剩余油量>=0,那么贪心法求得的start就是所求;否则本题就无解返回-1。

纯文字叙述不够直观,我把代码随想录的图片拉下来,可以更清晰的理解这个局部求最优的算法:
代码随想录刷题笔记9——贪心算法_第2张图片
下面贴出代码:

CPP版本

class Solution {
public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        int cur = 0;
        int total = 0;
        int start = 0;
        for (int i = 0; i < gas.size(); i++)
        {
            cur += gas[i] - cost[i];
            total += gas[i] - cost[i];
            if (cur < 0)
            {
                start = i + 1;
                cur = 0;
            }
        }
        if (total < 0) {return -1;}
        return start;
    }
};

C版本

int canCompleteCircuit(int* gas, int gasSize, int* cost, int costSize){
    int cur = 0, total = 0;
    int start = 0;
    for (int i = 0; i < gasSize; i++)
    {
        cur += gas[i] - cost[i];
        total += gas[i] - cost[i];
        if (cur < 0)
        {
            start = i + 1;
            cur = 0;
        }
    }
    if (total < 0) {return -1;}
    return start;
}

分发糖果

例题135(困难)分发糖果

注意要点:

  1. 这道题难就难在两边的大小都要考虑,想要一次性全部写出逻辑代码非常困难;一定是要确定一边之后,再确定另一边,例如比较每一个孩子的左边,然后再比较右边;
  2. 遍历,如果更大那就比前一个人的糖果数+1;
  3. 第二次遍历,就需要比较,如果你的前一个人没你大,那么你的糖果数就应该是前一个人+1和你当前糖果数的较大值

这道题很难靠直接头脑中的模拟直接做出来,我这儿再次白嫖代码随想录的示意图,可以更清晰直观的看出解题的步骤:
代码随想录刷题笔记9——贪心算法_第3张图片
下面贴出代码:

CPP版本

class Solution {
public:
    int candy(vector<int>& ratings) {
        vector<int> candy(ratings.size(), 1);
        for (int i = 1; i < candy.size(); i++)
        {
            if (ratings[i] > ratings[i - 1]) {candy[i] = candy[i - 1] + 1;}
        }
        for (int i = candy.size() - 2; i >= 0; i--)
        {
            if (ratings[i] > ratings[i+1]) {candy[i] = max(candy[i+1]+1, candy[i]);}
        }
        int sum = 0;
        for (const int& num : candy) {sum += num;}
        return sum;
    }
};

C版本

int candy(int* ratings, int ratingsSize){
    //贪心两次
    int* candy = (int* )malloc(sizeof(int) * ratingsSize);
    candy[0] = 1;
    for (int i = 1; i < ratingsSize; i++)
    {
        if (ratings[i] > ratings[i - 1]) {candy[i] = 1 + candy[i - 1];}
        else {candy[i] = 1;}
    }
    int sum = 0;
    for (int i = ratingsSize - 1; i > 0; i--)
    {
        sum += candy[i];
        if (ratings[i] < ratings[i - 1])
        {
            candy[i - 1] = candy[i - 1] > (candy[i] + 1) ? candy[i - 1] : candy[i] + 1;
        }
    }
    sum += candy[0];
    return sum;
}

柠檬水找零

例题860(简单)柠檬水找零

注意要点:

  1. 纸币的面值越小就越有用,所以优先使用10元找零

下面贴出代码:

CPP版本

class Solution {
public:
    bool lemonadeChange(vector<int>& bills) {
        int five = 0, ten = 0, twenty = 0;
        for (int bill : bills)
        {
            if (bill == 5) {five++;}
            else if (bill == 10)
            {
                if (five <= 0) {return 0;}
                ten++;
                five--;
            }
            else
            {
                if (five && ten)
                {
                    five--;
                    ten--;
                }
                else if (five >= 3) {five -= 3;}
                else return 0;
            }
        }
        return 1;
    }
};

C版本

bool lemonadeChange(int* bills, int billsSize){
    int* types = (int* )malloc(sizeof(int) * 2);
    types[0] = types[1] = 0;
    for (int i = 0; i < billsSize; i++)
    {
        if (bills[i] == 5)
        {
            types[0]++;
        }
        else if (bills[i] == 10)
        {
            types[1]++;
            if (types[0] <= 0) {return false;}
            types[0]--;
        }
        else
        {
            if (types[1])
            {
                types[1]--;
                if (types[0] <= 0) {return false;}
                types[0]--;
            }
            else if (types[0] < 3) {return false;}
            else types[0] -= 3;
        }
    }
    return true;
}

根据身高重建队列

例题406(中等)根据身高重建队列

注意要点:

  1. 同样有两个元素要考虑,所以要先考虑一个顺序,再考虑另一个;
  2. 优先考虑身高,保证身高是从高到低排列的;
  3. 贪心法精髓的局部最优在这里体现:按照k来决定当前数组插入到答案数组的下标位置,保证局部完成k的要求。

下面贴出代码:

CPP版本

class Solution {
private:
    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];
    }
public:
    vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
        sort(people.begin(), people.end(), cmp);
        list<vector<int>> que;
        for (int i = 0; i < people.size(); i++)
        {
            int pos = people[i][1];
            list<vector<int>>:: iterator it = que.begin();
            while (pos--) {it++;}
            que.insert(it, people[i]);
        }
        return vector<vector<int>> (que.begin(), que.end());
    }
};

C版本

/**
 * Return an array of arrays of size *returnSize.
 * The sizes of the arrays are returned as *returnColumnSizes array.
 * Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().
 */

int cmp(const void* a, const void* b)
{
    int* tmp1 = *(int** )a;
    int* tmp2 = *(int** )b;
    //身高从高到低,如果一样,k从小到大
    return tmp1[0] == tmp2[0] ? tmp1[1] - tmp2[1] : tmp2[0] - tmp1[0];
}

void move(int** people, int peopleSize, int start, int end)
{
    for (int i = end; i > start; i--)
    {
        people[i] = people[i - 1];
    }
}

int** reconstructQueue(int** people, int peopleSize, int* peopleColSize, int* returnSize, int** returnColumnSizes){
    *returnSize = peopleSize;
    *returnColumnSizes = (int* )malloc(sizeof(int) * peopleSize);
    for (int i = 0; i < peopleSize; i++) {(*returnColumnSizes)[i] = 2;}

    qsort(people, peopleSize, sizeof(int*), cmp);
    //排序完以后遍历入队
    for (int i = 0; i < peopleSize; i++)
    {
        //由k判断应该放在哪里
        int pos = people[i][1];
        int* tmp = people[i];
        //需要插入则之后的位置直到他原先的所在前一个位置都要向后移位
        move(people, peopleSize, pos, i);
        people[pos] = tmp;
    }
    return people;
}

重叠区间

例题452(中等)用最少数量的箭引爆气球

注意要点:

  1. 应该能轻松的联想到,只要重叠的区间,我就只用一支箭来搞定
  2. 为了求重叠区间,就需要对数组进行排序,头尾的排序都可以,为了简单就采取头部的排序
  3. 只要重叠,箭的数量就不必自增;
  4. 为了方便,如果是重叠的,每一次还要把最小上界更新到当前i数组

为了更简单的理解,我白嫖了代码随想录的示意图,后面的很多重叠区间的题目都可以套用这张图来思考:
代码随想录刷题笔记9——贪心算法_第4张图片
下面贴出代码:

CPP版本

class Solution {
private:
    static bool cmp(const vector<int>& a, const vector<int>& b)
    {
        return a[0] < b[0];
    }
public:
    int findMinArrowShots(vector<vector<int>>& points) {
        sort(points.begin(), points.end(), cmp);
        int result = 1;
        for (int i = 1; i < points.size(); i++)
        {
            if (points[i][0] > points[i - 1][1])
            {
                result++;
            }
            else
            {
                points[i][1] = min(points[i - 1][1], points[i][1]);
            }
        }
        return result;
    }
};

C版本

int cmp(const void* a, const void* b)
{
    int* tmp1 = *(int** )a;
    int* tmp2 = *(int** )b;
    return tmp1[0] == tmp2[0] ? tmp1[1] > tmp2[1] : tmp1[0] > tmp2[0];
}

int findMinArrowShots(int** points, int pointsSize, int* pointsColSize){
    if (!pointsSize) {return 0;}
    //先按照第一个元素的大小从小到大排列
    qsort(points, pointsSize, sizeof(int*), cmp);

    int num = 1;
    for (int i = 1; i < pointsSize; i++)
    {
        if (points[i][0] > points[i - 1][1])
        {
            num++; // 需要一支箭
        }
        else
        {
            // 更新重叠气球最小右边界
            points[i][1] = points[i-1][1] < points[i][1] ? points[i-1][1] : points[i][1]; 
        }
    }
    return num;
}

例题435(中等)无重叠区间

注意要点:

  1. 本题与上一题类似,就是求重叠区间;同样还是采用左边界的排序继续后续逻辑操作;
  2. 如果当前的头部>全局尾部,更新全局尾部为当前数组尾部即可;
  3. 否则就要更新重叠区间数量,再更新取更小尾部(以此来删去区间更大的数组,减少重叠区间的长度)。

下面贴出代码:

CPP版本

class Solution {
private:
    static bool cmp(const vector<int>& a, const vector<int>& b)
    {
        return a[0] < b[0];
    }
public:
    int eraseOverlapIntervals(vector<vector<int>>& intervals) {
        sort(intervals.begin(), intervals.end(), cmp);
        int count = 0;
        int end = intervals[0][1];
        for (int i = 1; i < intervals.size(); i++)
        {
            if (intervals[i][0] >= end) {end = intervals[i][1];}
            else
            {
                count++;
                end = min(end, intervals[i][1]);
            }
        }
        return count;
    }
};

C版本

int cmp(const void* a, const void* b)
{
    int* tmp1 = *(int** )a;
    int* tmp2 = *(int** )b;
    return tmp1[0] == tmp2[0] ? tmp1[1] > tmp2[1] : tmp1[0] > tmp2[0];
}

int eraseOverlapIntervals(int** intervals, int intervalsSize, int* intervalsColSize){
    if (!intervalsSize) {return 0;}
    qsort(intervals, intervalsSize, sizeof(int*), cmp);

    int num = 0;
    for (int i = 1; i < intervalsSize; i++)
    {
        if (intervals[i][0] < intervals[i - 1][1])
        {
            num++;
            if (intervals[i][1] > intervals[i-1][1])
            {
                intervals[i][1] = intervals[i-1][1];
            }
        }
    }
    return num;
}

例题56(中等)合并区间

注意要点:

  1. 同样是重叠区间的判断,只不过这一题涉及重叠之后的合并;
  2. 可以直接比较当前result数组中的最后一个尾部元素,进而判断是否需要合并。

合并的逻辑如下图所示,这里再次把代码随想录的图片粘贴上来:
代码随想录刷题笔记9——贪心算法_第5张图片
下面贴出代码:

CPP版本

class Solution {
private:
    static bool cmp(const vector<int>& a, const vector<int>& b)
    {
        return a[0] < b[0];
    }
public:
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
        sort(intervals.begin(), intervals.end(), cmp);
        vector<vector<int>> result;
        result.push_back(intervals[0]);
        for (int i = 1; i < intervals.size(); i++)
        {
            if (result.back()[1] >= intervals[i][0])
            {
                result.back()[1] = max(result.back()[1], intervals[i][1]);
            }
            else
            {
                result.push_back(intervals[i]);
            }
        }
        return result;
    }
};

C版本

/**
 * Return an array of arrays of size *returnSize.
 * The sizes of the arrays are returned as *returnColumnSizes array.
 * Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().
 */

int cmp(const void* a, const void* b)
{
    //左边元素从小到大,如果相等,则右边元素从大到小
    int* tmp1 = *(int** )a;
    int* tmp2 = *(int** )b;
    return tmp1[0] == tmp2[0] ? tmp1[1] < tmp2[1] : tmp1[0] > tmp2[0];
}

int** merge(int** intervals, int intervalsSize, int* intervalsColSize, int* returnSize, int** returnColumnSizes){
    qsort(intervals, intervalsSize, sizeof(int*), cmp);
    //开辟答案的二维数组,每列两个元素
    int** ans = (int** )malloc(sizeof(int* ) * intervalsSize);
    for (int i = 0; i < intervalsSize; i++)
    {
        ans[i] = (int* )malloc(sizeof(int) * 2);
    }

    *returnSize = 0;
    ans[(*returnSize)++] = intervals[0];
    for (int i = 1; i < intervalsSize; i++)
    {
        //发生重叠
        if (intervals[i][0] <= ans[(*returnSize) - 1][1])
        {
            ans[(*returnSize) - 1][1] = fmax(ans[(*returnSize) - 1][1], intervals[i][1]);
        }
        else
        {
            ans[(*returnSize)++] = intervals[i];
        }
    }
    *returnColumnSizes = (int* )malloc(sizeof(int) * (*returnSize));
    for (int i = 0; i < *returnSize; i++)
    {
        (*returnColumnSizes)[i] = 2;
    }
    return ans;
}

划分字母区间

例题763(中等)划分字母区间

注意要点:

  1. 遍历的过程中相当于是要找每一个字母的边界,如果找到之前遍历过的所有字母的最远边界,说明这个边界就是分割点了
  2. 借助哈希表从左往右遍历,把当前的位置作为hash的key存入,这样可以保证最后留在hash里的值就是最远位置;
  3. 再次遍历,判断是否达到最远位置,即可计算区间长度。

这一题我做过了一遍,第二遍还是忘了怎么做,把图贴在下面,更好理解和记忆这题的解题思路和流程:
代码随想录刷题笔记9——贪心算法_第6张图片
下面贴出代码:

CPP版本

class Solution {
public:
    vector<int> partitionLabels(string s) {
        int hash[26] = {0};
        for (int i = 0; i < s.size(); i++) {hash[s[i] - 'a'] = i;}
        vector<int> result;
        int left = 0, right = 0;
        for (int i = 0; i < s.size(); i++)
        {
            right = max(right, hash[s[i] - 'a']);
            if (i == right)
            {
                result.push_back(right - left + 1);
                left = right + 1;
            }
        }
        return result;
    }
};

C版本

/**
 1. Note: The returned array must be malloced, assume caller calls free().
 */
int* partitionLabels(char * s, int* returnSize){
    int* hash = (int* )malloc(sizeof(int) * 26);
    //遍历字符串,把当前位置放在对应的hash表中,最后就能得到这个字母最后一次出现的位置
    for (int i = 0; i < strlen(s); i++)
    {
        hash[s[i] - 'a'] = i;
    }

    int* ans = (int* )malloc(sizeof(int) * strlen(s));
    *returnSize = 0;
    int left = 0, right = 0;
    for (int i = 0; i < strlen(s); i++)
    {
        right = right > hash[s[i] - 'a'] ? right : hash[s[i] - 'a'];
        if (i == right)
        {
            ans[(*returnSize)++] = right - left + 1;
            left = right + 1;
        }
    }
    return ans;
}

单调递增的数字

例题738(中等)单调递增的数字

注意要点:

  1. 从后往前遍历,可以把结果进行覆盖,而不用每次都判断然后全部修改;
  2. 贪心的体现在于,只要不满足条件,则低位直接变成9而高位-1
  3. 需要一个标志位flag记录变9的位置

下面贴出代码:

CPP版本

class Solution {
public:
    int monotoneIncreasingDigits(int n) {
        string strNum = to_string(n);
        int flag = strNum.size();
        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);
    }
};

C版本

int my_itoa(int n, char* s)
{
    int count = 0;
    while (n)
    {
        s[count++] = n % 10 + '0';
        n /= 10;
    }
    s[count] = '\0';
    for (int i = 0; i < count / 2; i++)
    {
        int tmp = s[i];
        s[i] = s[count - i - 1];
        s[count - i - 1] = tmp;
    }
    return count;
}

int monotoneIncreasingDigits(int n){
    char* s = (char* )malloc(sizeof(char) * 11);
    //字符串操作每一位的数字更简单
    int len = my_itoa(n, s);
    //printf("s[0] = %c\n", s[0]);
    int flag = len;
    for (int i = len - 1; i > 0; i--)
    {
        if (s[i - 1] > s[i])
        {
            flag = i;
            s[i - 1]--;
        }
    }
    for (int i = flag; i < len; i++)
    {
        s[i] = '9';
    }
    return atoi(s);
}

监控二叉树

例题968(困难)监控二叉树

注意要点:

  1. 根据题意,每一个节点分别有三种状态:该节点无覆盖、该节点有摄像头、该节点有覆盖
  2. 根据这三种状态,从下往上遍历,通过子节点状态判定父节点应处于什么状态(自上而下到了叶子结点就可能多耗费摄像头);
  3. 如果子节点均覆盖,则当前节点一定为无覆盖;
  4. 如果两者之一有摄像头,那么该节点有覆盖;
  5. 如果两者之一无覆盖,那么该节点一定有摄像头;
  6. 最后需要注意递归完成后的头结点状态,如果是无覆盖状态,那么头结点就要再放一个摄像头
  7. 需要注意,对无覆盖的状态判断应早于有摄像头的判断,这样才能够对该装摄像头的地方全部完成判断!

这道题比较难,主要是要对每个节点进行状态的分类加粗样式,同时逻辑推导两个孩子的状态应该对应的父亲的状态

下面贴出代码:

CPP版本

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
private:
    int result;
    // 一共有三种状态
    // 0代表无覆盖,1代表有摄像头,2代表有覆盖
    int traversal(TreeNode* cur)
    {
        if (!cur) {return 2;}
        int left = traversal(cur->left);
        int right = traversal(cur->right);

        if (left == 2 && right == 2) {return 0;}
        if (!left || !right)
        {
            result++;
            return 1;
        }
        if (left == 1 || right == 1) {return 2;}
        return -1;
    }
public:
    int minCameraCover(TreeNode* root) {
        result = 0;
        if (!traversal(root)) {result++;}
        return result;
    }
};

C版本

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */

int traversal(struct TreeNode* root, int* result)
{
    //后续遍历
    //0是无覆盖,1是有摄像头,2是有覆盖
    if(!root) {return 2;} //空节点说明是叶子,不要摄像头且应该有覆盖到
    int left = traversal(root->left, result);
    int right = traversal(root->right, result);
    //左右均覆盖,那此时这个节点肯定是无覆盖
    if (left == 2 && right == 2) {return 0;}
    //有一个无覆盖,另一个是有摄像头或有覆盖,这个节点应该有摄像头
    if (!left || !right)
    {
        (*result)++;
        return 1;
    }
    //左右节点有一个摄像头,那这个节点肯定有覆盖
    if (left == 1 || right == 1) {return 2;}
    return -1; //只是为了函数结构,实际不会进入这里
}

int minCameraCover(struct TreeNode* root){
    int result = 0;
    //还要考虑头结点,如果未覆盖需要+1
    if (!traversal(root, &result)) {result++;}
    return result;
}

总结

贪心法,比较找到解题套路,都是看着局部最优能推导出全局最优,且找不出反例,那就是贪心法解决问题了。

题目类型总结

贪心法可以解决比如之前说的发饼干、找零、取反的问题;同时也能解决序列的一些单调性相关问题

贪心法还能解决股票投资问题(之后动态规划还会再讲);还有经典的区间重叠的各种问题,都是先对一个维度排序之后进行贪心;

困难的比如有两个相互影响的维度的贪心,这时一定要选择一边先贪心,然后再去考虑另一个维度的贪心情况!

最后我白嫖了代码随想录总结的贪心法问题以及一些注意要点,如下图所示:

一些函数API

406题中:vector的插入比较慢,就可以用list(底层是链表实现)

list<vector<int>> que;
        for (int i = 0; i < people.size(); i++)
        {
            int pos = people[i][1];
            list<vector<int>>:: iterator it = que.begin();
            while (pos--) {it++;}
            que.insert(it, people[i]);
        }

同时,在C中的数组插入可以通过move然后赋值:

//需要插入则之后的位置直到他原先的所在前一个位置都要向后移位
move(people, peopleSize, pos, i);
people[pos] = tmp;

56题中,可以直接对vector的尾部元素进行提取进而比较是否需要重叠:

vector<vector<int>> result;
result.back()[1] >= intervals[i][0]

738题中,涉及字符串和整型数的相互转换:

string strNum = to_string(n);
stoi(strNum)

你可能感兴趣的:(leetcode刷题,笔记,贪心算法,算法)