Leetcode刷题总结(1):贪心问题

又到了需要准备面试的季节,决定来小刷一下Leetcode,在每个Tag里适当地挑几道,锻炼一下手感啥的。

455

题意

有一堆曲奇和一堆小孩,曲奇和小孩都有一个属性值,给小孩一个属性值>=自己的属性值的曲奇就会使小孩满意,一个小孩只能有一块曲奇。求满意的小孩的最大数目。

分析

似乎是很常见的贪心模型,但有一些变化,比如可以给多块曲奇。我觉得可以把最优化的目标从小孩的数目细化一下,比如“浪费”的曲奇属性值,取为每个小孩身上浪费的属性值之和。不过这么搞显然违背了原有的最优化要求,所以需要改一下。

妈的,看错题了,是最多一块曲奇。那就没啥好说的了,分别从最大的属性值开始遍历然后分配好了。

这是一个Java的解法。总之从最不贪婪的孩子开始分配,效果是一样的。

Arrays.sort(g);
Arrays.sort(s);
int i = 0;
for(int j=0;i

代码

class Solution {
public:
    int findContentChildren(vector& g, vector& s) {
        sort(g.begin(), g.end());
        sort(s.begin(), s.end());
        int j = g.size() - 1;
        int res = 0;
        for (int i = s.size() - 1; i >= 0; i--) {
            while (s[i] < g[j] && j >= 0) j--;
            if (j < 0) break;
            // allocate s[i] cookie for g[j] kid
            res++;
            j--;
        }
        return res;
    }
};

时间是40ms。

659

题意

有一个排序了的整数序列,需要把它分割成几个子序列,要求每个子序列为单调递增的的整数,且长度至少为3。问能否进行这样的分割。

分析

最简单的想法就是从头开始找最长的序列,找到一个就全部删掉,然后再找下一个。这样做的问题就是可能会出现如下情况:

[1, 2, 3, 3, 4, 5]

如果直接用以上解法,就会得到[1, 2, 3, 4, 5]和剩下的3。但是其实是可以分成[1, 2, 3][3, 4, 5]的。中间有2个重复的时候同理。3个重复的时候就可以得到一个新的子序列。

所以新想法是从重复最多的数字开始找。先满足它们的需求。但是似乎只能向一侧延伸,不然效果和刚才是一样的。

另一个想法是直接只找长度为3的序列。但是这样剩下的1和2长度的序列完全无法处理。

查看了一下题目的相关topic,发现除了greedy之外还有heap,所以可能从重复最多的数字开始找是可行的。可以尝试消耗左侧或者右侧最多的数字,仍然是找最长序列。然后用堆来进行维护。

敲了半天,感觉不靠谱,还是去看题解了。


这道题果然很有趣,有好多种解题思路。

模拟

http://www.cnblogs.com/pk28/p/7384602.html
这篇文章的思路很有代表性。作者开始的思路是这样的:

开始的想法是,第一遍扫一下,依次枚举3个连续的数作为一个分割,并记录每个分割最后的一个数(为后期拓展所用)。
第二次扫描,看剩下的数能不能放到任何一个分割的后面。如果能,那么更新分割的最后一个值。
第三次扫描,看还有没有剩下的数字,如果没有那么返回true,否则false

但这样的思路是存在问题的,作者给出了反例。于是需要调整一下思考的顺序:

其实我们思考的顺序有错误,对于当前的数字x,我们应该先判断x-1是否存在,如果存在就直接放上就好了,不存在的时候再构建一个长度为3个分割,如果长度为3的分割都构建不了,那么直接返回false就ok了。说道这里,这题目还是有贪心的味道的....

至于模拟的具体实现,这种实现是比较简洁的:http://www.cnblogs.com/grandyang/p/7525821.html。

线段

这个做法参考了https://leetcode.com/problems/split-array-into-consecutive-subsequences/solution/#approach-1-opening-and-closing-events-accepted。可以将本题看做是一系列长度大于等于3的线段叠在一起,数字的数量增加即一条线段的开始,减少即一条线段的结束。只需将开始事件和结束事件配对,要求其间隔>=3即可。

代码

模拟

注意没有给出数字的范围!所以需要用map!!

class Solution {
public:
    bool isPossible(vector& nums) {
        map cnt;
        map need;
        
        if (nums.size() < 3) return false;
        
        // count
        for (int i = 0; i < nums.size(); i++) {
            cnt[nums[i]]++;
        }
        
        for (int i = 0; i < nums.size(); i++) {
            if (cnt[nums[i]] <= 0)
                continue;
            
            // find extend
            if (need[nums[i]] > 0) {
                need[nums[i]]--;
                need[nums[i] + 1]++;
                cnt[nums[i]]--;
            }
            // find consecutive
            else if (cnt[nums[i]] > 0 && cnt[nums[i]+1] > 0 && cnt[nums[i]+2] > 0) {
                cnt[nums[i]]--;
                cnt[nums[i]+1]--;
                cnt[nums[i]+2]--;
                need[nums[i]+3]++;
            }
            else {
                return false;
            }
        }
        
        return true;
    }
};

时间为29.39%,可以说是相当慢了,因为使用了两个map的缘故吧。

线段

class Solution {
public:
    bool isPossible(vector& nums) {
        if (nums.size() < 3)
            return false;
        map cnt;
        queue q;
        for (int i = 0; i < nums.size(); i++) {
            cnt[nums[i]]++;
        }
        for (int i = nums[0]; i <= nums[nums.size()-1] + 1; i++) {
            int event = cnt[i] - cnt[i-1];
            // segment start
            if (event > 0) {
                for (int j = 0; j < event; j++)
                    q.push(i);  // starts at i
            }
            else if (event < 0) {
                for (int j = 0; j < -event; j++) {
                    if (q.empty())
                        return false;
                    int start = q.front();
                    q.pop();
                    if (i - start < 3)
                        return false;
                }
            }
        }
        
        if (!q.empty()) return false;
        
        return true;
    }
};

注意边界情况的处理。
时长为56.42%,没有什么优势。

45

题意

给你一个正整数数组,每个数代表了你从该位置起跳能够跳到的最大距离。假定你一定能够跳到终点,求最小跳数。

分析

第一种思路类似于DP。从第一点开始跳,同时更新能跳的范围内“跳到此处所需的最小跳数”。然后逐步更新。由于每次更新的范围最多为n,因此复杂度为O(n^2)。这是很容易想到的。也是很容易超时的。(果然超时了)

那么如何优化呢?

看了看题解发现这还真是道贪心题,囧。思路很简单,干脆就把它当成是一个BFS问题。第一跳能跳到的结点显然有一个范围,剩下的结点都是必须要跳两次及以上的。然而,跳两次能跳到的结点的范围显然也是连续的。也就是说可以把这个线性的数组按顺序分成若干层,每层都是一层BFS的结果。然后直接做就行了。

代码

暴力

class Solution {
public:
    int jump(vector& nums) {
        if (nums.size() <= 1)
            return 0;
        vector f(nums.size(), 2147483647);
        f[0] = 0;
        for (int i = 0; i < nums.size() -1; i++) {
            for (int j = 0; j <= nums[i] && i+j < nums.size(); j++) {
                if (f[i] + 1 < f[i+j])
                    f[i+j] = f[i] + 1;
            }
        }
        return f[nums.size() - 1];
    }
};

超时了。

BFS

class Solution {
public:
    int jump(vector& nums) {
        if (nums.size() <= 1)
            return 0;
        
        int start = 0, end = 0;
        int end2 = 0;
        int layer = 0;
        while (end < nums.size() - 1) {
            end2 = end + 1;
            for (int i = start; i <= end; i++)
                end2 = max(end2, i + nums[i]);
            start = end + 1;
            end = end2;
            layer++;
        }
        return layer;
    }
};

时间大概是13.97%,没有什么优势,不过我觉得已经写得比较简略了。

你可能感兴趣的:(Leetcode刷题总结(1):贪心问题)