刷题中遇到的贪心算法

去除重复字母

给你一个字符串 s ,请你去除字符串中重复的字母,使得每个字母只出现一次。需保证 返回结果的字典序最小(要求不能打乱其他字符的相对位置)

输入:s = "bcabc"
输出:"abc"

贪心加单调栈

  1. 题目要求字典排序, 则结果中第一个字母的字典序靠前的优先级最高—贪心
  2. 因为不能打乱相对位置—即输入中排在输出第一个字母位置前的字母都不能出现,所以要在保证每个字母至少出现一次的前提下再考虑字典序排列
  3. 根据第1点可以考虑使用单调栈来保证字典序排列,根据第2点我们给元素出栈时加上限制条件,只有在栈顶元素字典序靠后,且在之后还有出现次数才弹出栈.同时压栈时应该注意栈中没有出现过该元素才能压栈.

如果遇到了后边没有出现次数但是较大的,这就相当于本个较大的把前边的都固定住了:
cbeabcc序列:
beac其中字母e就相当于把其他的内容都固定住了,这样可以保证出现在前边的字母都是比它小的,字典序最小。
这种方式保证了在前边的元素字典序尽可能地小,只有退无可退的时候才进行固定,相当于找到了他可以拥有的最靠后的位置,通过单调栈筛选出具有某些特性但仍需要保持顺序的串(或者满足某些特性)

   public static String removeDuplicateLetters(String s) {
        if(s.length()==0){
            return s;
        }
        int[][] dp=new int[26][s.length()];
        boolean[]exists = new boolean[26];
        //需要注意的是,记得要使用记录数组,记录栈中有什么元素
        //这个dp的意思可以理解为包含本次的情况下,后边还有多少个同字符的内容
        Character tem3=s.charAt(s.length()-1);
        dp[tem3-'a'][s.length()-1]=1;
        for(int i=s.length()-2;i>=0;i--){

            for(int j='a'-'a';j<='z'-'a';j++){
                dp[j][i]=dp[j][i+1];
            }
            //对于记忆数组进行初始化
            Character tem2=s.charAt(i);
            dp[tem2-'a'][i]=dp[tem2-'a'][i]+1;
        }
        Stack<Character> stack=new Stack<>();
        for(int i=0;i<s.length();i++){

            Character tem=s.charAt(i);
            if(exists[tem-'a']){
                continue;
            }
            while(!stack.empty()&&stack.peek()-'a'>tem-'a'&&dp[stack.peek()-'a'][i]>=1){
                exists[stack.peek() - 'a'] = false;
                stack.pop();

            }
                stack.push(tem);
                exists[tem - 'a'] = true;

        }
        Iterator<Character> it=stack.iterator();
        String fin=new String();
        while(it.hasNext()){
            fin=fin+it.next();
        }
        return fin;
    }

删除被覆盖区间

给你一个区间列表,请你删除列表中被其他区间所覆盖的区间。

只有当 c <= a 且 b <= d 时,我们才认为区间 [a,b) 被区间 [c,d) 覆盖。

在完成所有删除操作后,请你返回列表中剩余区间的数目。

输入:intervals = [[1,4],[3,6],[2,8]]
输出:2
解释:区间 [3,6] 被区间 [2,8] 覆盖,所以它被删除了。

这个好办,每次都找左边不能被覆盖的那个区间就行了(退无可退

    public static int removeCoveredIntervals(int[][] intervals) {
        Arrays.sort(intervals, new Comparator<int[]>() {
            @Override
            public int compare(int[] t1, int[] t2) {
                if(t1[0]>t2[0]){
                    return 1;
                }else if(t1[0]<t2[0]) {
                    return -1;
                }else{
                    if(t1[1]>t2[1]){
                        return -1;
                    }else if(t1[1]<t2[1]){
                        return 1;
                    }
                }
                return 0;
            }
        });
        int start=intervals[0][0];
        int end=intervals[0][1];
        int fina=0;
        for(int i=1;i<intervals.length;i++){
            if(intervals[i][0]<=end){
                if(intervals[i][1]<=end){
                    fina=fina+1;//两头被包围了,减去
                }else{
                    start=intervals[i][0];
                    end=intervals[i][1];
                }
            }else{
                //互相不影响,更迭
                start=intervals[i][0];
                end=intervals[i][1];
            }
        }
        return(intervals.length-fina);
    }

划分数组为连续数字的集合

给你一个整数数组 nums 和一个正整数 k,请你判断是否可以把这个数组划分成一些由 k 个连续数字组成的集合。
如果可以,请返回 True;否则,返回 False。

输入:nums = [1,2,3,3,4,4,5,6], k = 4
输出:true
解释:数组可以分成 [1,2,3,4] 和 [3,4,5,6]。

超时解法通过了42/45个案例
思路:先排序,设置一个栈,每使用一个数字就弹出一个,如果有和本数字重复的数字就弹出,存入cache中
本题是O(MN)类的问题,进行操作的时候先考虑M,N哪个套在外边,这是本位问题
首先思考失败的情况

  • 栈弹着弹着发现不连续了。
  • 弹着弹着,发现没有数字了,栈空了。
        public static boolean isPossibleDivide(int[] nums, int k) {
        if(nums.length%k!=0){
            return false;
        }
        Arrays.sort(nums);
        Stack<Integer> stack=new Stack<>();
        Stack<Integer> cache=new Stack<>();
        for(int i=nums.length-1;i>=0;i--){
            stack.push(nums[i]);
        }
        //想的这么复杂的逻辑一看就是进入误区了,直接两个队列就解决的问题,
        int count=0;
        int mem=0;
        for(int i=0;i<nums.length;i++){
            if(count==0){
                mem=stack.peek()+1;
                stack.pop();
                count=count+1;
            }else if(count==k){
                while(!cache.empty()){
                    int tem=cache.peek();
                    cache.pop();
                    stack.push(tem);
                }
                if(!stack.empty()){

                    mem=stack.peek()+1;
                    count=1;
                    stack.pop();
                }
            }else{
                while(!stack.empty()&&stack.peek()!=mem) {
                    if (stack.peek() > mem) {
                        return false;
                    }
                    int tem = stack.pop();
                    cache.push(tem);
                }
                if(stack.empty()){
                    return false;
                }
                stack.pop();
                mem=mem+1;
                count=count+1;
            }
        }
        return true;
    }

简化方法:利用java优先队列容器的移除特性
注意:这个remove方法如果传入的是实数的话,移除的是index
如果传入的是Integer类型的话(Object)移除的是对应的元素:

Integer it=new Integer(10);

对于元素的移除,会返回boolean类型的是否移除成功。

import java.util.PriorityQueue;

public class Solution {

    public boolean isPossibleDivide(int[] nums, int k) {
        int len = nums.length;
        if (len % k != 0) {
            return false;
        }

        PriorityQueue<Integer> minHeap = new PriorityQueue<>(len);
        for (int num : nums) {
            minHeap.offer(num);
        }

        while (!minHeap.isEmpty()) {
            Integer top = minHeap.poll();

            for (int i = 1; i < k; i++) {
                // 从 1 开始,正好需要移除 k - 1 个元素
                // i 正好就是相对于 top 的偏移
                // 注意:这个 remove 操作会线性去扫 top + i 的索引,时间复杂度是 O(N)
                if (!minHeap.remove(top + i)) {
                    // 如果移除失败,说明划分不存在,直接返回 false 即可
                    return false;
                }
            }
        }
        return true;
    }
}

进一步优化,离散化,利用堆的解法

   public static boolean isPossibleDivide(int[] nums, int k) {
        TreeMap<Integer,Integer> map=new TreeMap<>();
        for(int num:nums){
            if(map.containsKey(num)){
                int tem=map.get(num);
                map.put(num,tem+1);
            }else{
                map.put(num,1);
            }
        }
        while(!map.isEmpty()){
            int tem=map.firstKey();
            int count2=map.get(tem);
            if (count2 == 1) {
                map.remove(tem);
            } else {
                map.put(tem , count2 - 1);
            }
            for(int i=1;i<k;i++){
                if(map.containsKey(tem+i)){
                    int count=map.get(tem+i);
                    if (count == 1) {
                        map.remove(tem + i);
                    } else {
                        map.put(tem + i, count - 1);
                    }
                }else{
                    return false;
                }
            }
        }
        return true;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值