C++算法 —— 贪心(4)

文章目录

  • 1、分发饼干
  • 2、最优除法
  • 3、跳跃游戏Ⅱ
  • 4、跳跃游戏Ⅰ
  • 5、加油站
  • 6、单调递增的数字
  • 7、坏了的计算器


1、分发饼干

455. 分发饼干

C++算法 —— 贪心(4)_第1张图片
其实看完这个题会发现,如果给定的两个数组不排序的话会非常难受,所以无论怎样,先排序。接下来需要比较两个数组的值,可以用双指针来指向。两个数组的两个元素比较时,和之前有相同的思路,如果满足条件,那么后面的元素都比这个元素大,肯定也满足,但为了满足更多次的条件,所以就选用最小的那个值;如果不满足条件,这里就跳过去,找下一个更大的元素去看看能否满足条件。这也就是贪心思想。

    int findContentChildren(vector<int>& g, vector<int>& s) {
        sort(g.begin(), g.end());
        sort(s.begin(), s.end());
        int res = 0, m = g.size(), n = s.size();
        for(int i = 0, j = 0; i < m && j < n; ++i, ++j)
        {
            while(j < n && s[j] < g[i]) ++j;
            if(j < n) res++;
        }
        return res;
    }

2、最优除法

553. 最优除法

C++算法 —— 贪心(4)_第2张图片
无论怎样,假设abcdefg7个数,a / b / c / d / e / f,整个式子就是一个分式,a一定在分子,b一定在分母。对于贪心来说,让分子变大,让分母变小,就是最优解。这道题来看,其实应当让分子变大就是它的最优解,所以接下来就要让分子变大。让分子变大的办法就是在把b ~ f都放到一个括号里,这样就变成了 a * c * d * e * f / b。

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

3、跳跃游戏Ⅱ

45. 跳跃游戏 II

C++算法 —— 贪心(4)_第3张图片
这道题意思就是如果是[2, 3, 1, 5, 4],那么在0下标位置时最多可以跳2步到1这个位置。

这道题可以用动规,以i位置为结尾,遍历一遍前面所有的元素,如果能从某个位置跳过来,那就选那个位置,而那个位置存储了到它的最小跳跃数,然后+1即可,但这样是n ^ 2的时间复杂度,思路并不行。

这道题的思路可以是一个类似层序遍历的过程。假设一个数组[2, 3, 1, 1, 4, 2, 6, 7, 1, 5, 8],从0下标开始,是2,我们能够确定跳到3或1这个点,也就是第一次选定起点后确定了下一次起跳的左端点和右端点;接着,3可以跳到1,1,4,而1这里,就加上贪心,因为3跳得远,且它一定比1要至少1,所以1能跳到的,3一定能跳到的,所以这里就只考虑大的数字,跳的区间为114,但是重叠了,重叠部分是2下标处的1,所以把这部分去掉,只看1和4,1就是左端点,4就是右端点;接着从4走,能到2671,这时候14和2671没有重叠的,所有不会划掉一部分,2就是左端点,1就是右端点。这样的过程就是选定了一个点后,就能确定下一次的左端点和右端点,所以很像层序遍历。

这个思路的时间复杂度是O(N)。

    int jump(vector<int>& nums) {
        int left = 0, right = 0, maxPos = 0, n = nums.size(), res = 0;
        while(left <= right)//防止跳不到n - 1位置
        {
            if(maxPos >= n - 1)//先判断是否已经能跳到最后一个位置
                return res;
            for(int i = left; i <= right; ++i)
            {
                maxPos = max(maxPos, nums[i] + i);
            }
            left = right + 1;
            right = maxPos;
            res++;
        }
        return -1;
    }

4、跳跃游戏Ⅰ

55. 跳跃游戏

C++算法 —— 贪心(4)_第4张图片
先看跳跃游戏Ⅱ。

其实就是改两处

    bool canJump(vector<int>& nums) {
        int left = 0, right = 0, maxPos = 0, n = nums.size();
        while(left <= right)//防止跳不到n - 1位置
        {
            if(maxPos >= n - 1)//先判断是否已经能跳到最后一个位置
                return true;
            for(int i = left; i <= right; ++i)
            {
                maxPos = max(maxPos, nums[i] + i);
            }
            left = right + 1;
            right = maxPos;
        }
        return false;
    }

5、加油站

134. 加油站

C++算法 —— 贪心(4)_第5张图片
C++算法 —— 贪心(4)_第6张图片
按照这个题的思路来看,两个数组要同时看,这时不如作为一个数组,因为真正需要的是差。gas和cost两个数组每每对应,用gas的减cost的,比如例1,就得到[-2, -2, -2, 3, 3]。最简单的办法就是暴力解法,依次枚举所有起点,模拟加油的流程。

实际上,这道题可以在暴力解法上改进而得到。差值的数组不需要创建出来,不过下面还是看差值数组。用i表示下标,然后用一个step变量,表示走多少步,比如走0步就还是原地,走1步就到下一个位置,不过要%上数组大小,这样就不会越界了。

    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        int n = gas.size();
        for(int i = 0; i < n; i++)
        {
            int rest = 0;
            for(int step = 0; step < n; step++)
            {
                int index = (i + step) % n;
                rest = rest + gas[index] - cost[index];
                if(rest < 0) break;
            }
            if(rest >= 0) return i;
        }
        return -1;
    }

不过这样肯定超出时间限制。现在基于这个来优化。假设差值数组是abcdefg,从a走到f就不能走了,说明a + b + c + d + e + f < 0,暴力解法就会从b再来一遍,但这样明显做了无用功。上面的式子小于0,那么去掉a,还是小于0,所以就不用管这些了,直接从g位置再出发。这样平均时间复杂度就是O(N)了。

    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        int n = gas.size();
        for(int i = 0; i < n; i++)
        {
            int rest = 0;
            int step = 0;
            for(; step < n; step++)
            {
                int index = (i + step) % n;
                rest = rest + gas[index] - cost[index];
                if(rest < 0) break;
            }
            if(rest >= 0) return i;
            i = i + step;
        }
        return -1;
    }

6、单调递增的数字

738. 单调递增的数字

C++算法 —— 贪心(4)_第7张图片
当然最简单的方法就是暴力枚举,从这个数字到0,判断每一个数字是否是单调递增,找到的第一个就是结果。不过重点在于如何判断单调递增。

对于一个数字,如果想对每一位做一些判断,转换成字符串就好。另一个经典操作就是模10再除10,就能从个位开始拿到每一位。

这个暴力解法的时间复杂度是O(nlogn),取一个数字的每一位的时间复杂度是logn。

但这里不用暴力解法,要用贪心,不过这更像找规律。

从头开始判断,如果发现了不是递增,那要对字符串如何操作?比如123454367,到了5这个位置就不能继续了,但因为要找更小的数字,那么4367不能改为6367。我们可以修改5,把它减1,然后后面的数字全变成9,就是要求的数字。但这里还有问题,如果是连续的几个5之后有个4呢?这样就得把第一个5改成4,后面全变成9。

    int monotoneIncreasingDigits(int n) {
        string s = to_string(n);
        int i = 0, m = s.size();
        while(i + 1 < m && s[i] <= s[i + 1]) ++i;
        if(i + 1 == m) return n;
        while(i - 1 >= 0 && s[i] == s[i - 1]) --i;
        s[i]--;
        for(int j = i + 1; j < m; ++j) s[j] = '9';
        return stoi(s);
    }

7、坏了的计算器

991. 坏了的计算器

C++算法 —— 贪心(4)_第8张图片
对于小于目标的数,那么乘2会更快地接近目标,但是也有不是最优解的,比如6和目标10。

这道题适合逆着思考,把操作变成除2和+1。
这个题没有小数,所以能除2的只能是偶数,那么遇到奇数的话就只能+1。偶数可以除2,可以+1。把目标值变成原始值,原始值则变成目标值。假设原有的目标值是end,原始值是begin。

针对偶数,如果end <= begin,也就是目标值更小,那么遇到偶数就得+1。如果end > begin,奇数还是只能+1,而偶数,经过证明,其实是先除更优。

    int brokenCalc(int startValue, int target) {
        //正难则反 + 贪心
        int res = 0;
        while(target > startValue)
        {
            if(target % 2 == 0) target /= 2;
            else target += 1;
            res++;
        }
        return res + startValue - target;//目标值变成小于原始值了,奇偶数都是+1,所以就是加上差值。
    }

结束。

你可能感兴趣的:(C++算法,c++,算法,开发语言,贪心算法,学习)