leetcode贪心篇

贪心思想

455.分发饼干

假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。

对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。

示例 1:

输入: g = [1,2,3], s = [1,1]
输出: 1
解释: 
你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。
虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。
所以你应该输出1。
  • 思路
    • 每个孩子应该给予的饼干应该最刚好满足这个孩子,这样更大的饼干可以给胃口更大的孩子
    • 典型的贪心思想
class Solution {
    public int findContentChildren(int[] g, int[] s) {
        Arrays.sort(g); //孩子的胃口排序
        Arrays.sort(s); //饼干大小排序
        int idx = 0; //idx=0开始,因为先满足胃口最小的孩子
        // 从i=0开始排序,也就是先把最小的饼干满足给孩子
        for(int i = 0; i < s.length; ++i) {
            if(g[idx] <= s[i]) ++idx;
            // 如果饼干都满足孩子且还有剩余的时候会有越界的情况,加以判断
            if(idx == g.length) break;
        }
        return idx;
    }
}

435.无重叠区间

给定一个区间的集合 intervals ,其中 intervals[i] = [starti, endi] 。返回 需要移除区间的最小数量,使剩余区间互不重叠

示例 1:

输入: intervals = [[1,2],[2,3],[3,4],[1,3]]
输出: 1
解释: 移除 [1,3] 后,剩下的区间没有重叠。

示例 2:

输入: intervals = [ [1,2], [1,2], [1,2] ]
输出: 2
解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。

示例 3:

输入: intervals = [ [1,2], [2,3] ]
输出: 0
解释: 你不需要移除任何区间,因为它们已经是无重叠的了。
  • 思路
    • 需要移除区间的最小数量,也就是说在这个intervals里用尽可能多的数组来组成一个不重叠的区间,然后把总长度减去不重叠区间的个数,就是最终答案
    • 可以对区间右边界进行排序
class Solution {
    public int eraseOverlapIntervals(int[][] intervals) {
        /**
        	1.用lambda表达式会有额外耗时
        	2.不要写return o1[1] - o2[1],有溢出风险!
        */
        Arrays.sort(intervals, new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {
                return (o1[1] < o2[1]) ? -1 : ((o1[1] == o2[1]) ? 0 : 1);
            }
        });
        int end = intervals[0][1]; //初始化end为第一个区间的右边界
        int count = 1; //计算不重叠区间的组成个数
        for(int i = 1; i < intervals.length; ++i) {
            if(intervals[i][0] >= end) {
                ++count;
                end = intervals[i][1];
            }
        }
        return intervals.length - count;
    }
}

452.使用最少数量箭射爆气球

有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points ,其中points[i] = [xstart, xend] 表示水平直径在 xstartxend之间的气球。你不知道气球的确切 y 坐标。

一支弓箭可以沿着 x 轴从不同点 完全垂直 地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 x``startx``end, 且满足 xstart ≤ x ≤ x``end,则该气球会被 引爆 。可以射出的弓箭的数量 没有限制 。 弓箭一旦被射出之后,可以无限地前进。

给你一个数组 points返回引爆所有气球所必须射出的 最小 弓箭数

示例 1:

输入:points = [[10,16],[2,8],[1,6],[7,12]]
输出:2
解释:气球可以用2支箭来爆破:
-在x = 6处射出箭,击破气球[2,8]和[1,6]。
-在x = 11处发射箭,击破气球[10,16]和[7,12]。

示例 2:

输入:points = [[1,2],[3,4],[5,6],[7,8]]
输出:4
解释:每个气球需要射出一支箭,总共需要4支箭。

示例 3:

输入:points = [[1,2],[2,3],[3,4],[4,5]]
输出:2
解释:气球可以用2支箭来爆破:
- 在x = 2处发射箭,击破气球[1,2]和[2,3]。
- 在x = 4处射出箭,击破气球[3,4]和[4,5]。
  • 思路
    • 贪心思想,希望每次射出去的箭都能恰好射到最多的气球,也就是重叠的区间
    • 最终需要多少支箭就是求不重叠区间的个数,和无重叠区间是一样的
class Solution {
    public int findMinArrowShots(int[][] points) {
        int n = points.length;
        Arrays.sort(points, new Comparator<int[]>() {
            public int compare(int[] o1, int[] o2) {
                return o1[1] < o2[1] ? -1 : (o1[1] == o2[1]) ? 0 : 1; 
            }
        });
        int end = points[0][1];
        int cnt = 1;
        for(int i = 1; i < n; ++i) {
            if(points[i][0] > end) {
                cnt++;
                end = points[i][1];
            }
        }
        return cnt;
    }
}

406.根据身高重建队列

You are given an array of people, people, which are the attributes of some people in a queue (not necessarily in order). Each people[i] = [hi, ki] represents the ith person of height hi with exactly ki other people in front who have a height greater than or equal to hi.

Reconstruct and return the queue that is represented by the input array people. The returned queue should be formatted as an array queue, where queue[j] = [hj, kj] is the attributes of the jth person in the queue (queue[0] is the person at the front of the queue).

Example 1:

Input: people = [[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]]
Output: [[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]
Explanation:
Person 0 has height 5 with no other people taller or the same height in front.
Person 1 has height 7 with no other people taller or the same height in front.
Person 2 has height 5 with two persons taller or the same height in front, which is person 0 and 1.
Person 3 has height 6 with one person taller or the same height in front, which is person 1.
Person 4 has height 4 with four people taller or the same height in front, which are people 0, 1, 2, and 3.
Person 5 has height 7 with one person taller or the same height in front, which is person 1.
Hence [[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]] is the reconstructed queue.

Example 2:

Input: people = [[6,0],[5,0],[4,0],[3,2],[2,2],[1,4]]
Output: [[4,0],[5,0],[2,2],[3,2],[1,4],[6,0]]
  • 思路
    • 既然p[1]表示的是大于等于本身身高的人,那我们就先把身高较高的几个先插进去,如果身高一样就看p[1]
    • 后续插入的people再根据自己的p[1]来插入
class Solution {
    public int[][] reconstructQueue(int[][] people) {
        // 身高降序,个数值升序
        Arrays.sort(people, (p1, p2) -> 
            p1[0] != p2[0] ? (p2[0] - p1[0]) : p1[1] - p2[1]
        );
        List<int[]> ans = new LinkedList();
        // 不会出现越界的问题,因为比你高的people已经插入了List,那你最多插入的位置也就是List.size
        for(int[] p : people) {
            ans.add(p[1], p);
        }
        // 转数组的方法
        return ans.toArray(new int[ans.size()][]);
    }
}

121.买股票最佳时机

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0

示例 1:

输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
     注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。

示例 2:

输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 没有交易完成, 所以最大利润为 0。

提示:

  • 1 <= prices.length <= 105
  • 0 <= prices[i] <= 104

思路

  • 第一想法是通过枚举,暴力搜索,显然超时

  • 贪心,想着肯定是在某一天买到最便宜的票,并在未来的某一天卖出最贵

  • 动态规划

class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length;
        int[][] dp = new int[n][2]; // dp[i][0]是购入,dp[i][1]是出售
        // 在这步先初始化,可读性更强
        dp[0][0] = -prices[0]; 
        dp[0][1] = 0; 
        for(int i = 1; i < n; ++i) {
            // 判断当天购入便宜还是之前某天购入便宜?
            dp[i][0] = Math.max(dp[i - 1][0], -prices[i]);
            // 判断当天出售更赚还是之前某天出售更赚
            dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] + prices[i]);
        }
        // 最后dp[n - 1][1]存的就是最大的利息
        return dp[n - 1][1];
    }
}

122.买股票最佳时机Ⅱ

给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。

在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。

返回 你能获得的 最大 利润

示例 1:

输入:prices = [7,1,5,3,6,4]
输出:7
解释:在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。
     随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6 - 3 = 3 。
     总利润为 4 + 3 = 7 。

示例 2:

输入:prices = [1,2,3,4,5]
输出:4
解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。
     总利润为 4 。

示例 3:

输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 交易无法获得正利润,所以不参与交易可以获得最大利润,最大利润为 0 。

提示:

  • 1 <= prices.length <= 3 * 104
  • 0 <= prices[i] <= 104

思路

  • 有买股票的一天,只要之后有赚,就直接卖,如果未来的日子里还能有如此效果就继续做(贪心的想法)
  • 与股票Ⅰ问题不一样,股票Ⅱ可以进行多次交易,在中间的日子能赚就赚,不用等到最后一天
public class Solution {
    public int maxProfit(int[] prices) {
        int len = prices.length;
        if (len < 2) {
            return 0;
        }
        int res = 0;
        for (int i = 1; i < len; i++) {
            // 有赚就卖
            /*
            	此题说明可以当天卖,当天买
            */
            int diff = prices[i] - prices[i - 1];
            if (diff > 0) {
                res += diff;
            }
        }
        return res;
    }
}

605.种花问题

假设有一个很长的花坛,一部分地块种植了花,另一部分却没有。可是,花不能种植在相邻的地块上,它们会争夺水源,两者都会死去。

给你一个整数数组 flowerbed 表示花坛,由若干 01 组成,其中 0 表示没种植花,1 表示种植了花。另有一个数 n ,能否在不打破种植规则的情况下种入 n 朵花?能则返回 true ,不能则返回 false

示例 1:

输入:flowerbed = [1,0,0,0,1], n = 1
输出:true

示例 2:

输入:flowerbed = [1,0,0,0,1], n = 2
输出:false

提示:

  • 1 <= flowerbed.length <= 2 * 104
  • flowerbed[i]01
  • flowerbed 中不存在相邻的两朵花
  • 0 <= n <= flowerbed.length

思路

  • 贪心思想,从左往右,能种就种,就优解肯定是花尽量靠在一起(也就是中间最多只隔一格)
  • 模拟这个过程,如果当前盆有花(1),那就往后第二盆判断
    • 注:模拟过程是题目说到,所给flowerbed没有相邻的花!
  • 如果当前盆没有花(0),判断其后一个花盆是不是有花
    • 如果有花,就往后第三盆判断
    • 如果没有花,由于我们是一直往后2盆看的花,所以前面肯定没有花,n–
      • 注意如果是最后一盆花就直接n–,避免数组越界问题
class Solution {
    public boolean canPlaceFlowers(int[] flowerbed, int n) {
        int len = flowerbed.length;
        for(int i = 0; i < len;) {
            if(flowerbed[i] == 1) {
                i += 2; // 如果有花,看后第二盆               
            }else if(i == len - 1 || flowerbed[i + 1] == 0) { //没有花,看后一盆
                n--;
                i += 2;
            }else { // 有花,看后第三盆
                i += 3;
            }
        }
        return n <= 0;
    }
}

392.判断子序列

给定字符串 st ,判断 s 是否为 t 的子序列。

字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace""abcde"的一个子序列,而"aec"不是)。

进阶:

如果有大量输入的 S,称作 S1, S2, … , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?

致谢:

特别感谢 @pbrother 添加此问题并且创建所有测试用例。

示例 1:

输入:s = "abc", t = "ahbgdc"
输出:true

示例 2:

输入:s = "axc", t = "ahbgdc"
输出:false

提示:

  • 0 <= s.length <= 100
  • 0 <= t.length <= 10^4
  • 两个字符串都只由小写字母组成

思路

  • 题目问到若有大量的输入需要如何快速判断他是T序列的子序列
    • 第一想法就是求出T序列的所有子序列放在map当中,在这样判断的时间复杂度为O(1)
    • 0 <= t.length <= 10^4 所以肯定超出内存限制
      • 代码附上
class Solution {
    Map<String, Integer> map = new HashMap();
    int len = 0;
    public boolean isSubsequence(String s, String t) {
        len = t.length();
        dfs(t, 0, "");
        return map.get(s) != null;
    }

    /**
    	求所有子序列的递归函数
    */
    public void dfs(String str, int i, String cur) {
        if(i == len) {
            map.put(cur, 1);
            return;
        }
        char c = str.charAt(i);
        dfs(str, i + 1, new String(cur + c));
        dfs(str, i + 1, new String(cur));
    }
}
  • 乖乖回到贪心思想的做法,总是从最左边的字符先判断,双指针
class Solution {
    public boolean isSubsequence(String s, String t) {
        int n = s.length(), m = t.length();
        int i = 0, j = 0;
        while (i < n && j < m) {
            if (s.charAt(i) == t.charAt(j)) {
                i++;
            }
            j++;
        }
        return i == n;
    }
}

665.非递减数列

给你一个长度为 n 的整数数组 nums ,请你判断在 最多 改变 1 个元素的情况下,该数组能否变成一个非递减数列。

我们是这样定义一个非递减数列的: 对于数组中任意的 i (0 <= i <= n-2),总满足 nums[i] <= nums[i + 1]

示例 1:

输入: nums = [4,2,3]
输出: true
解释: 你可以通过把第一个 4 变成 1 来使得它成为一个非递减数列。

示例 2:

输入: nums = [4,2,1]
输出: false
解释: 你不能在只改变一个元素的情况下将其变为非递减数列。

提示:

  • n == nums.length
  • 1 <= n <= 104
  • -105 <= nums[i] <= 105

思路

  • 用flag来标记是否已经做过修改了
  • 如果发现了nums[i] > nums[i + 1],此时做修改
      1. 如果修改nums[i] = num[i + 1] 那么nums[i]就变小,有可能会破坏前面已经遍历的非递减数列
      1. 如果修改nums[i + 1] = nums[i] 那么nums[i + 1]就变大,有可能使得后面的非递减数列破坏
  • 所以我们要考虑nums[i - 1]
    • 若nums[i + 1] >=nums[i - 1] 说明修改方案一并不会破坏前面的非递减数列
    • 若nums[i + 1] < nums[i - 1] 那就实在没办法只能用修改方案2
    • 修改后flag取反
class Solution {
    public boolean checkPossibility(int[] nums) {
        if(nums.length == 1) return true;
        // flag一开始就做判断,是因为for循环中有i-1的操作,防止越界
        boolean flag = nums[0] <= nums[1] ? false : true;
        // for循环的终止是到nums.length-1,因为有i+1
        for(int i = 1; i < nums.length - 1; ++i) {
            if(nums[i] > nums[i + 1]) {
                if(!flag) {
                    flag = true;
                    if(nums[i + 1] >= nums[i - 1]) {
                        nums[i] = nums[i - 1]; // 修改方案1
                    }else {
                        nums[i + 1] = nums[i]; // 修改方案2
                    }
                }else {
                    return false;
                }
            }
        }
        return true;
    }
}

53.最大子数组和

给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

子数组 是数组中的一个连续部分。

示例 1:

输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。

示例 2:

输入:nums = [1]
输出:1

示例 3:

输入:nums = [5,4,-1,7,8]
输出:23

提示:

  • 1 <= nums.length <= 105
  • -104 <= nums[i] <= 104

思路

  • 看liweiwei1419,用的是动态规划来完成的
    • 子问题定义:以nums[i]结尾的最大连续子数组之和
  • dp数组记录和,dp[i]代表以nums[i]结尾的最大子数组之和
  • 若dp[i - 1] < 0 ,那么dp[i] = nums[i]就行
    • 个人理解:既然你前面i-1个数的连续子数组的和最大都是小于0,那我nums[i]带上你们就是拖累我,那就直接自己做一个连续子数组的和都比他们大
    • 如果dp[i - 1] > 0 说明前面还是有点用,可以留着,就dp[i] = nums[i] + dp[i - 1]
class Solution {
    public int maxSubArray(int[] nums) {

        int n = nums.length;
        int[] dp = new int[n];
        dp[0] = nums[0];
        for(int i = 1; i < n; ++i) {
            if(dp[i - 1] < 0) {
                dp[i] = nums[i];
            }else {
                dp[i] = nums[i] + dp[i - 1];
            }
        }  
        int ans = Integer.MIN_VALUE;
        for(int i = 0; i < n; ++i) {
            ans = Math.max(ans, dp[i]);
        }     
        return ans;
    }
}

763.划分字母区间

给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。

注意,划分结果需要满足:将所有划分结果按顺序连接,得到的字符串仍然是 s

返回一个表示每个字符串片段的长度的列表。

示例 1:

输入:s = "ababcbacadefegdehijhklij"
输出:[9,7,8]
解释:
划分结果为 "ababcbaca"、"defegde"、"hijhklij" 。
每个字母最多出现在一个片段中。
像 "ababcbacadefegde", "hijhklij" 这样的划分是错误的,因为划分的片段数较少。 

示例 2:

输入:s = "eccbbbbdec"
输出:[10]

提示:

  • 1 <= s.length <= 500
  • s 仅由小写英文字母组成

思路

  • 题目的意思就是希望同一个字母只能出现在一个片段当中
  • 可以记录每个字母最后出现的下标位置
  • 因为了分出更多的片段,所以我们要在一个片段当中放尽可能少的字母,同时又保证这些字母不会出现在别的片段
class Solution {
    public List<Integer> partitionLabels(String s) {
        int[] lastIndex = new int[26]; //记录字母最后出现的下标
        int len = s.length();
        for(int i = 0; i < len; ++i) {
            lastIndex[s.charAt(i) - 'a'] = i;
        }
        List<Integer> ans = new LinkedList();
        int start = 0, end = 0;
        for(int i = 0; i < len; ++i) {
            /**
            	这步我觉得要好好理解一下为什么这么写
            	首先我们在遍历到某个字母的时候,我们先记下这个字母的最大下标
				一个片段的所有字母都遍历过,我们能得到这个片段里最大的下标
				那么此时若i==end就说明这里面的字母只可能出现在这个片段,因为他们的最后一次出现的下标都比end来的小,而end此时是这个片段里最大的一个下标
            */
            end = Math.max(end, lastIndex[s.charAt(i) - 'a']);
            if(i == end) {
                ans.add(end - start + 1);
                start = end + 1;
            }
        }
        return ans;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值