Bootstrap

贪心算法(2)

目录

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

题解:

代码:

按身高排序(田忌赛马的预备)

题解:

代码:

方法一:

方法二: 

优势洗牌(田忌赛马)

题解:

代码:

最长回文串

题解:

代码:

增减字符串匹配

题解:

代码: 

分发饼干(田忌赛马)

题解:

代码:

最优除法

题解:

代码:


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

1005. K 次取反后最大化的数组和 - 力扣(LeetCode)icon-default.png?t=O83Ahttps://leetcode.cn/problems/maximize-sum-of-array-after-k-negations/description/

题解:

为了最大化数组和,就要对数组中最小的 k 个数取反,我们统计出数组中负数的个数 m

如果 m >= k,那么我们只需要对这 k 个负数取反,就可以得到最大的数组和;

如果 m < k,我们结合具体例子讨论,如下图所示,m = 4,

将所有的负数取反后,操作数剩下 k - m 次, 为了得到最大的数组和,我们只需要考虑如何操作取反后数组的最小值:

  • 如果  k - m 为偶数,那么我们无需操作取反后数组的最小值,因为负负得正,数组和不变;
  • 如果  k - m 为奇数,那么取反后数组的最小值取负值即可。

代码:

class Solution {
public:
    int largestSumAfterKNegations(vector<int>& nums, int k) {
        int minNum=INT_MAX,ret=0,n=nums.size(),m=0;
        for(auto x:nums)
        {
            if(x<0) m++;
            minNum=min(minNum,abs(x));//找出绝对值最小的数
        }
        if(m>k)//负数的个数大于k,反转前k小负数
        {
            sort(nums.begin(),nums.end());
            for(int i=0;i<k;i++)
                ret+= -nums[i];
            for(int i=k;i<n;i++)
                ret+=nums[i];
        }
        else//负数的个数小于等于k,有的数字被多次反转
        {
            for(auto x:nums)
                ret+=abs(x);//反转所有负数

            if((k-m)%2)//有的数被反转奇数次
            {
                ret-=minNum*2;
            }
        }

        return ret;
    }
};

按身高排序(田忌赛马的预备)

2418. 按身高排序 - 力扣(LeetCode)icon-default.png?t=O83Ahttps://leetcode.cn/problems/sort-the-people/description/

题解:

这道题有两种方法:

方法一

用 pair 或者 struct 创建一个二元组,二元组的内容为姓名和对应的身高,将所有的二元组放入数组中,再对数组根据身高排序,排序后按照顺序把名字提取出来即可。

或者用哈希表,定义 map< int, string > hash,把身高和姓名放进哈希表中,再根据身高排序即可。

方法二

因为姓名、身高和下标是一一对应的关系,如果只是对身高排序,身高和下标原本的对应关系就被打乱了,就无法根据身高的下标找到对应的姓名了。

如图所示,原本 165 、John 对应 1 号下标,只对身高排序后,下标为 1 的身高变为 170,John 再次根据下标 1 去找自己的身高时,身高变成 170 了。 

为了保留身高、姓名和下标的对应关系, 我们可以重开一个数组 tmp,tmp 存的是身高数组的下标,新建一个排序规则根据身高对下标排序,但不改变身高数组

如下图,由于下标为 1 的身高165 小于 下标为 2 的身高 170,所以交换 tmp 中元素 1 和 2 的位置,tmp 排序后的最终顺序为 [ 0, 2, 1 ],然后遍历 tmp 数组,height[ tmp[0] ] 就是 Mary,height[ tmp[1] ] 就是 Emma,height[ tmp[2] ] 就是 John。

代码:

方法一:

class Solution {
public:
    struct person
    {
        string name;
        int height;
    }p[1010];
    vector<string> sortPeople(vector<string>& names, vector<int>& heights) 
    {
        int n=names.size();
        for(int i=0;i<n;i++)
        {
            p[i].name=names[i];
            p[i].height=heights[i];
        }
        sort(p,p+n,[](const struct person a,const struct person b)
        {   return a.height>b.height;   });
        for(int i=0;i<n;i++)
        {
            names[i]=p[i].name;
        }
        return names;
    }
};

方法二: 

class Solution {
public:
    vector<string> sortPeople(vector<string>& names, vector<int>& heights) {
        int n=names.size();
        vector<int> tmp(n);
        for(int i=0;i<n;i++)
        {
            tmp[i]=i;//初始化
        }
        sort(tmp.begin(),tmp.end(),[&](int a,int b)
        {   return heights[a]>heights[b]; });

        vector<string> ret;
        for(int i=0;i<n;i++)
        {
            ret.push_back(names[tmp[i]]);
        }
        return ret;
    }
};

优势洗牌(田忌赛马)

870. 优势洗牌 - 力扣(LeetCode)icon-default.png?t=O83Ahttps://leetcode.cn/problems/advantage-shuffle/description/

题解:

我们对 nums1 和 nums2 排升序,以示例二为例,排序后 nums1 = [ 8, 12, 24, 32 ] ,nums2 = [ 11, 13, 25, 32 ],我们 用 i 遍历 nums1 ,用 left 指向 nums2 的较小数,right 指向 nums2 的较大数:

  • 如果 nums1[ i ] > nums2[ left ] ,说明 nums1[ i ] 有优势,i++,left++,去匹配下一对优势数;
  • 如果 nums1[ i ] <= nums2[ left ],说明该数没有优势没有优势的数应该去拖累 nums2 的较大数,即 nums1[ i ] 和 nums2[ right ] 匹配,然后 i++,right -- . 因为 nums2[ left ]  后面的数更大,nums1[ i ] 更没有优势了,继续匹配还不如去拖累较大数。

为了不打乱nums2 的下标和数字的相对位置,在排序 nums2 时,用了 按身高排序的 排序方法,即只排序下标。

代码:

class Solution {
public:
    vector<int> advantageCount(vector<int>& nums1, vector<int>& nums2) {
        int n=nums1.size();
        vector<int> index(n);

        for(int i=0;i<n;i++)
            index[i]=i;

        sort(nums1.begin(),nums1.end());//排升序
        sort(index.begin(),index.end(),[&](const int a,const int b)
        {
            return nums2[a]<nums2[b];
        });
        vector<int> ret(n);
        int left=0,right=n-1;
        for(auto x:nums1)
        {
            if(x>nums2[index[left]])
                ret[index[left++]]=x;
            else
                ret[index[right--]]=x;
        }

        return ret;
    }
};

最长回文串

409. 最长回文串 - 力扣(LeetCode)icon-default.png?t=O83Ahttps://leetcode.cn/problems/longest-palindrome/description/

题解:

为了得到回文串,就需要让字符串对称轴左右两侧的字母一一对应。

从下图可以看出,

  • 如果回文串的长度为偶数,则每个字母的个数为偶数
  • 如果回文串的长度为奇数,则除了对称轴上的字母的个数为奇数之外,其余字母的个数为偶数。

统计提供的字符串 s 的每个字母出现的次数,

  • 如果某字母出现的次数为偶数,说明这个字母可以直接加入回文串
  • 如果某字母出现的次数为奇数,为了得到最长的回文串, 只放入偶数个该字母,且这个偶数要尽可能大。

上述过程得到的回文串的长度为偶数,我们可以考虑一下对称轴上是否可以放一个字母,这里有一个小巧思,

  • 如果得到的回文串的长度 ==  s 的长度,说明 s 中每个字母出现的次数都是偶数,已经没有多余的字母可以让我们放在对称轴上了;
  • 如果得到的回文串的长度 <  s 的长度,说明 s 中至少存在一个字母出现的次数为奇数,我们只需要让个数为奇数个的这个字母放在对称轴上,放一个就可以了,最后让回文串的长度 +1。

代码:

class Solution {
public:
    int longestPalindrome(string s) {
        int ret=0;
        vector<int> count(256);
        for(auto x:s)
            count[x]++;
        for(auto x:count)
        {
            ret+=(x/2*2);
        }
        return ret<s.size()?ret+1:ret;
    }
};

增减字符串匹配

942. 增减字符串匹配 - 力扣(LeetCode)icon-default.png?t=O83Ahttps://leetcode.cn/problems/di-string-match/description/

题解:

定义两个指针 left 和 right,left 初始化为 0,right 初始化为 1,用 i 遍历字符串 s,将结果存在数组 ret 中:

  • 如果 s[ i ] == ' I ',说明 ret[ i+1 ] > ret[ i ] ,即上升趋势,如果 ret[ i ] 的数字选得很大,上升的趋势很快,若后面出现连续的上升趋势,剩下的数字可能没办法满足上升的需求了,所以需要选择比较小的数字,减缓上升的趋势,而 left 指向的就是比较小的数,故 ret[ i ] = left,left++,指向下一个数;
  • 如果 s[ i ] == ' D ',说明 ret[ i+1 ] < ret[ i ] ,即下降趋势,如果 ret[ i ] 的数字选得很小,下降的趋势很快,若后面出现连续的下降趋势,剩下的数字可能没办法满足下降的需求了,所以需要选择比较大的数字,减缓下降的趋势,而 right 指向的就是比较小的数,故 ret[ i ] = right,right --,指向下一个数;
  • 因为 s 的长度为 n,而我们需要排 n+1 个数,最后 left 和 right 一定会相遇,指向同一个数,把这个数放在数组最后即可。

代码: 

class Solution {
public:
    vector<int> diStringMatch(string s) {
        int n=s.size();
        vector<int> ret;
        int left=0,right=n;
        for(int i=0;i<n;i++)
        {
            if(s[i]=='I')   
            {
                ret.push_back(left);    left++;
            }
            else
            {
                ret.push_back(right);   right--;
            }
        }
        ret.push_back(left); 
        return ret;
    }
};

分发饼干(田忌赛马)

455. 分发饼干 - 力扣(LeetCode)icon-default.png?t=O83Ahttps://leetcode.cn/problems/assign-cookies/description/

题解:

和田忌赛马的思路大同小异,对胃口 g 和饼干 s 排序,用 i 遍历 g,用 j 遍历 s,

  • 如果 g[ i ] > s[ j ] ,说明当前饼干不符合该小孩的胃口,后面的小孩的胃口就更不符合了,直接舍弃该饼干,即 j++;
  • 如果 g[ i ] <= s[ j ],说明当前饼干符合该小孩的胃口,该饼干直接给该小孩即可,即 i++,j++.

代码:

class Solution {
public:
    int findContentChildren(vector<int>& g, vector<int>& s) {
        int ret=0;  int n1=g.size(),n2=s.size();
        sort(g.begin(),g.end());//排升序

        sort(s.begin(),s.end());//排升序

        int i=0,j=0;
        while(i<n1 && j<n2)
        {
            if(g[i]>s[j])   j++;
            else
            {
                i++;    j++;    ret++;
            }
        }
        return ret;
    }
};

最优除法

553. 最优除法 - 力扣(LeetCode)icon-default.png?t=O83Ahttps://leetcode.cn/problems/optimal-division/description/

题解:

加括号就是为了尽可能地把除数转为被除数,把放在分母的数放到分子上。

代码:

class Solution {
public:
    string optimalDivision(vector<int>& nums) {
        string ret;
        ret+=to_string(nums[0]);
        
        int n=nums.size();
        if(n==2)
            ret+="/"+to_string(nums[1]);
        else if(n>2)
        {
            ret+="/("+to_string(nums[1]);
            for(int i=2;i<n;i++)
            {
                ret+="/"+to_string(nums[i]);
            }
            ret+=")";
        }
        return ret;
    }
};
;