【数据结构与算法】贪心思想经典题总结[leetcode]

分发饼干

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) {
        Arrays.sort(g);
        Arrays.sort(s);
        int i=0,j=0;
        while(i<g.length&&j<s.length){
            if(s[j]>=g[i])
                ++i;
            ++j;
        }
        return i;
    }
}

无重叠区间

LeetCode:无重叠区间

题目描述:

给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。

注意:

可以认为区间的终点总是大于它的起点。
区间 [1,2] 和 [2,3] 的边界相互“接触”,但没有相互重叠。

示例:

输入: [ [1,2], [2,3], [3,4], [1,3] ]

输出: 1

解释: 移除 [1,3] 后,剩下的区间没有重叠。

思想:

首先对每个区间排序(按照右边界排序)。假设A区间与B区间相邻(B在A右边),若A与B重叠,两者至少要移除一个才能满足要求,这时一定是移除B区间,因为A与B相比,B对后续区间可能的影响更大。

代码

class Solution {
    public int eraseOverlapIntervals(int[][] intervals) {
        if(intervals.length==0) return 0;
        Arrays.sort(intervals,new Comparator<int[]>(){
            @Override
            public int compare(int[] a, int[] b){
                return a[1]-b[1];
            }
        });
        int preEnd=intervals[0][1],res=0;
        for(int i=0;i<intervals.length-1;++i){
            if(preEnd<=intervals[i+1][0])
                preEnd = intervals[i+1][1];
            else
                ++res;
        }
        return res;
    }
}

用最少数量的箭引爆气球

LeetCode:用最少数量的箭引爆气球

题目描述:

在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。由于它是水平的,所以y坐标并不重要,因此只要知道开始和结束的x坐标就足够了。开始坐标总是小于结束坐标。平面内最多存在104个气球。

一支弓箭可以沿着x轴从不同点完全垂直地射出。在坐标x处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足  xstart ≤ x ≤ xend,则该气球会被引爆。可以射出的弓箭的数量没有限制。 弓箭一旦被射出之后,可以无限地前进。我们想找到使得所有气球全部被引爆,所需的弓箭的最小数量。

示例:

输入:
[[10,16], [2,8], [1,6], [7,12]]

输出:
2

解释:
对于该样例,我们可以在x = 6(射爆[2,8],[1,6]两个气球)和 x = 11(射爆另外两个气球)。

思想:

首先这题拿来可以想到,大致做法是,先排序,再遍历一遍得到结果。

排序有两种排序方式,按右边界排序,还是左边界排序?假设排序后A区间和B区间相邻。如果按照右边界,A_right只与B_left的大小关系不清晰;如果按照左边界排序,A_right与B_left、B_right关系都不清晰。明显是第一种更好,只需要判断一次。通过判断决定A和B是分别射穿,还是一起射穿。这样,思路就出来了。

贪心思想在于:尽可能地爆更多气球,每次都射边界位置。所以需要每次循环时,用int值记录边界值。

代码

class Solution {
    public int findMinArrowShots(int[][] points) {
        if(points.length==0) return 0;
        Arrays.sort(points,new Comparator<int[]>(){
            public int compare(int[] a,int[] b){
                return a[1]-b[1];
            }
        });
        int res=1,edge=points[0][1];
        for(int i=1;i<points.length;++i){
            if(points[i][0]>edge){
                ++res;
                edge = points[i][1];
            }
        }
        return res;
    }
}

根据身高重建队列

LeetCode:根据身高重建队列

题目描述:

假设有打乱顺序的一群人站成一个队列。 每个人由一个整数对(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]]

思想:

贪心算法很重要一个特性:无后效性,即某个状态以后的过程不会影响以前的状态,只与当前状态有关。

同时本题很重要一个点:高个子的人可以忽略矮个子的人,即每个人的插入位置只与比自己高的人相关。

于是可以想到,先按身高由高到低排序,再依次插入List中,刚好满足贪心的无后效性。每一次插入时,item[1]值刚好是插入序号,这个位置是对当前人来说,最优的决策,即局部最优。然后可以导致全局最优。

代码

class Solution {
    public int[][] reconstructQueue(int[][] people) {
        Arrays.sort(people, new Comparator<int[]>(){
            public int compare(int[] a,int[] b){
                return a[0]!=b[0]?(b[0]-a[0]):(a[1]-b[1]);
            }
        });
        List<int[]> res = new LinkedList<>();
        for(int[] item : people){
            res.add(item[1],item);
        }
        return res.toArray(people);
    }
}

买卖股票的最佳时机2

LeetCode:买卖股票的最佳时机2

题目描述:

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例:

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

思想:

买卖股票的最佳时机是使用dp思想,而本题是升级版,使用贪心思想。

首先要抓住本题特点:每一天都进行买卖不影响结果,即d - a = (d - c) + (c - b) + (b - a)

然后,每一天都进行最大经济效益的买卖,即将前一天的卖出再立即买入今天的(收益为当天与前一天的差价),或者前一天不买今天才来买入(今天收益0)。这样每一天的最大收益加起来就是总收益。典型的局部最优累加得到全局最优。

代码

class Solution {
    public int maxProfit(int[] prices) {
        if(prices.length<2) return 0;
        int num=0,profitNow;
        for(int i=1;i<prices.length;++i){
            if(prices[i]<=prices[i-1])
                profitNow = 0;
            else
                profitNow = prices[i] - prices[i-1];
            num+=profitNow;
        }
        return num;
    }
}

种花问题

LeetCode:种花问题

题目描述:

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

给定一个花坛(表示为一个数组包含0和1,其中0表示没种植花,1表示种植了花),和一个数 n 。能否在不打破种植规则的情况下种入 n 朵花?能则返回True,不能则返回False。

注意

  • 数组内已种好的花不会违反种植规则。
  • 输入的数组长度范围为 [1, 20000]。
  • n 是非负整数,且不会超过输入数组的大小。

示例:

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

思想:

  • 通过设置pre和next,来解决前后边界问题

这题我觉得不算严格的贪心问题,因为不满足无后效性。但是推导过程可以稍微借助贪心思想来考虑。假设A和B两个位置相邻,都是0,遍历到A时,种或者不种问题。如果A选择种花,势必会影响到后面,即B不能再种花。但是如果A不种花,为了弥补这一缺憾,B就必须要种(否则就少种了一株花),而这样的话又对B之后的位置产生影响。影响更深远。所以单纯对当前的A而言,选择种花,对后面的影响最小,也就是变相的局部最优。

这是我的理解。

代码

class Solution {
    public boolean canPlaceFlowers(int[] flowerbed, int n) {
        int len=flowerbed.length,pre,next;
        for(int i=0;i<len;++i){
            pre = i==0?0:flowerbed[i-1];
            next = (i+1)<len?flowerbed[i+1]:0;
            if(pre==0&&next==0&&flowerbed[i]==0){
                flowerbed[i] = 1;
                --n;
            }
        }
        return n<=0;
    }
}

判断子序列

LeetCode:判断子序列

题目描述:

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

你可以认为 s 和 t 中仅包含英文小写字母。字符串 t 可能会很长(长度 ~= 500,000),而 s 是个短字符串(长度 <=100)。

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

示例:

示例 1:
s = "abc", t = "ahbgdc"

返回 true.

示例 2:
s = "axc", t = "ahbgdc"

思想:

使用indexOf比使用charAt逐个比较要效率更高。

代码

class Solution {
    public boolean isSubsequence(String s, String t) {
        char[] arr = s.toCharArray();
        int index = -1;
        for(int i=0;i<s.length();++i){
            index = t.indexOf(arr[i],index+1);
            if(index<0)
                return false;
        }
        return true;
    }
}

非递减数列

LeetCode:非递减数列

题目描述:

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

我们是这样定义一个非递减数列的: 对于数组中所有的 i (1 <= i < n),总满足 array[i] <= array[i + 1]。

示例:

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

思想:

按顺序遍历,遇到当前位置比前面一位小的情况,有两种修改方式(假设遍历A、B、C,C小于B)

  • 当A<=C时,修改B=C,这样对后面影响最小;当B为首元素时,也是这样修改。
  • 存在一个特殊情况,当A>C时,只能修改C=B。

代码

class Solution {
    public boolean checkPossibility(int[] nums) {
        int cnt=0;
        for(int i=1;cnt<2&&i<nums.length;++i){
            if(nums[i]>=nums[i-1]) continue;
            cnt++;
            if(i>1&&nums[i-2]>nums[i]){
                nums[i] = nums[i-1];
            }else
                nums[i-1] = nums[i];
        }
        return cnt<2;
    }
}

划分字母区间

LeetCode:划分字母区间

题目描述:

字符串 S 由小写字母组成。我们要把这个字符串划分为尽可能多的片段,同一个字母只会出现在其中的一个片段。返回一个表示每个字符串片段的长度的列表。

注意:

S的长度在[1, 500]之间。
S只包含小写字母'a'到'z'。

示例:

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

思想:

没感觉这题有什么贪心思想。网上说,针对每一个区间,lastIndex能更新就更新,这属于贪心思想。感觉有点牵强。

代码

  • 笨方法,时间复杂度O(n^2)
class Solution {
    public List<Integer> partitionLabels(String S) {
        char[] strList= S.toCharArray();
        int lastIndex=0;
        ArrayList<Integer> list=new ArrayList<>();
        int lastAdd=0;
        for(int i=0;i<S.length();++i){
            lastIndex=Math.max(lastIndex,S.lastIndexOf(strList[i]));
            if(i==lastIndex){
                list.add(i+1-lastAdd);
                lastAdd=i+1;
            }   
        }
        return list;
    }
}
  • 正确方法。先遍历一次,把每一个字母的lastindex值记入一个数组,这样就不用循环里再套一层循环了。
class Solution {
    public List<Integer> partitionLabels(String S) {
        int[] charList = new int[26];
        for(int i=0;i<S.length();++i){
            charList[char2int(S.charAt(i))] = i;
        }
        List res = new LinkedList();
        int lastIndex=0,pos=0;
        for(int i=0;i<S.length();++i){
            lastIndex = Math.max(lastIndex, charList[char2int(S.charAt(i))]);
            if(i==lastIndex){
                res.add(lastIndex-pos+1);
                pos = lastIndex+1;
            }
        }
        return res;
    }
    private int char2int(char c){
        return c- 'a';
    }
}
原文地址:https://www.cnblogs.com/buptleida/p/12528221.html