算法三 贪心(Java实现)

1,179 阅读3分钟

刷题时间:2021.7.24-2021.7.25

贪心法:遵循某种规律,不断贪心的选取当前最优策略的算法设计方法。

当前最优解即为全局最优解,贪心成立。

1、LeetCode455分发饼干

思路:1.对需求因子数组g与饼干大小数组s进行从小到大的排序。

2.按照从小到大的顺序使用各饼干尝试是否可满足某个孩子,每个饼干只尝试1次;若尝试成功,则换下一个孩子尝试;直到发现没更多的孩子或者没更多的饼干,循环结束。

class Solution {
    public int findContentChildren(int[] g, int[] s) {
        int child = 0;
        int cookie = 0;
        Arrays.sort(g);
        Arrays.sort(s);
        while (child < g.length && cookie < s.length ){ //当孩子或饼干同时均未尝试完时
            if (g[child] <= s[cookie]){ //当孩子的满足因子小于或等于饼干大小时
                child++;//该饼干满足了孩子,孩子向后移动
            }
            cookie++; // 无论成功或失败,每个饼干只尝试一次,饼干向后移动
        }
        return child; 
    }
}

2、LeetCode376摆动序列

思路:当序列有一段连续的递增(或递减)时,为形成摇摆子序列,我们只需要保留这段连续的递增(或递减)的首尾元素,这样更可能使得尾部的后一个元素成为摇摆子序列的下一个元素。

class Solution {
    public int wiggleMaxLength(int[] nums) {
        int n = nums.length;
        if (n < 2) {//序列个数小于2时直接为摆动序列
            return n;
        }
        int begin = 0;//扫描序列时的三种状态
        int up = 1;
        int down = 2;
        int state = begin;
        int maxn = 1;//摆动序列最大长度至少为1
        for (int i = 1; i < n; i++) {//从第二个元素开始扫描
            if (state == begin) {
                if (nums[i] > nums[i - 1]){
                    state = up;
                    maxn++;
                }else if(nums[i] < nums[i - 1]){
                    state = down;
                    maxn++;
                }
            }
            else if (state == up) {
                if(nums[i] < nums[i - 1]){
                    state = down;
                    maxn++;
                }
            }else if(state == down){
                if (nums[i] > nums[i - 1]){
                    state = up;
                    maxn++;
                }
            }
        }
        return maxn;
    }
}

LeetCode评论代码

class Solution {
    public int wiggleMaxLength(int[] nums) {
        int n = nums.length;
        if (n < 2) {
            return n;
        }
        int up = 1;
        int down = 1;
        for (int i = 1; i < n; i++) {
            if (nums[i] > nums[i - 1]) {
                up = down + 1;
            }
            if (nums[i] < nums[i - 1]) {
                down = up + 1;
            }
        }
        return Math.max(up, down);
    }
}

3、LeetCode402移掉K位数字

思路:从高位向低位遍历,如果对应的数字大于下一位数字,则把该位数字去掉,得到的数字最小!

使用栈存储最终结果或删除工作,从高位向低位遍历num,如果遍历的数字大于栈顶元素, 则将该数字push入栈,如果小于栈顶元素则进行pop弹栈,直到栈为空或不能再删除数字(k==0)或栈顶小于当前元素为止。

class Solution {
    public String removeKdigits(String num, int k) {
        Stack<Integer>stack=new Stack<>();
        for(int i=0;i<num.length();i++){//从最高位循环扫描数字num
            int number = num.charAt(i)-'0';
            //可能好几个值都比当前值大,那么我们就在k允许的情况下,去去除它。
            while(!stack.isEmpty() && k>0 && number<stack.peek()){
                stack.pop();
                k--;
            }
            if(number!=0||!stack.isEmpty()){
                stack.push(number);
            }
        }
        //56789这种情况,前面一直比后面小,那就去除栈顶,谁让栈顶最大
        while(k>0 && !stack.isEmpty()){
            stack.pop();
            k--;
        }
        //10,1(当number=0时,满足条件,去掉1,但now为0,且为空。)
        if(stack.isEmpty())
            return "0";
        StringBuilder sb=new StringBuilder();
        while(!stack.isEmpty())
            sb.append(stack.pop());
        //从后往前添加所以我们要逆序
        return sb.reverse().toString();
    }
}

4、LeetCode55跳跃游戏

public class Solution {
    public boolean canJump(int[] nums) {
        int n = nums.length;
        int[] index = new int[n];//最远可跳至位置
        for (int i = 0; i < n; i++) {
            index[i] = i + nums[i];//计算index数组
        }
        int jump = 0,max_index = index[0];
        while(jump<n && jump<=max_index){//直到jump跳至数组尾部或jump超越了当前可以跳的最远位置
            if(max_index<index[jump]){
                max_index = index[jump];//如果当前可以跳的更远,则更新max_index
            }
            jump++;//扫描jump
        }
        if(jump == n){
            return true;
        }
        return false;
    }
}

LeetCode题解

在遍历的过程中,如果最远可以到达的位置大于等于数组中的最后一个位置,那就说明最后一个位置可达,我们就可以直接返回 True 作为答案。反之,如果在遍历结束后,最后一个位置仍然不可达,我们就返回 False 作为答案。

public class Solution {
    public boolean canJump(int[] nums) {
        int n = nums.length;
        int rightmost = 0;
        for (int i = 0; i < n; ++i) {
            if (i <= rightmost) {
                rightmost = Math.max(rightmost, i + nums[i]);
                if (rightmost >= n - 1) {
                    return true;
                }
            }
        }
        return false;
    }
}

5、LeetCode45跳跃游戏 II

class Solution {
    public int jump(int[] nums) {
        int n = nums.length;
        if(n < 2) return 0;
        int current_max_index = nums[0];//当前可达到的最远位置
        int pre_max_index = nums[0];//遍历各个位置中,可达到的最远位置
        int jump_min = 1;
        for (int i = 1; i < n; i++) {
            if (i > current_max_index) {//若无法再向前移动了,才进行跳跃
                jump_min++;
                current_max_index = pre_max_index;//即更新当前可达到的最远位置
            }
            if(pre_max_index < nums[i]+i){
                pre_max_index = nums[i]+i;
            }
        }
        return jump_min;
    }
}

LeetCode题解

「贪心」地进行正向查找,每次找到可到达的最远位置,就可以在线性时间内得到最少的跳跃次数。

class Solution {
    public int jump(int[] nums) {
        int length = nums.length;
        int end = 0;
        int maxPosition = 0; 
        int steps = 0;
        for (int i = 0; i < length - 1; i++) {
            maxPosition = Math.max(maxPosition, i + nums[i]); 
            if (i == end) {
                end = maxPosition;
                steps++;
            }
        }
        return steps;
    }
}

6、LeetCode452用最少数量的箭引爆气球

思路:1.对各个气球进行排序,按照气球的左端点从小到大排序。

2.遍历气球数组,同时维护一个射击区间,在满足可以将当前气球射穿的情况下,尽可能击穿更多的气球,每击穿一个新的气球,更新一次射击区间(保证射击区间可以将新气球也击穿)。

3.如果新的气球没办法被击穿了,则需要增加一名弓箭手,即维护一个新的射击区间(将该气球击穿),随后继续遍历气球数组。

class Solution {
    public int findMinArrowShots(int[][] points) {
        if (points.length == 0) {
            return 0;
        }
        //java.lang包的Integer类的compare()方法比较作为参数给出的两个整数值(x,y),如果(x == y)则返回零,如果(x <y)则返回小于零,如果(x> y),则返回大于零的值。
        Arrays.sort(points,(a,b)->Integer.compare(a[1],b[1]));
        // Arrays.sort(points, new Comparator<int[]>() {
        //     public int compare(int[] point1, int[] point2) {
        //         if (point1[1] > point2[1]) {
        //             return 1;
        //         } else if (point1[1] < point2[1]) {
        //             return -1;
        //         } else {
        //             return 0;
        //         }
        //     }
        // });
        //对气球按左端点从小到大排序
        int shoot_num = 1;//初始化弓箭手数量为1
        int shoot_begin = points[0][0];//初始化射击区间,即为第一个气球的两端点
        int shoot_end = points[0][1];
        for (int i = 1; i < points.length; i++) {           
            if (points[i][0]<=shoot_end) {
                shoot_begin = points[i][0];
                if(shoot_end>points[i][1]){
                    shoot_end = points[i][1];
                }               
            }else{
                shoot_num++;//在保证当前气球被射穿的条件下,射击区间不能再更新了,需要增加一个新的射击区间了
                shoot_begin = points[i][0];
                shoot_end = points[i][1];
            }
        }
        return shoot_num;
    }
}

LeetCode评论代码

class Solution {
    public int findMinArrowShots(int[][] points) {
        if (points.length == 0) return 0;
        Arrays.sort(points, (o1, o2) -> Integer.compare(o1[0], o2[0]));

        int count = 1;
        for (int i = 1; i < points.length; i++) {
            if (points[i][0] > points[i - 1][1]) {// 气球i和气球i-1不挨着,注意这里不是>=
                count++;// 需要一支箭
            } else {// 气球i和气球i-1挨着
                points[i][1] = Math.min(points[i][1],points[i - 1][1]);// 更新重叠气球最小右边界
            }
        }
        return count;
    }
}

7、LeetCode871最低加油次数

(这个代码没写出来)

思路:1.设置一个最大堆,用来存储经过的加油站的汽油量。

2.按照从起点至终点的方向,遍历各个加油站之间与加油站到终点距离。

3.每次需要走两个加油站之间的距离d,如果发现汽油不够走距离d时,从最大堆中取出一个油量添加,直到可以足够走距离d。

4.如果把最大堆的汽油都添加仍然不够行进距离d,则无法达到终点。

5.当前油量p减少d。

6.将当前加油站油量添加至最大堆。

LeetCode题解

dp[i] 为加 i 次油能走的最远距离,需要满足 dp[i] >= target 的最小 i

class Solution {
    public int minRefuelStops(int target, int startFuel, int[][] stations) {
        int N = stations.length;
        long[] dp = new long[N + 1];
        dp[0] = startFuel;
        for (int i = 0; i < N; ++i){
            for (int t = i; t >= 0; --t){
                if (dp[t] >= stations[i][0]){
                    dp[t+1] = Math.max(dp[t+1], dp[t] + (long) stations[i][1]);//每多一个加油站 station[i] = (location, capacity),如果之前可以通过加 t 次油到达这个加油站,现在就可以加 t+1 次油得到 capcity 的油量。
                }
            }
        }
        for (int i = 0; i <= N; ++i){
            if (dp[i] >= target) return i;
        }            
        return -1;
    }
}