Offer必备算法34_贪心算法二_七道力扣题详解(由易到难)

目录

①力扣121. 买卖股票的最佳时机

解析代码

②力扣122. 买卖股票的最佳时机 II

解析代码1_拆分交易

解析代码2_双指针

③力扣1005. K 次取反后最大化的数组和

解析代码

④力扣2418. 按身高排序

解析代码

⑤力扣870. 优势洗牌(田忌赛马)

解析代码

⑥力扣409. 最长回文串

解析代码

⑦力扣942. 增减字符串匹配

解析代码

本篇完。


①力扣121. 买卖股票的最佳时机

121. 买卖股票的最佳时机

难度 简单

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。

示例 1:

输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
     注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。

示例 2:

输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 没有交易完成, 所以最大利润为 0。

提示:

  • 1 <= prices.length <= 10^5
  • 0 <= prices[i] <= 10^4
class Solution {
public:
    int maxProfit(vector<int>& prices) {

    }
};

解析代码

        可以两层for循环暴力枚举,会超时。

        贪心策略:由于只能交易一次,所以对于某一个位置 i ,要想获得最大利润,仅需知道前面所有元素的最小值。然后在最小值的位置买入股票,在当前位置卖出股票即可。

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int Min = INT_MAX, ret = 0;
        for(int i = 0; i < prices.size() - 1; ++i)
        {
            Min = min(Min, prices[i]);
            ret = max(ret, prices[i + 1] - Min);
        }
        return ret;
    }
};


②力扣122. 买卖股票的最佳时机 II

122. 买卖股票的最佳时机 II

难度 中等

给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。

在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。

返回 你能获得的 最大 利润 。

示例 1:

输入:prices = [7,1,5,3,6,4]
输出:7
解释:在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。
     随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6 - 3 = 3 。
     总利润为 4 + 3 = 7 。

示例 2:

输入:prices = [1,2,3,4,5]
输出:4
解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。
     总利润为 4 。

示例 3:

输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 交易无法获得正利润,所以不参与交易可以获得最大利润,最大利润为 0 。

提示:

  • 1 <= prices.length <= 3 * 10^4
  • 0 <= prices[i] <= 10^4
class Solution {
public:
    int maxProfit(vector<int>& prices) {

    }
};

解析代码1_拆分交易

        贪心策略: 由于可以进行无限次交易,所以只要是一个上升区域,我们就把利润拿到手就好了(可以拆分一天一天交易,也可以用双指针得到这个上升区域的起点和终点)。

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


解析代码2_双指针

虽然两个循环,但时间也是O(N)的。

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int ret = 0, left = 0, sz = prices.size();
        while(left < sz)
        {
            int right = left + 1;
            while(right < sz && prices[right] > prices[right - 1])
            {
                ++right;
            }
            ret += prices[right - 1] - prices[left]; // right已经是极大值下一个了
            left = right;
        }
        return ret;
    }
};


③力扣1005. K 次取反后最大化的数组和

1005. K 次取反后最大化的数组和

难度 简单

给你一个整数数组 nums 和一个整数 k ,按以下方法修改该数组:

  • 选择某个下标 i 并将 nums[i] 替换为 -nums[i] 。

重复这个过程恰好 k 次。可以多次选择同一个下标 i 。

以这种方式修改数组后,返回数组 可能的最大和 。

示例 1:

输入:nums = [4,2,3], k = 1
输出:5
解释:选择下标 1 ,nums 变为 [4,-2,3] 。

示例 2:

输入:nums = [3,-1,0,2], k = 3
输出:6
解释:选择下标 (1, 2, 2) ,nums 变为 [3,1,0,2] 。

示例 3:

输入:nums = [2,-3,-1,5,-4], k = 2
输出:13
解释:选择下标 (1, 4) ,nums 变为 [2,3,-1,5,4] 。

提示:

  • 1 <= nums.length <= 10^4
  • -100 <= nums[i] <= 100
  • 1 <= k <= 10^4
class Solution {
public:
    int largestSumAfterKNegations(vector<int>& nums, int k) {

    }
};

解析代码

贪心策略:可以先排序,然后分情况讨论,设整个数组中负数的个数为 m 个:

  • m > k :把前 k 小负数,全部变成正数;
  • m == k :把所有的负数全部转化成正数;
  • m < k :
  1. 先把所有的负数变成正数。
  2. 然后根据 k - m 的奇偶分情况讨论:如果是偶数,直接忽略,如果是奇数,挑选当前数组中最小的数,变成负
class Solution {
public:
    int largestSumAfterKNegations(vector<int>& nums, int k) {
        int sz = nums.size(), Min = INT_MAX, sum = 0, m = 0; // m统计0和负数
        sort(nums.begin(), nums.end());
        for(int i = 0; i < sz; ++i)
        {
            sum += nums[i];
            Min = min(Min, abs(nums[i])); // 绝对值最小的数
            if(nums[i] <= 0)
                ++m;
        }
        if(m >= k)
        {
            for(int i = 0; i < k; ++i)
                sum -= 2 * nums[i]; // 最小的负数变正数,两倍
        }
        else // m < k
        {
            for(int i = 0; i < m; ++i) // 先把所有的负数变成正数
                sum -= 2 * nums[i];
            if((k - m) % 2 == 1) // 绝对值最小的数取反
                sum += 2 * -Min;
        }
        return sum;
    }
};


④力扣2418. 按身高排序

2418. 按身高排序

难度 简单

给你一个字符串数组 names ,和一个由 互不相同 的正整数组成的数组 heights 。两个数组的长度均为 n 。

对于每个下标 inames[i] 和 heights[i] 表示第 i 个人的名字和身高。

请按身高 降序 顺序返回对应的名字数组 names 。

示例 1:

输入:names = ["Mary","John","Emma"], heights = [180,165,170]
输出:["Mary","Emma","John"]
解释:Mary 最高,接着是 Emma 和 John 。

示例 2:

输入:names = ["Alice","Bob","Bob"], heights = [155,185,150]
输出:["Bob","Alice","Bob"]
解释:第一个 Bob 最高,然后是 Alice 和第二个 Bob 。

提示:

  • n == names.length == heights.length
  • 1 <= n <= 10^3
  • 1 <= names[i].length <= 20
  • 1 <= heights[i] <= 10^5
  • names[i] 由大小写英文字母组成
  • heights 中的所有值互不相同
class Solution {
public:
    vector<string> sortPeople(vector<string>& names, vector<int>& heights) {
        
    }
};

解析代码

此题并不是贪心的题,只是一个简单的排序,为下一道贪心的题做准备。

可以直接创建一个二元组vector<pair<int, string>>,或者用哈希的解法。下面看另一种解法:

        对下标排序解法(通过排序 ''索引'' 的方式):我们不能直接按照 i 位置对应的 heights 来排序,因为排序过程是会移动元素的,但是names 内的元素是不会移动的。

        由题意可知, names数组和 heights数组的下标是壹壹对应的,因此可以重新创建出来一个下标数组,将这个下标数组按照 heights[i] 的大小排序。那么,当下标数组排完序之后,里面的顺序就相当于heights这个数组排完序之后的下标。之后通过排序后的下标,依次找到原来的name ,完成对名字的排序。

class Solution {
public:
    vector<string> sortPeople(vector<string>& names, vector<int>& heights) {
        int sz = heights.size();
        vector<int> index(sz);
        for(int i = 0; i < sz; ++i)
        {
            index[i] = i;
        }
        sort(index.begin(), index.end(),[&](int index1, int index2)
        {
            return heights[index1] > heights[index2];
        });

        vector<string> ret;
        for(auto& e : index)
        {
            ret.push_back(names[e]);
        }
        return ret;
    }
};


⑤力扣870. 优势洗牌(田忌赛马)

870. 优势洗牌

难度 中等

给定两个长度相等的数组 nums1 和 nums2nums1 相对于 nums2 的优势可以用满足 nums1[i] > nums2[i] 的索引 i 的数目来描述。

返回 nums1 的任意排列,使其相对于 nums2 的优势最大化。

示例 1:

输入:nums1 = [2,7,11,15], nums2 = [1,10,4,11]
输出:[2,11,7,15]

示例 2:

输入:nums1 = [12,24,8,32], nums2 = [13,25,32,11]
输出:[24,32,8,12]

提示:

  • 1 <= nums1.length <= 10^5
  • nums2.length == nums1.length
  • 0 <= nums1[i], nums2[i] <= 10^9
class Solution {
public:
    vector<int> advantageCount(vector<int>& nums1, vector<int>& nums2) {

    }
};

解析代码

这里讲一下田忌赛马背后的博弈决策,从三匹马拓展到 n 匹马之间博弈的最优策略。

  • 田忌:下等马 中等马 上等马
  • 齐王:下等马 中等马 上等马
  1. 田忌的下等马 pk 不过齐王的下等马,因此把这匹马丢去消耗一个齐王的最强的马。
  2. 接下来选择中等马 pk 齐王的下等马,勉强获胜。
  3. 最后用上等马 pk 齐王的中等马,勉强获胜。

由此可以得出一个最优的决策方式:

  • 当我方此时最差的比不过对面最差的时候,让我方最差的去处理掉对面最好的(反正要输,不如去拖掉对面一个最强的)。
  • 当我方此时最差的能比得上对面最差的时候,就让两者比对下去(最差的都能获胜,就不需要更强的比了)。
class Solution {
public:
    vector<int> advantageCount(vector<int>& nums1, vector<int>& nums2) {
        int sz = nums1.size();
        sort(nums1.begin(), nums1.end());
        vector<int> index(sz), ret(sz); // index和nums2下标绑定
        for(int i = 0; i < sz; ++i)
        {
            index[i] = i;
        }
        sort(index.begin(), index.end(),[&](int index1, int index2)
        {
            return nums2[index1] < nums2[index2];
        });

        int left = 0, right = sz - 1;
        for(auto& e : nums1)
        {
            if(e <= nums2[index[left]]) // 最弱的和最弱的判断
                ret[index[right--]] = e; // 比不过就去和最强的比,相等也比不过
            else // 比过了,放到最弱的位置
                ret[index[left++]] = e;
        }
        return ret;
    }
};


⑥力扣409. 最长回文串

409. 最长回文串

难度 简单

给定一个包含大写字母和小写字母的字符串 s ,返回 通过这些字母构造成的 最长的回文串 。

在构造过程中,请注意 区分大小写 。比如 "Aa" 不能当做一个回文字符串。

示例 1:

输入:s = "abccccdd"
输出:7
解释:
我们可以构造的最长的回文串是"dccaccd", 它的长度是 7。

示例 2:

输入:s = "a"
输出:1

示例 3:

输入:s = "aaaaaccc"
输出:7

提示:

  • 1 <= s.length <= 2000
  • s 只由小写 和/或 大写英文字母组成
class Solution {
public:
    int longestPalindrome(string s) {

    }
};

解析代码

贪心策略:用尽可能多的字符去构造回文串:

  • 如果字符出现偶数个,那么全部都可以用来构造回文串。
  • 如果字符出现奇数个,减去一个之后,剩下的字符能够全部用来构造回文串。
  • 最后再判断⼀下,如果有字符出现奇数个,就把它单独拿出来放在中间。
class Solution {
public:
    int longestPalindrome(string s) {
        int ret = 0;
        int hash[123] = { 0 }; //⽤数组模拟哈希表计数,ASCII码最大127,z是122
        for(auto& e : s)
        {
            hash[e]++;
        }
        for(auto& e : hash)
        {
            ret += (e / 2 * 2); // 奇数就去掉1再乘2
        }
        return ret == s.size() ? ret : ret + 1; // 不足原长度就有奇数
    }
};


⑦力扣942. 增减字符串匹配

942. 增减字符串匹配

难度 简单

由范围 [0,n] 内所有整数组成的 n + 1 个整数的排列序列可以表示为长度为 n 的字符串 s ,其中:

  • 如果 perm[i] < perm[i + 1] ,那么 s[i] == 'I' 
  • 如果 perm[i] > perm[i + 1] ,那么 s[i] == 'D' 

给定一个字符串 s ,重构排列 perm 并返回它。如果有多个有效排列perm,则返回其中 任何一个 。

示例 1:

输入:s = "IDID"
输出:[0,4,1,3,2]

示例 2:

输入:s = "III"
输出:[0,1,2,3]

示例 3:

输入:s = "DDI"
输出:[3,2,0,1]

提示:

  • 1 <= s.length <= 10^5
  • s 只包含字符 "I" 或 "D"
class Solution {
public:
    vector<int> diStringMatch(string s) {

    }
};

解析代码

贪心策略:

  • 当遇到 'I' 的时候,为了让下一个上升的数可选择的范围更多,当前选择最小的那个数。
  • 当遇到 'D' 的时候,为了让下一个下降的数可选择的范围更多,选择当前最大的那个数。
class Solution {
public:
    vector<int> diStringMatch(string s) {
        int left = 0, right = s.size();
        vector<int> ret;
        for(auto& e : s)
        {
            if(e == 'I') // 递增,先放最小
                ret.push_back(left++);
            else
                ret.push_back(right--);
        }
        ret.push_back(right); // 重合
        return ret;
    }
};


本篇完。

下一篇是DFS解决FloodFill。

下下篇是贪心算法的第三部分。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GR鲸鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值