round1/day11补/贪心4

1. 关于贪心

还是感觉没有定论,甚至不觉得自己贪心

2. 例题

lc406 根据身高重建队列

前置知识点

  1. 链表
    利用add(index, element) 方法,允许在链表中的指定位置插入元素
  2. sort排序
    • 利用sort(数据源,排序方法即lambda (参数1,参数2)->{表达式})
    • 关于升降序:a-b升序,b-a降序
  3. 二维数组
    • 遍历二维数组中的每一个数组for(p : people)。第一行取值是p[0],第二列取值是p[1]
    • 将链表转为数组queue.toArray(new int[people.length][ ])
      • 因为链表中的每个元素都对应一个一维数组,而链表的大小就是元素的个数,所以第二维的长度会与链表的大小一致,无需额外指定

思路

我的思路:排序之后不断互换
Step 1: 对people进行排序,按身高降序,如果身高相同,按列的排序位置升序排序
Step 2: 从矮到高遍历,进行互换

   people.sort(key=lambda x: (-x[0], x[1]))
   for i in range(len(people)):
       j = i
       while j > 0 and people[j][1] < j:
           //交换当前人和前面的人
           people[j], people[j - 1] = people[j - 1], people[j]
           j -= 1
   return people

精简思路:插入新的队列,而不是在原队列上互换
Step 1: 排序,从高到矮,顺序则从前到后
Step 2: 将排序后的数组从第一个点cur开始插入queue,插入给定的位置cur[1]即可

易错点

到处都是

代码实现

class Solution {
    public int[][] reconstructQueue(int[][] people) {
       //先将peope排序
       Arrays.sort(people, (a,b)->{ //a,b这里是待比较的两个一维数组
        if(a[0]==b[0])
            return a[1]-b[1]; //step2,若身高相同,则按顺序[1]升序排序
        return b[0]-a[0]; //step1,按身高[0]降序排序
       });

        //将people中的值从前往后遍历,不断插入queue中
        LinkedList<int[]> queue = new LinkedList<>(); //创建链表,每个节点都是一维数组
        for(int[] p:people){ //遍历people中的每个一维数组
            queue.add(p[1],p); //利用了链表的功能.add(indexLoc, val),在指定位置插入元素,而不用费劲去找到底位置在哪里才是正确的了
        }

        return queue.toArray(new int[people.length][]); //不要忘记返回值改成数组
    }
}

lc435 无重叠区间

思路

  • 区间去重问题
  • 所有需要排序的问题,仔细思考要对谁进行排序
    • ∵ 删除最小=区间间挨得距离最近=每一个区间越小越好–>终止值越小越好
    • ∴ 重点(接下来是否删除的重点也在终止值)。即让终止值小的区间先排在前面

易错点

  • 我的问题:排序时先考虑起始值再考虑终止值

代码实现

class Solution {
    public int eraseOverlapIntervals(int[][] intervals) {
       //优先按照终止值来升序排序。这里没有设置起始值:因为如果终止值相同,默认起始值也是升序排序
       Arrays.sort(intervals, (a,b)->{
        return a[1]-b[1];  
       });

       int remove =0;
       int prev_start = intervals[0][0];
       int prev_end = intervals[0][1];

       for(int i=1;i<intervals.length;i++){
           int[] cur = intervals[i];
            int cur_start = cur[0];
            int cur_end = cur[1];

            //如果当前起始值在上一个值的区间里,则舍弃它(因为它的终止值比上一个更大,所以删掉不可惜),接下来继续找和上一个小区间(小终止值)挨得最近的那个区间
            if(cur_start<prev_end){
                remove++;//相当于不把当前数组加入数组里,所以不更新prev_start和prev_end
            }else{
                prev_start=cur_start;
                prev_end=cur_end;
            }
       }
       return remove;

    }
}

lc763 划分字母区间

思路

  • 一共26个字母,给每个字母计数 每次都遍历一个字母,找到它的最后一位——这个思路是对的
  • 在找到每个字母的最远区间后,怎么利用最远的那个字母来切割?——更新游标boundary,如果下一个字母的boundary比现在大就更新,直到i=boundary为止,进行切割,然后重置boundary

代码实现

class Solution {
    public List<Integer> partitionLabels(String s) {
        LinkedList<Integer> cut = new LinkedList<>();
        
        int[] alphabet = new int[26];

        //这里要把s转为字符数组,方便引用检索
        char[] str = s.toCharArray();

        //记录每个字母的最后一位位置
        for(int i=0;i<str.length;i++){
            alphabet[str[i]-'a'] = i;
        }
        
        //遍历str,不断更新boundary直到真的可以切割,把切割记录进cut
        int boundary=0;
        int prevCutloc=-1;
        for(int i=0;i<str.length;i++){
            if(alphabet[str[i]-'a']>boundary){
                boundary = alphabet[str[i]-'a'];
            }
            if(boundary==i){
                cut.add(i-prevCutloc); 
                prevCutloc=i; //更新上一刀的位置
            }
        }
        return cut;
    }
}

lc56 合并区间

思路

去重题,优先考虑排序的顺序问题,从前往后还是从后往前?
与435互为镜像
检测重叠:如果curStart<preEnd则确定重叠

易错点

  1. 根据[0]还是[1]排序?做这种去重题,首先要重新排序让数组规整,但是如果目的是覆盖区间,则一定要从起始值最小开始排。和435删除重叠区间相反,那个要确保终止值最小,所以要以终止值为基准排
  2. (在检测重合后)更新cur的值时:起始值确定(已经按照从大到小排序了,只需要取前一个值确定更小),那终止值取prev还是cur?需要比大小

代码实现

class Solution {
    public int[][] merge(int[][] intervals) {
        //易错点1:如果目的是覆盖区间,则一定要从起始值最小开始排。和435删除重叠区间相反,那个要确保终止值最小,所以要以终止值为基准排
        Arrays.sort(intervals, (a,b) ->{ 
            return a[0]-b[0];
            });

        int[] prev = intervals[0];
        LinkedList<int[]> result = new LinkedList<>();
        result.add(prev);

        for(int i=1;i<intervals.length;i++){
            int[] cur=intervals[i];
            if(cur[0]>prev[1]){//判定不重叠
                result.add(cur);
            }else{
                cur[0]=prev[0];
                cur[1]=Math.max(cur[1],prev[1]); //易错点2:注意这个不能省略,因为可能出现第二个虽然起始值大,但是整个数组包含于第一个数组区间的情况
                result.removeLast(); //删除上一个重叠的prev
                result.add(cur);
            }
            prev=cur; //prev前进到当前位置
        }
        return result.toArray(new int[result.size()][]); 
    }
}

lc738 单调递增数字

前置知识

string,int, char[]之间的转换

  1. 数字变为字符数组char[]:Integer.toString(n).toCharArray()
  2. 数字变为数字数组int[]:
    • 先变为字符数组:char[] charArray = Integer.toString(n).toCharArray()
    • 然后把字符变为数字:for (int i=0; i<=charArray.length; i++) {int[] intArray[i] =Character.getNumericValue(charArray[i]);}
  3. 整数数组int[]变成数字:for(int number : numbers {result=result*10+number;}
  4. 字符数组char[]变成字符串变成数字:str= new String(char) 再 integer.parseInt(str) 或者 Integer.parseInt(String.valueOf(digits))

思路

我的思路
1.先把数字转为数组:char[] digits = Integer.toString(n).toCharArray();
2.从前向后遍历若cur<next,则cur-1,next之后都是9,每次类推 -->这样有个问题,碰到332,就需要退两格修改digits[i-2]和digits[i-1],这样会很麻烦

优化:从后向前遍历:标记不符合条件的情况cur<next,每次标记开始change的位置next,并且把当前值cur–
如:标记最后一位,倒数第二位都不符合要求,从倒数第二位开始改9. 332–32[2]–2[2][2]–>299

易错点

在修改digit[i]赋值为9时,注意应赋值字符‘9’而不是数字

代码实现

class Solution {
    public int monotoneIncreasingDigits(int n) {
        //精简1:转为字符串后直接转为char[]
        char[] digits = Integer.toString(n).toCharArray();

        int changeIndex =digits.length; //预设不需要修改9
        
        //从后向前遍历
        for(int i=digits.length-2;i>=0;i--){ //从倒数第二位开始,同时排除了如果i-2<0的情况
            if(digits[i]>digits[i+1]){
                digits[i]--;
                changeIndex=i+1; //*重点:每次标记开始change的位置next,并且把当前值cur--
            }
        }
        //从changeIndex开始全部改成9
        for(int j=changeIndex;j<digits.length;j++){
            digits[j]='9'; //易错点:这里是字符而不是数字!这是一个char。如果转为int[]就不用管
        }

        //最后变成数字
        int result = Integer.parseInt(new String(digits));
        return result;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值