算法日记day 31(贪心之根据身高重建队列|最少数量的箭引爆气球|无重复区间)

一、根据身高重建队列

题目:

假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高大于或等于 hi 的人。

请你重新构造并返回输入数组 people 所表示的队列。返回的队列应该格式化为数组 queue ,其中 queue[j] = [hj, kj] 是队列中第 j 个人的属性(queue[0] 是排在队列前面的人)。

示例 1:

输入:people = [[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]]
输出:[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]
解释:
编号为 0 的人身高为 5 ,没有身高更高或者相同的人排在他前面。
编号为 1 的人身高为 7 ,没有身高更高或者相同的人排在他前面。
编号为 2 的人身高为 5 ,有 2 个身高更高或者相同的人排在他前面,即编号为 0 和 1 的人。
编号为 3 的人身高为 6 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
编号为 4 的人身高为 4 ,有 4 个身高更高或者相同的人排在他前面,即编号为 0、1、2、3 的人。
编号为 5 的人身高为 7 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
因此 [[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]] 是重新构造后的队列。

示例 2:

输入:people = [[6,0],[5,0],[4,0],[3,2],[2,2],[1,4]]
输出:[[4,0],[5,0],[2,2],[3,2],[1,4],[6,0]]

思路:

首先应对一个属性进行排序,我们先对身高进行从大到小排序,后得到一个新的数组,这时再看k,在身高相同的情况下,我们应对k降序排序,让值小的在数组前,这样才能满足题意,如(7,0)在(7,1)之前,身高h不同的情况下,按照k相对应的位置进行插入 

以示例一为例:

people = [[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]]

第一次排序按照身高h降序排序,身高相同k不同按照降序排序

[[7,0][7,1][6,1][5,0][5,2][4,4]]

第二次排序再次遍历数组,在h不同的下情况下按照k大小排到相应位置

[7,0]

[7,0][7,1]

[7,0][6,1][7,1]    [6,1]与[7,1]h不同,k的值为1,因此排在数组下标为1的位置

[5,0][7,0][6,1][7,1]   [5,0]k值为0,因此排在数组下标为0的位置

[5,0][7,0][5,2][6,1][7,1]    同理,[5,2]h相同k越小排在前,k=2,因此排在当前数组下标为2的地方

.......

依次排序

[5,0][7,0][5,2][6,1][4,4][7,1] 

代码:

public int[][] reconstructQueue(int[][] people) {
    // 身高从大到小排(身高相同k小的站前面)
    Arrays.sort(people, (a, b) -> {
        if (a[0] == b[0]) return a[1] - b[1]; // 如果身高相同,按照 k 的升序排列
        return b[0] - a[0]; // 否则按照身高的降序排列
    });

    LinkedList<int[]> que = new LinkedList<>();

    // 根据排好序的数组插入到队列中
    for (int[] p : people) {
        que.add(p[1], p); // 在索引 p[1] 处插入元素 p
    }

    // 将队列转换成数组并返回
    return que.toArray(new int[people.length][]);
}
  1. 排序规则说明

    • Arrays.sort(people, (a, b) -> {...}) 使用了一个 lambda 表达式作为比较器,对 people 数组进行排序。
    • (a, b) -> {...} 定义了比较器,其中 a 和 b 是两个人的信息数组。如果 a[0](身高)与 b[0] 相等,则按照 a[1](k 值)升序排列;否则按照 a[0](身高)降序排列。
  2. 队列操作

    • LinkedList<int[]> que = new LinkedList<>(); 创建一个 LinkedList 用于存储排好序的人员信息。
    • que.add(p[1], p); 根据每个人的 p[1](k 值,即应该在队列中的位置)将其插入到队列中。
  3. 返回结果

    • que.toArray(new int[people.length][]) 将 LinkedList 转换为二维数组并返回。

Lambda表达式的基本语法

Lambda表达式的基本语法如下:

(parameters) -> expression

或者对于多条语句的情况:

(parameters) -> {
    // 多条语句
    return expression;
}

其中:

  • parameters 是参数列表,可以为空或者非空,如果只有一个参数,括号可以省略。
  • -> 是lambda操作符,将参数列表与lambda主体分开。
  • expression 是lambda表达式的执行体,或者是用花括号包裹的多条语句。

Lambda表达式的特点

  1. 匿名性: Lambda表达式本身没有名称,它是匿名函数的一种实现方式。
  2. 简洁性: 可以通过Lambda表达式来简化代码,尤其是对于单一方法接口(Single Abstract Method,SAM)的实现。
  3. 函数式编程支持: 支持函数作为参数传递、函数作为返回值等函数式编程的特性。

 

示例用法

  1. 简单示例

    // 无参数,无返回值的Lambda表达式
    Runnable runnable = () -> System.out.println("Hello, Lambda!");
    runnable.run(); // 输出: Hello, Lambda!
    
  2. 函数式接口

    // 使用函数式接口作为Lambda表达式的目标类型
    List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Dave");
    names.forEach(name -> System.out.println("Hello, " + name));
    
  3. Comparator排序

    // 使用Lambda表达式实现Comparator接口
    List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Dave");
    names.sort((s1, s2) -> s1.compareTo(s2));
    System.out.println(names); // 输出: [Alice, Bob, Charlie, Dave]
    
  4. 线程处理

    // 使用Lambda表达式创建线程
    Thread thread = new Thread(() -> {
        System.out.println("Thread is running...");
    });
    thread.start();
    

注意事项

  • Lambda表达式的参数类型可以省略,由编译器根据上下文推断出来。
  • 如果Lambda表达式体只有一条语句,那么可以省略花括号 {} 和 return 关键字。
  • Lambda表达式的参数列表、操作符 -> 和主体部分都是固定的语法结构。

二、最少数量的箭引爆气球 

题目:

有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points ,其中points[i] = [xstart, xend] 表示水平直径在 xstart 和 xend之间的气球。你不知道气球的确切 y 坐标。

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

给你一个数组 points ,返回引爆所有气球所必须射出的 最小 弓箭数 

 

示例 1:

输入:points = [[10,16],[2,8],[1,6],[7,12]]
输出:2
解释:气球可以用2支箭来爆破:
-在x = 6处射出箭,击破气球[2,8]和[1,6]。
-在x = 11处发射箭,击破气球[10,16]和[7,12]。

示例 2:

输入:points = [[1,2],[3,4],[5,6],[7,8]]
输出:4
解释:每个气球需要射出一支箭,总共需要4支箭。

示例 3:

输入:points = [[1,2],[2,3],[3,4],[4,5]]
输出:2
解释:气球可以用2支箭来爆破:
- 在x = 2处发射箭,击破气球[1,2]和[2,3]。
- 在x = 4处射出箭,击破气球[3,4]和[4,5]。

思路:

如何使用最少的箭,当前一个气球的右边界小于下一个气球的左边界时,说明两气球没有重合区,因此必须多使用一只箭,在有重合区的情况下,可以将区间更新为俩只箭在右区间重合的最小值

代码:

public int findMinArrowShots(int[][] points) {
    // 如果气球数组为空,直接返回0
    if (points.length == 0)
        return 0;

    // 按照气球的起始位置对数组进行升序排序
    Arrays.sort(points, (a, b) -> Integer.compare(a[0], b[0]));

    // 初始化箭的计数器为1,因为至少需要一支箭
    int count = 1;

    // 遍历排序后的气球数组
    for (int i = 1; i < points.length; i++) {
        // 如果当前气球的起始位置大于前一个气球的结束位置,需要增加一支箭
        if (points[i][0] > points[i - 1][1]) {
            count++;
        } else {
            // 如果有重叠,更新当前气球的结束位置为当前气球和前一个气球的结束位置的最小值
            points[i][1] = Math.min(points[i][1], points[i - 1][1]);
        }
    }

    // 返回射爆所有气球所需的最小箭数
    return count;
}

 

三、无重复区间

题目:

给定一个区间的集合 intervals ,其中 intervals[i] = [starti, endi] 。返回 需要移除区间的最小数量,使剩余区间互不重叠 

示例 1:

输入: intervals = [[1,2],[2,3],[3,4],[1,3]]
输出: 1
解释: 移除 [1,3] 后,剩下的区间没有重叠。

示例 2:

输入: intervals = [ [1,2], [1,2], [1,2] ]
输出: 2
解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。

示例 3:

输入: intervals = [ [1,2], [2,3] ]
输出: 0
解释: 你不需要移除任何区间,因为它们已经是无重叠的了。

思路:

与引爆气球类似,当前一个的右边界小于当前区间的左边界时,说明两区间重复,计数加一,同时更新右区间为最小的那个(代替删除操作)

 

代码:

public int eraseOverlapIntervals(int[][] intervals) {
    // 如果区间集合为空,直接返回0,因为不需要移除任何区间
    if (intervals.length == 0)
        return 0;

    // 按照区间起始位置对区间集合进行升序排序
    Arrays.sort(intervals, (a, b) -> {
        return Integer.compare(a[0], b[0]);
    });

    // 记录需要移除的区间数量
    int count = 0;

    // 遍历排序后的区间集合
    for (int i = 1; i < intervals.length; i++) {
        // 如果当前区间的起始位置小于前一个区间的结束位置,表示有重叠
        if (intervals[i][0] < intervals[i - 1][1]) {
            // 需要移除当前区间,因此计数器加1
            count++;
            // 更新当前区间的结束位置为当前区间和前一个区间的结束位置的最小值
            intervals[i][1] = Math.min(intervals[i][1], intervals[i - 1][1]);
        }
    }

    // 返回需要移除的区间数量
    return count;
}
  • 定义了一个名为 eraseOverlapIntervals 的公共方法,接收一个二维整数数组 intervals 作为参数,表示区间集合。
  • 如果区间集合 intervals 的长度为0,即没有区间存在,直接返回0,因为不需要移除任何区间。
  • 使用 Arrays.sort() 方法,按照区间的起始位置 a[0] 对 intervals 数组进行升序排序。
  • 初始化一个计数器 count,用于记录需要移除的重叠区间的数量。
  • 使用 for 循环遍历排序后的区间集合 intervals
    • 判断当前区间 intervals[i] 的起始位置是否小于前一个区间 intervals[i - 1] 的结束位置,即判断是否有重叠。
    • 如果有重叠,将计数器 count 加1,表示需要移除一个区间。
    • 更新当前区间 intervals[i] 的结束位置为当前区间和前一个区间的结束位置的最小值,确保移除最小化重叠。
  • 返回计数器 count,即需要移除的重叠区间的数量。

相关资料

https://www.programmercarl.com/0435.%E6%97%A0%E9%87%8D%E5%8F%A0%E5%8C%BA%E9%97%B4.html#%E6%80%9D%E8%B7%AF

今天的学习就到这里

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值