zoukankan      html  css  js  c++  java
  • 【数据结构与算法】贪心思想经典题总结[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';
        }
    }
    
  • 相关阅读:
    Centos7 下 PHP 添加缺少的组件 sockets 和 openssl
    vue使用 video.js动态切换视频源视频源不刷新问题
    vue+js清除定时器
    获取ip地址,并根据ip获取当前省份
    html页面引用video.js播放m3u8格式视频
    uniapp每隔几秒执行一下网络请求(h5端亲测可以,其他端未测试)
    关于uniapp获取当前距离屏幕顶部的距离
    按值传递与按值引用详解(java版)
    JavaScript的深入理解(1)
    vue-cli3配置webpack-bundle-analyzer插件
  • 原文地址:https://www.cnblogs.com/buptleida/p/12528221.html
Copyright © 2011-2022 走看看