LeetCode日记——【算法】贪心思想专题
假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。对每个孩子 i ,都有一个胃口值 gi ,这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j ,都有一个尺寸 sj 。如果 sj >= gi ,我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
注意:
你可以假设胃口值为正。
一个小朋友最多只能拥有一块饼干。
示例 :
输入: [1,2,3], [1,1]
输出: 1
解释:
你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。
虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。
所以你应该输出1。
代码:
class Solution { public int findContentChildren(int[] g, int[] s) { if(g==null||s==null) return 0; Arrays.sort(g); Arrays.sort(s); int i=0,j=0; while(i < g.length && j < s.length){ if(g[i]<=s[j]){ i++; } j++; } return i; } }
分析:
先把胃口序列和饼干序列都排好序。
设计两个指针,一开始分别指向两个数组的头部。
比较两个指针指向的数,如果当前饼干能满足孩子,就跳到下一个孩子,下一块饼干。
如果不能满足,换下一块饼干,孩子胃口指针不动。(这块不能满足,就看看下一块更大的饼干能不能满足)。
如果饼干没有了,或者孩子没有了,都退出循环。
最后返回孩子胃口的索引。
最后返回的i,如果i从1开始,本来应该是i-1个孩子,但是数组下标是从0开始的,一抵消,就相当于直接返回i。
给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。
注意:
可以认为区间的终点总是大于它的起点。
区间 [1,2] 和 [2,3] 的边界相互“接触”,但没有相互重叠。
示例 1:
输入: [ [1,2], [2,3], [3,4], [1,3] ]
输出: 1
解释: 移除 [1,3] 后,剩下的区间没有重叠。
代码:
class Solution { public int eraseOverlapIntervals(int[][] intervals) { //intervals是一个数组,数组元素是区间 //若intervals为空,则直接返回0 if (intervals.length == 0) { return 0; } //按区间的结尾从小到大进行排序 Arrays.sort(intervals, new Comparator<int[]>() { @Override public int compare(int[] o1, int[] o2) { return o1[1] - o2[1]; } }); int cnt = 1; //end定义为第一个区间的结尾 int end = intervals[0][1]; //遍历第2个区间到最后一个区间 for (int i = 1; i < intervals.length; i++) { //若当前区间的开头比end小,就直接跳下一个区间 if (intervals[i][0] < end) { continue; } //若当前区间与end所在区间没有重叠,则更新end为当前区间的末尾,计数++ end = intervals[i][1]; cnt++; } return intervals.length - cnt; } }
分析:
这里重写了sort方法,目的是按区间尾部从小到大排序。
基本思想是记录第一个区间的尾部,然后从第二个区间开始,比较第二个区间与第一个区间是否有重叠。
若有重叠,直接跳过第二个区间,看第三个区间,比较第三个区间与第一个区间是否有重叠。
若无重叠,更新end值为第二个区间的尾部,然后计数++。然后接着比较第三个区间与第二个区间是否有重叠。
以此类推。最后返回总区间个数与计数的差值。
在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。由于它是水平的,所以y坐标并不重要,因此只要知道开始和结束的x坐标就足够了。开始坐标总是小于结束坐标。平面内最多存在104个气球。
一支弓箭可以沿着x轴从不同点完全垂直地射出。在坐标x处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足 xstart ≤ x ≤ xend,则该气球会被引爆。可以射出的弓箭的数量没有限制。 弓箭一旦被射出之后,可以无限地前进。我们想找到使得所有气球全部被引爆,所需的弓箭的最小数量。
Example:
输入:
[[10,16], [2,8], [1,6], [7,12]]
输出:
2
解释:
对于该样例,我们可以在x = 6(射爆[2,8],[1,6]两个气球)和 x = 11(射爆另外两个气球)。
代码:
class Solution { public int findMinArrowShots(int[][] points) { if(points.length==0) return 0; Arrays.sort(points, Comparator.comparingInt(o -> o[1])); int cnt=1; int end = points[0][1]; for(int i=1;i<points.length;i++){ //注意这里要取等号!! if(points[i][0]<=end){ continue; } end=points[i][1]; cnt++; } return cnt; } }
分析:
思路与上一道基本相同,sort方法使用了lambda表达式来简化代码。
不同的是,若判断到当前区间与上一个end表示的区间有重叠,这里接壤也算有重叠(意思就是接壤也算能用一根剑刺破了)所以要取等。但是上一道接壤是不算重叠的,所以不取等。
假设有打乱顺序的一群人站成一个队列。 每个人由一个整数对(h, k)表示,其中h是这个人的身高,k是排在这个人前面且身高大于或等于h的人数。 编写一个算法来重建这个队列。
注意:
总人数少于1100人。
示例
输入:
[[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]]
输出:
[[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]]
代码:
class Solution { public int[][] reconstructQueue(int[][] people) { if(people==null||people.length==0||people[0].length==0) return new int[0][0]; //按身高降序排列;若身高相同,按k升序排列 Arrays.sort(people, (a, b) -> (a[0] == b[0] ? a[1] - b[1] : b[0] - a[0])); List<int[]> queue = new ArrayList<>(); //遍历people数组 for(int[] p:people){ //p[1]表示遍历到的人的k值,把遍历到的学生插入队列第k个位置 queue.add(p[1], p); } //新建一个数组arry来存放排列好的人 int [][] arry = new int[queue.size()][]; //把队列转换成数组 queue.toArray(arry); //返回数组arry return arry; } }
分析:
差点没看懂题意。用那个例子来理解一下:
[[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]] 第一位身高为5的人的前面没人比他高所以k为0;第二位身高为7的人的前面也没人比他高所以k为0;第三位身高为5的人的前面有2人比他高或者一样高所以k为2;第四位身高为6的人的前面有1人比他高所以k为1;第五位身高为4的人的前面有4人比他高所以k为4;第六位身高为7的人的前面有1人比他高或一样高所以k为1。
基本思路就是:先将原数组按身高降序,身高一样按k升序。建立一个队列,然后遍历数组,从头开始分别把人塞到第k位,保持当前塞的人是第k位即可。最后要把队列转化为二维数组再返回。
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。
注意:你不能在买入股票前卖出股票。
示例 1:
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
代码:
class Solution { public int maxProfit(int[] prices) { int n = prices.length; if (n == 0) return 0; //将当前最低价初始化为第一个元素 int soFarMin = prices[0]; int max = 0; for (int i = 1; i < n; i++) { //若当前价格比soFarMin低,就更新soFarMin if (soFarMin > prices[i]) soFarMin = prices[i]; //若当前价格比soFarMin高或相等,将当前最高利润更新为当前价格与soFarMin的差值 else max = Math.max(max, prices[i] - soFarMin); } return max; } }
分析:
只要记录前面的最小价格,将这个最小价格作为买入价格,然后将当前的价格作为售出价格,查看当前收益是不是最大收益。
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入: [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
代码:
class Solution { public int maxProfit(int[] prices) { int profit=0; for(int i=1;i<prices.length;i++){ if(prices[i]>prices[i-1]){ profit+=(prices[i]-prices[i-1]); } } return profit; } }
分析:
当访问到一个 prices[i] 且 prices[i] - prices[i-1] > 0,那么就把 prices[i] - prices[i-1] 添加到收益中。
假设你有一个很长的花坛,一部分地块种植了花,另一部分却没有。可是,花卉不能种植在相邻的地块上,它们会争夺水源,两者都会死去。
给定一个花坛(表示为一个数组包含0和1,其中0表示没种植花,1表示种植了花),和一个数 n 。能否在不打破种植规则的情况下种入 n 朵花?能则返回True,不能则返回False。
示例 1:
输入: flowerbed = [1,0,0,0,1], n = 1
输出: True
代码:
class Solution { public boolean canPlaceFlowers(int[] flowerbed, int n) { int len = flowerbed.length; int cnt = 0; for(int i=0; i<len && cnt<n; i++){ if(flowerbed[i]==1){ continue; } int pre=i==0?0:flowerbed[i-1]; int next=i==len-1?0:flowerbed[i+1]; if(pre==0&&next==0){ cnt++; flowerbed[i]=1; } } return cnt>=n; } }
分析:
遍历数组中的每个元素,遇到1跳过,遇到0时,记录下它前面的数pre,与后面的数next(注意边界处理)。若pre和next也为0,就计数++,并将这个位置设为1(种花)。
给定字符串 s 和 t ,判断 s 是否为 t 的子序列。
你可以认为 s 和 t 中仅包含英文小写字母。字符串 t 可能会很长(长度 ~= 500,000),而 s 是个短字符串(长度 <=100)。
字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。
示例 1:
s = "abc", t = "ahbgdc"
返回 true.
代码:
class Solution { public boolean isSubsequence(String s, String t) { int index = -1; //遍历s中的每个字符 for (char c : s.toCharArray()) { //从t中的index+1位置开始寻找字符c,并把第一次找到的索引存入index,若找不到,返回-1 index = t.indexOf(c, index + 1); if (index == -1) { return false; } } return true; } }
分析:
初始化index为-1。将s转化为数组,然后遍历s中的每个元素。从t中的index+1位置开始查找字符c,并把第一次找到的索引存入index。若找不到,则index=-1。index为-1则返回false。最后返回true。
题9:非递减数列
给你一个长度为 n 的整数数组,请你判断在 最多 改变 1 个元素的情况下,该数组能否变成一个非递减数列。
我们是这样定义一个非递减数列的: 对于数组中所有的 i (0 <= i <= n-2),总满足 nums[i] <= nums[i + 1]。
示例 1:
输入: nums = [4,2,3]
输出: true
解释: 你可以通过把第一个4变成1来使得它成为一个非递减数列。
代码:
class Solution { public boolean checkPossibility(int[] nums) { //改变的元素的个数 int cnt = 0; //从第2个元素开始遍历,改变元素为2即不再遍历返回false for (int i = 1; i < nums.length && cnt < 2; i++) { //若满足当前元素比上一个元素大或相等(满足非递减),就跳过继续遍历下一个元素 if (nums[i] >= nums[i - 1]) { continue; } //若遇到当前元素比上一个元素小,计数++ cnt++; //如果当前遍历的是第3个元素及以后的元素,且它的前面的前面的元素比它大 if (i - 2 >= 0 && nums[i - 2] > nums[i]) { //将当前值设为上一个元素的值 nums[i] = nums[i - 1]; } else { //将上一个元素的值设为当前值 nums[i - 1] = nums[i]; } } return cnt <= 1; } }
分析:
主要思路就是,设置一个计数值,然后从第二个元素开始遍历(满足计数值为0或1才遍历,因为最多只能修改一次),如果满足要求就直接遍历下一个元素,如果出现不满足的情况,计数值++,然后将nums[i-1]=nums[i],再进行下一个遍历。
还有一个比较特别的情况就是 nums[i] < nums[i - 2],修改 nums[i - 1] = nums[i] 不能使数组成为非递减数组,只能修改 nums[i] = nums[i - 1]。
比如3,4,2。我们遍历到2,发现它比4小。如果我们把4变成2,那么3,2,2也不满足条件了,这种情况应该将2改为4,即变成3,4,4。
题10:子数组最大的和
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
class Solution { public int maxSubArray(int[] nums) { if(nums==null||nums.length==0) return 0; //先定义preSum和maxSum为第一个元素 int preSum = nums[0]; int maxSum = preSum; //从第2个元素开始遍历 for(int i=1;i<nums.length;i++){ //之前和大于0,则加上当前元素,否则更新preSum为当前元素 preSum = preSum > 0 ? preSum + nums[i] : nums[i]; //maxSum始终记录最大的preSum值 maxSum = Math.max(maxSum, preSum); } //返回maxSum return maxSum; } }
分析:
一开始定义preSum为第一个元素的值。然后从第2个元素开始遍历。如果preSum为负数,直接舍弃,设置preSum为当前数。然后不断更新maxSum。
题11:划分字母区间
字符串 S 由小写字母组成。我们要把这个字符串划分为尽可能多的片段,同一个字母只会出现在其中的一个片段。返回一个表示每个字符串片段的长度的列表。
示例 1:
输入:S = "ababcbacadefegdehijhklij"
输出:[9,7,8]
解释:
划分结果为 "ababcbaca", "defegde", "hijhklij"。
每个字母最多出现在一个片段中。
像 "ababcbacadefegde", "hijhklij" 的划分是错误的,因为划分的片段数较少。
代码:
class Solution { public List<Integer> partitionLabels(String S) { //创建一个容量为26的数组 int[] lastIndexsOfChar = new int[26]; //遍历S中的每一个字符 for (int i = 0; i < S.length(); i++) { //lastIndexsOfChar[i处的字符的编号]=i //这里生成的数组表示a~z每个字母最后出现的位置 //如daeab,则数组为[3,4, ,0,2,......] lastIndexsOfChar[char2Index(S.charAt(i))] = i; } List<Integer> partitions = new ArrayList<>(); int firstIndex = 0; while (firstIndex < S.length()) { int lastIndex = firstIndex; //从firstindex遍历到lastindex for (int i = firstIndex; i < S.length() && i <= lastIndex; i++) { //index存放当前字母最后一次出现的位置的索引 int index = lastIndexsOfChar[char2Index(S.charAt(i))]; if (index > lastIndex) { //lastIndex始终为遍历过的字母中最后出现的索引 //如遍历到daeab中的第一个a时,lastIndex就为3了,直到遍历到b,lastIndex才更新为4 lastIndex = index; } } partitions.add(lastIndex - firstIndex + 1); firstIndex = lastIndex + 1; } return partitions; } //返回字符的编号,a为0,z为25 private int char2Index(char c) { return c - 'a'; } }
分析:
这也太难了吧,啥玩意儿
贪心思想专题完结撒花~