LintCode贪心法题总结

贪心法的题目主要就考你会不会做,知道就知道,不知道那就很难知道。没啥套路,所以需要把这些题目过一遍。

82. Single Number

一个数组中,每个数字都出现了2次,只有一个数字出现了1次,要求找到那个数。可以用HashSet来记录,但是这样会消耗额外空间,以及HashSet的查找也额外花时间。可以利用异或操作^,a^a=0, a^0=a,根据交换律a^b=b^a,比如3^4^3=4,根据异或操作的这种性质,我们可以将所有元素依次做异或操作,相同元素异或为0,最终剩下的元素就为Single Number。时间复杂度O(n),空间复杂度O(1)。

    public int singleNumber(int[] A) {
        int res = 0;
        for (int i = 0; i < A.length; i++) {
            res ^= A[i];
        }
        return res;
    }


83. Single Number II

题目说,除了一个数字以外,其他的都出现了3次,要求找到那个只出现1次的数字。

直观的方法是用HashMap来统计出现次数,然后输出出现次数为1的那个,但是这种方法消耗了额外的空间。有没有办法可以in place来做呢?那就是位运算了。

题目已经说了,除了一个数字以外,其他的都出现了3次。如果我们把那个特殊的数剔除,对于剩下的数在0到31位的每一位的和sum的话,每一位上1出现的次数必然都是3的倍数。所以,解法就在这里,将每一位数字分解到32个bit上,统计每一个bit上1出现的次数。最后对于每一个bit上1出现的个数对3取模,取模的结果就是要找到的那个数字了。

    public int singleNumberII(int[] A) {
        int[] bit = new int[32];
        int res = 0;
        for (int i = 0; i < 32; i++) {
            for (int j = 0; j < A.length; j++) {
                if (((A[j] >> i) & 1) == 1) {
                    bit[i]++;
                }
            }
            res += ((bit[i] % 3) << i);
        }
        return res;
    }

84. Single Number III

这道题是上上一道single number的变种题。除了数字b和数字c,其余的每个数字都在数组A中出现2次。

而上上一道single number是只有一个数字没有出现过2次,所以那道题可以直接从头到尾逐个取异或,来得到答案。但是这道题怎么用single number的解法来做呢?

这题有两个未知数b和c,直接做异或肯定是不行的,那么如何通过一些变换把这道题分解开,使得可以应用Single Number的解法来做,才是这个题目有意思的地方。

如果这道题先直接套用single number的方法,拿得到的结果是b和c的异或:即b^c。那如何把b和c从b^c中提取出来呢?

我们从微观的角度来看,把b和c从32位Integer的角度来看。因为b和c肯定是不同的,所以b^c肯定有一位是1,如下所示:

LintCode贪心法题总结_第1张图片

那么当我们找到这个K之后,就可以按照第K位bit是否等于1,将原数组A划分成两个子数组,而这两个子数组分别包含了b和c,那么剩下的就只需要把single number的算法直接应用到这两个子数组上,就可以得到b和c了。

    public List singleNumberIII(int[] A) {
        int res = 0, a = 0, b = 0;
        List result = new ArrayList();
        
        for (int i = 0; i < A.length; i++) {
            res ^= A[i];
        }
        
        int flag = 1;
        while ((flag & res) == 0) {
            flag <<= 1;
        }
        
        for (int i = 0; i < A.length; i++) {
            if ((flag & A[i]) == 0) {
                a ^= A[i];
            } else {
                b ^= A[i];
            }
        }
        result.add(a);
        result.add(b);
        
        return result;
    }


46. Majority Number

在一个数组中,有一个数出现的次数超过了数组长度的一半,要求找到这个数。一开始肯定还是想到用HashMap来做,统计每个数出现的次数,但是题目要求不用到额外的空间。只能用贪心法来做。

一个简单的方法就是对数组排序,然后下标为n/2的元素就是要返回的值,因为注意到题目说肯定有一个元素出现的次数超过一半,那么排序后那个元素肯定会出现在正中间(可证明),时间复杂度就是排序复杂度O(nlogn)

当然还有更好的线性算法:每次从数组中找出一对不同的元素,将它们从数组中删除,直到遍历完整个数组。由于这道题已经说明一定存在一个出现次数超过一半的元素,所以遍历完数组后数组中一定会存在至少一个元素。Moore voting algorithm--每找出两个不同的element,就成对删除即count--,最终剩下的一定就是所求的。时间复杂度:O(n)

    public int majorityNumber(ArrayList nums) {
        int count = 0;
        int candidate = 0;
        for (int i = 0; i < nums.size(); i++) {
            if (count == 0) {
                count = 1;
                candidate = nums.get(i);
            } else {
                if (candidate == nums.get(i)) {
                    count++;
                } else {
                    count--;
                }
            }
        }
        return candidate;
    }
欲知摩尔投票算法的详情及动画演示,可以参考UT Austin大学的这个: http://www.cs.utexas.edu/~moore/best-ideas/mjrty/index.html


47. Majority Number II

跟上道题类似,不过是找出数组中出现次数超过三分之一的数。通过枚举,我们可以发现数组中超过三分之一的数最多有2个,我们可以运用摩尔投票法则找到这2个数,然后在比较他们谁出现的次数更多,就返回那个较多的数,可参考博客:http://www.cnblogs.com/grandyang/p/4606822.html 以及 http://www.cnblogs.com/yuzhangcmu/p/4175779.html

    public int majorityNumber(ArrayList nums) {
        int candidate1 = 0, candidate2 = 0, count1 = 0, count2 = 0;
        // 找到2个候选者
        for (int num : nums) {
            if (num == candidate1) {
                count1++;
            } else if (num == candidate2) {
                count2++;
            } else if (count1 == 0) {
                candidate1 = num;
                count1++;
            } else if (count2 == 0) {
                candidate2 = num;
                count2++;
            } else {
                count1--;
                count2--;
            }
        }
        
        // 对这2个候选者投票,找到更大的那个数
        count1 = count2 = 0;
        for (int num : nums) {
            if (num == candidate1) {
                count1++;
            } else if (num == candidate2) {
                count2++;
            }
        }
        // 返回两者之间的众数
        return count1 > count2 ? candidate1 : candidate2;
    }

182. Delete Digits

在一个数组中,删掉k个数,使得剩余的数组成的整数最小。

首先,还是以一个简单的输入为例,如输入字符串A="859", k=1,因此我们在这里要删除一个字符。很明显这里删除字符8就可以得到结果。如输入字符串A="859", k=2,则再删除9即可。 

然后,我们推导思路:因为我们要求最小的数,所以需要让排在最前的数字较小就可以,要做到这一点,我们依次比较两个相邻的数字,如果前一个数字比后一个数字大,则将前一个数字删除;如果前一个数字比后一个数字小,则不变,继续比较后面的数字。当进行到最后一个数字时,不需要再比较,直接将其删除即可。在上面的例子中,首先比较8和5,因为8比5大,所以删去8即可。如果k=2,那么继续比较5和9,因为5比9小,所以不用删除;下面到了最后一个字符,不用再比较,直接将其删除即可。 

最后,按照上面的思路,我们可以写出代码。 这里有一点需要注意就是,当处理之后的字符串中前面的数有可能是0,此时我们应该跳过前面的0,从第一个非0的字符处返回。

    public String DeleteDigits(String A, int k) {
        StringBuffer sb = new StringBuffer(A);
        for (int i = 0; i < k; i++) {
            for (int j = 0; j < sb.length(); j++) {
                if (j == sb.length() - 1 || j + 1 < sb.length() && sb.charAt(j) > sb.charAt(j + 1)) {
                    sb.deleteCharAt(j);
                    break;
                }
            }
        }
        int index = 0;
        while (sb.charAt(index) == '0') {
            index++;
        }
        return sb.toString().substring(index);
    }


412. Candy

n个小孩站在同一排,给他们分糖果。高个的小孩要比他左手边和右手边的2个小孩拿到更多的糖果。问你最少可以花多少糖果可以满足这个规则?

贪心法问题,新建一个数组,一开始给数组全部初始化为1(因为如果小孩全部一样高,那么每个小孩只要拿到1颗糖就行了)。

然后从左往右遍历,遇到更高的,更高的那个就比他左边那个小孩多拿1颗糖。

然后从右往左遍历,遇到更高的,如果更高的那个比他右边拿的糖要少,更高的那个就比他右边那个小孩多拿1颗糖。

这样就用最少的糖果达到目标了:

    public int candy(int[] ratings) {
        int[] res = new int[ratings.length];
        // 初始化
        for (int i = 0; i < res.length; i++) {
            res[i] = 1;
        }
        // 从左往右遍历
        for (int i = 0; i < res.length - 1; i++) {
            if (ratings[i + 1] > ratings[i]) {
                res[i + 1] = res[i] + 1;
            }
        }
        // 从右往左遍历
        for (int i = res.length - 1; i > 0; i--) {
            if (ratings[i - 1] > ratings[i] && res[i - 1] <= res[i]) {
                res[i - 1] = res[i] + 1;
            }
        }
        // 统计
        int result = 0;
        for (int i = 0; i < res.length; i++) {
            result += res[i];
        }
        return result;
    }

187. Gas Station

在一个环形公路上分布了一些加油站,每个加油站里面都有一定量的汽油。一辆车从一个加油站开到另一个加油站的所需要的油以数组形式给出。有一台没有油的卡车,把它初始放置到哪个加油站,才能使得他能走完整个环形公路?

我们首先要知道能走完整个环的前提是gas的总量要大于cost的总量,这样才会有起点的存在。

假设开始设置起点start = 0, 并从这里出发,如果当前的gas值大于cost值,就可以继续前进。

此时到下一个站点,剩余的gas加上当前的gas再减去cost,看是否大于0,若大于0,则继续前进。

当到达某一站点时,若这个sum值小于0了,则说明从起点到这个点中间的任何一个点都不能作为起点,则把起点设为下一个点,继续遍历。

当遍历完整个环时,当前保存的起点即为所求。代码如下:

    public int canCompleteCircuit(int[] gas, int[] cost) {
        int total = 0, sum = 0, start = 0;
        for (int i = 0; i < gas.length; i++) {
            total += (gas[i] - cost[i]);
            sum += (gas[i] - cost[i]);
            if (sum < 0) {
                start = i + 1;
                sum = 0;
            }
        }
        if (total < 0) {
            return -1;
        }
        return start;
    }

196. Find the Missing Number

给定了n-1个数字,他们的范围是0到n,要求你找到那个少了的数。比如:Given N = 3 and the array [0, 1, 3], return 2.

参考了老印的解法:http://www.geeksforgeeks.org/find-the-missing-number/ 看完后恍然大悟,把从0到n求和,然后减去数组之和,就是那个少了的数了

    public int findMissing(int[] nums) {
        int n = nums.length;
        int total = n * (n + 1) / 2;
        int sum = 0;

        for (int i = 0; i < nums.length; i++) {
            sum += nums[i];
        }
        return total - sum;
    }



你可能感兴趣的:(Algorithm)