贪心算法

1.算法简介

贪心算法在每一步都做出在当前看来最优的选择,希望能通过局部最优找到全局最优。因此使用贪心算法来找最优解还需要证明它的确能得到全局最优解,通常可以用反证法加以证明。贪心算法在解决一些问题的时候会比动态规划更加方便,有很多基于贪心策略设计的算法,比如最小生成树算法,单源最短路径的Dijkstra算法,实际上A*算法中的启发式信息也可以看成是贪心策略的应用。

下面还是通过实际的问题来加深理解。

2.算法应用

 2.1. Jump Game

Given an array of non-negative integers, you are initially positioned at the first index of the array.

Each element in the array represents your maximum jump length at that position.

Your goal is to reach the last index in the minimum number of jumps.

For example:
Given array A = [2,3,1,1,4]

The minimum number of jumps to reach the last index is2. (Jump1 step from index 0 to 1, then3 steps to the last index.)

Note:
You can assume that you can always reach the last index.

通常 minimum,maximum 等字眼会提醒我们题目可能会用贪心算法或动态规划解决。这里就可以用贪心算法解决,每次都选择所跳范围内下一步能够跳的更远的位置。比如第一个元素2,它可以选择跳到其后的3或者1,但是选择3的话,下一步会跳的更远,因此应该选择3。选择3,只需要跳2次就可以到达最后,选择1则需要3步。

我们可以简单的证明如下:

a——b——c————d——........——e

假如当前可以选择跳到a或者b,跳到a的话下一步可以跳到c,跳到b的话下一步可以跳到d(最远)。假设选择a会以更少的步数跳到结尾e。分两种情况:如果c在b,d之间,那么选择b也一样能以最少的步数到达e(b在下一步选择c即可),与假设矛盾。还有一种情况,c在a,b之间,那么直接选择c,会以更少的步数跳到最后,与假设矛盾。所以假设不成立,这就证明了我们的算法是正确的。

代码如下:

public class Solution {
    public int jump(int[] nums) {
        if(nums == null || nums.length <= 1) return 0;
        int step = 0, max=0, index = 0, i=0, j=0, len = nums.length;
        while(i < len-1){
            if(i+nums[i] >= len-1){
                step++;
                return step;
            }
            max = 0;
            for(j = i+1;j <= i+nums[i]; j++){
                if(j-i+nums[j]>max){
                    max = j-i+nums[j];
                    index = j;
                }
            }//for
            step++;
            i = index;
            if(i>len-1) break;
        }//
        return step;
    }
}

2.2. Best Time to Buy and Sell Stock II


Say you have an array for which the ith element is the price of a given stock on dayi.

Design an algorithm to find the maximum profit. You may complete as many transactions as you like (ie, buy one and sell one share of the stock multiple times). However, you may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again).

贪心策略:只要有利可图就进行买卖。

简单证明如下:假设我们的策略不正确。我们知道任何一个数列均可看成是由一系列的递增子数列组成的,设xi....xj, xk....xl是相邻的两个递增数列,其中xj>xk。现在设xa(i<=a<=j)买入,xb(k <= b <=l)卖出,此时收益是(xb-xa)。只需要证明 xb - xa < (xj - xa)+(xb-xk)。即xj - xk > 0,这显然是正确的,这就证明了我们的结论。

代码如下:

public class Solution {
    public int maxProfit(int[] prices) {
        if(prices == null) return 0;
        if(prices.length == 0 || prices.length == 1) return 0;
        int sum = 0;
        for(int i = 0;i < prices.length-1;i++){
            if(prices[i] < prices[i+1]) sum += prices[i+1] - prices[i];
        }
        return sum;
    }
}

2.3. Gas Station

There are N gas stations along a circular route, where the amount of gas at stationi isgas[i].

You have a car with an unlimited gas tank and it costscost[i] of gas to travel from stationi to its next station (i+1). You begin the journey with an empty tank at one of the gas stations.

Return the starting gas station's index if you can travel around the circuit once, otherwise return -1.

Note:
The solution is guaranteed to be unique.

贪心策略:在连续累积gas最多的情况下,如果能环绕一圈就可以,否则,不能。也就是说如果gas累积到最大的情况下,不会绕一圈,那么就不可能从任何位置开始绕一圈。

简单证明如下:假设从b开始到d gas累积到最大值,但是没能环绕一圈,但是从c开始却能环绕一圈。

1. c在b,d之间,由条件知,b->a累积gas>0,a->b累积gas>0,那么从a->d累积的gas > b->d累积的gas,这与假设矛盾。

a--------b--------c-------d------a。

2.c在b,d之外

a-----c-----b--------d----------a

由从b开始未能环绕一周知:b->d+d->a + a->c + c->b累积gas<0,由从c能绕一周知:c->b + b->d + d->a+a->c累积gas>=0,即c->b累积gas>-(b->d+d->a + a->c + c->b累积gas),=>c->b累积gas>0,这与最大累积gas是从b开始又矛盾。

综上,得证。

继续推广:

a----1----b----2-----c------3-----a

如上图1,2,3代表三个区间。如果2最大,那么可以推出1,3都是负的,可以通过反证法证明,比较容易证。进一步推出1+3是小于0的,进一步就得到了这样的结论,如果1+2+3>=0,就能环绕一圈,否则,无法环绕一圈

题目要求返回起始位置,如果我们是从区间1内的某点开始遍历的,通过记录最大值对应的起始位置可以得到最终结果;但是如果是从区间2内的某点开始的,就行不同了,这时可以通过记录最小值的结束位置来实现。

代码如下:

public class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        if(gas.length != cost.length) return -1;
        if(gas.length == 0) return -1;
        int total = 0,diff  =0;
        int MAX = gas[0] - cost[0], MIN = gas[0] - cost[0], max = gas[0] - cost[0], min = gas[0] - cost[0];
        int stmax = 0, stMax = 0,endMIN = 0;
        
        for(int i=0;i < gas.length;i++){
            diff = gas[i] - cost[i];
            total += diff;
            if(max < 0){
                max = diff;
                stmax = i; 
            }else max += diff;
            if(max>MAX){
                MAX = max;
                stMax = stmax;
            }
            
            if(min > 0) min = diff;
            else min += diff;
            
            if(min<MIN){
                MIN = min;
                endMIN = i; //endMIN用来存储最小序列的末尾点
            }
        }
        return total<0?-1:((MAX>total-MIN)?stMax:(endMIN+1)%gas.length);
    }
}







  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值