【每日一题】Leetcode - 贪心算法题目(1)

题目推荐来源:GitHub - LeetCode 101: A LeetCode Grinding Guide (C++ Version)

  • 算法解释:贪心算法或贪心思想采用贪心的策略,保证每次操作都是局部最优的,从而使最后得到的结果是全局最优的。

  • Leetcode-455.分发饼干 - easy

class Solution {
    public int findContentChildren(int[] g, int[] s) {
        Arrays.sort(g);
        Arrays.sort(s);
        int child = 0, cookie = 0;
        while (child < g.length && cookie < s.length) {
            if(g[child] <= s[cookie]) ++child;
            ++cookie;
        }
        return child;
    }
}

Java 使用 Sort 给数组排序

//这是最笨的直接模拟的方法
class Solution {
    public int candy(int[] ratings) {
        int index = 0;
        int[] candies = new int[ratings.length];
        while (index < ratings.length) {
            if (index == 0) {
                candies[index] = 1;
            }else if (ratings[index-1] < ratings[index]) {
                candies[index] = candies[index-1] + 1;
            }else if (ratings[index-1] == ratings[index]) {
                candies[index] = 1;
            }else if (ratings[index-1] > ratings[index]) {
                candies[index] = 1; 
                if(candies[index-1] == 1) {
                    candies[index-1] += 1;
                    int tIndex = index - 1; 
                    while(tIndex > 0) {
                        if (ratings[tIndex-1] > ratings[tIndex] && candies[tIndex-1] <= candies[tIndex]) {
                            candies[tIndex-1] += 1;
                            --tIndex;
                        }else {
                            break;
                        }

                    }   
                }
            }
            ++index;
        }
        int sum = 0;
        for(int num : candies) {
            sum += num;
        }
        return sum;
    }
}
//这是使用贪心的很有趣的方法 
class Solution {
    public int candy(int[] ratings) {
        int[] candies = new int[ratings.length];
        candies[0] = 1;
        for(int i = 1; i < ratings.length; ++i) {
            if(ratings[i-1] < ratings[i]) {
                candies[i] = candies[i-1] + 1;
            }else {
                candies[i] = 1;
            }
        }
        for(int i = ratings.length - 1; i > 0; --i) {
            if(ratings[i-1] > ratings[i] && candies[i-1] <= candies[i]) {
                candies[i-1] = candies[i] + 1; 
            }
        }
        return Arrays.stream(candies).sum();
    }
}
class Solution {
    public int eraseOverlapIntervals(int[][] intervals) {
        Arrays.sort(intervals, new Comparator<int[]>() {
            @Override
            public int compare(int[] t1, int[] t2) {
                return t1[1] - t2[1];
            }
        });
        int remove = 0;
        int last = 0;
        for(int i = 1; i < intervals.length; ++i) {
            if(intervals[i][0] >= intervals[last][1]){
                last = i;
            }else{
                ++remove;
            }
        }
        return remove;
    }
}
//最笨的模拟方法,但是运行挺快,就是写起来比较繁琐
class Solution {
    public boolean canPlaceFlowers(int[] flowerbed, int n) {
        for(int i = 0; i < flowerbed.length && n != 0; ++i) {
            if(flowerbed[i] != 0) continue;
            if(i == 0) {
                if(flowerbed.length == 1 || flowerbed[i+1] == 0){
                    --n;
                    flowerbed[i] = 1;
                };
            }else if (i == flowerbed.length-1) {
                if(flowerbed[i-1] == 0) {
                    flowerbed[i] = 1; 
                    --n;
                }
            }else {
                if(flowerbed[i-1] == 0 && flowerbed[i+1] == 0) {
                    flowerbed[i] = 1;
                     --n;
                }
            }
        }
        return n == 0;
    }
}
//数学归纳法,可归纳出连续0的数量与最大可种植数目的关系:(count-1)/2
//对于边界问题可假想为左右两边各补了一个0
public static boolean canPlaceFlowers(int[] flowerbed, int n) {
        if (flowerbed == null || flowerbed.length == 0) return n == 0;

        int countOfZero = 1; // 当前全0区段中连续0的数量,刚开始预设1个0,因为开头花坛的最左边没有花,可以认为存在一个虚无的0
        int canPlace = 0; // 可以种的花的数量
        for (int bed : flowerbed) {
            if (bed == 0) { // 遇到0,连续0的数量+1
                countOfZero++;
            } else { // 遇到1,结算上一段连续的0区间,看能种下几盆花:(countOfZero-1)/2
                canPlace += (countOfZero-1)/2;
                if (canPlace >= n) return true;
                countOfZero = 0; // 0的数量清零,开始统计下一个全0分区
            }
        }
        // 最后一段0区还未结算:
        countOfZero++; // 最后再预设1个0,因为最后花坛的最右边没有花,可以认为存在一个虚无的0
        canPlace += (countOfZero-1)/2;

        return canPlace >= n;
}
-- from Leetcode 官方题解下的评论 思路很不错 学习一下
//此处贪心的策略是要使得一只箭射爆最多的气球,及找到最多的公共区间。
//排序+贪心的思想
class Solution {
    public int findMinArrowShots(int[][] points) {
        if(points == null || points.length == 0) return 0;
        Arrays.sort(points, Comparator.comparingInt(p -> p[1]));

        int arrows = 1;
        int end = points[0][1];
        for(int[] point : points) {
            if(point[0] > end) {
                ++arrows; 
                end = point[1];
            }
        }
        return arrows;
    }
}
//自己按照贪心算法的思想写的代码
//1.首先统计每个字母出现的区间
//2.排序字母出现区间,按照先后顺序
//3.寻找最小的无重叠的组合区间(贪心的思想)
class Solution {
   public List<Integer> partitionLabels(String S) {
        Map<Character, int[]> pos = new HashMap<>(26);
        for (int i = 0; i < S.length(); ++i) {
            if (pos.containsKey(S.charAt(i))) {
                pos.get(S.charAt(i))[1] = i;
            } else {
                pos.put(S.charAt(i), new int[]{i, i});
            }
        }
        List<int[]> posList = new ArrayList<>(pos.values());
        posList.sort(Comparator.comparingInt(t -> t[0]));
        List<Integer> result = new ArrayList<>();
        int start = -1;
        int end = -1;
        for (int[] entry : posList) {
            if (entry[0] > end) {
                if (start != -1) {
                    result.add(end - start + 1);
                }
                start = entry[0];
                end = entry[1];
            } else {
                end = Math.max(end, entry[1]);
            }
        }
        result.add(end - start + 1);
        return result;
    }
}
//官方题解
//使用数组下标对应字母序作为索引统计字母出现的最后位置
//遍历字符串统计当前出现字符的最后位置,直到达到当前片段的最后位置
//有时候你可能只差一步:学会深入思考。
class Solution {
    public List<Integer> partitionLabels(String S) {
        int[] last = new int[26];
        int length = S.length();
        for (int i = 0; i < length; i++) {
            last[S.charAt(i) - 'a'] = i;
        }
        List<Integer> partition = new ArrayList<Integer>();
        int start = 0, end = 0;
        for (int i = 0; i < length; i++) {
            end = Math.max(end, last[S.charAt(i) - 'a']);
            if (i == end) {
                partition.add(end - start + 1);
                start = end + 1;
            }
        }
        return partition;
    }
}
//自己思考的贪心算法,可以类比与函数图像的极值点
//当前的极大值减去之前的极小值可以得到这段时间的最大利润
class Solution {
    public int maxProfit(int[] prices) {
        int min = Integer.MAX_VALUE;
        int profit = 0;

        for (int i = 0; i < prices.length; ++i) {
            if(prices[i] < min) {
                min = prices[i];
            }
            else if(i == prices.length-1) {
                if(prices[i] > min) {
                    profit += (prices[i] - min);
                }
            }
            else if(prices[i] > prices[i-1] && prices[i] >= prices[i+1]) {
                profit += (prices[i] - min);
                min = prices[i+1];
            }
            System.out.println(i);
        }

        return profit;
    }
}
//官方的贪心算法,很精辟
class Solution {
    public int maxProfit(int[] prices) {
        int ans = 0;
        int n = prices.length;
        for (int i = 1; i < n; ++i) {
            ans += Math.max(0, prices[i] - prices[i - 1]);
        }
        return ans;
    }
}

官方题解截图如下:来源力扣(LeetCode)
著作权归作者所有

在这里插入图片描述

class Solution {
 public int[][] reconstructQueue(int[][] people) {
        if (people == null || people.length == 0 || people.length == 1) {
            return people;
        }
        Arrays.sort(people, (p1, p2) -> p1[0] == p2[0] ? (p2[1] - p1[1]) : p1[0] - p2[0]);
        int[][] ans = new int[people.length][];
        for (int[] p : people) {
            int space = p[1] + 1;
            int i = 0;
            for (; i < ans.length; ++i) {
                if (ans[i] == null) {
                    --space;
                    if (space == 0) {
                        break;
                    }
                }
            }
            ans[i] = p;
        }
        return ans;
    }
}
//借鉴网友的贪心思想的算法
//这道题目看似是Easy的难度,但是贪心的策略还是挺难思考的
class Solution {
    public boolean checkPossibility(int[] nums) {
        int cnt = 0;
        for (int i = 0; i < nums.length - 1 && cnt < 2; ++i) {
            if (nums[i] > nums[i+1]) {
                ++cnt;
                if(i > 0 && nums[i-1] > nums[i+1]) {
                    nums[i + 1] = nums[i];
                } else {
                    nums[i] = nums[i + 1];
                }
            }
        }

        return cnt < 2;
    }
}

贪心的策略:

  1. 要保证是一个非递减序列,即保证所有的nums[i] <= nums[i+1]
  2. 当出现一个逆序对时,即nums[i] > nums[i+1],此时要改变二者其一使得序列仍然保持非递减
    • 如果改变 nums[i+1] (增大),可能会影响 i+1 之后的序列
    • 如果改变 nums[i] (减小),可以肯定的是 nums[i] >= nums[i-1],那么将nums[i] 减小到与 nums[i-1] 相等即可。
    • 那么综上这两点,贪心的策略即在发现逆序对时尽可能的减小 nums[i];
  3. 那在什么情况下需要增大nums[i+1]呢?
    • 我们已知 nums[i] >= nums[i-1],nums[i] > nums[i+1],如果此时nums[i+1] < nums[i-1],那我们减小 nums[i] 到达 nums[i-1] 时仍然存在逆序 nums[i] > nums[i-1],所以在这种情况下我们应当增加 nums[i+1],使得nums[i+1] = nums[i];
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值