这是一道经典的离线查询(Offline Query)结合有序集合(TreeSet)的题目。

核心思路

这道题的难点在于,如果我们对每个查询都遍历一遍所有房间,时间复杂度会达到 O(Q times N),在数据量大时会超时。

优化策略:
1.  排序(离线处理):
    *   我们将房间按面积从大到小排序。
    *   我们将查询也按最小面积需求从大到小排序。
    *   为什么这样做? 这样我们可以维护一个“候选房间集合”。当我们处理下一个查询时,因为它的面积要求变小了(或相等),之前满足条件的房间依然满足,我们只需要往集合里新增一些房间,而不需要重新筛选。

2.  有序集合(TreeSet):
    *   我们需要快速找到与 preferred 最接近的房间号。
    *   TreeSet 可以自动对房间号排序,并支持 floor(找小于等于某值的最大值)和 ceiling(找大于等于某值的最小值)操作,这两个操作的时间复杂度是 O(log N)。

3.  双指针:
    *   用一个指针遍历排序后的房间数组,将满足当前查询面积要求的房间号加入 TreeSet。

Java 代码实现

import java.util.Arrays;
import java.util.TreeSet;

class Solution {
    public int[] closestRoom(int[][] rooms, int[][] queries) {
        int n = rooms.length;
        int q = queries.length;
        
        // 1. 对房间按面积降序排序
        // 如果面积相同,房间号顺序无所谓
        Arrays.sort(rooms, (a, b) -> b[1] - a[1]);
        
        // 2. 处理查询
        // 我们需要记录查询的原始索引,因为最后要按原顺序返回结果
        // indexedQueries[i] = {原始索引, 偏好房间号, 最小面积}
        int[][] indexedQueries = new int[q][3];
        for (int i = 0; i  b[2] - a[2]);
        
        int[] res = new int[q];
        Arrays.fill(res, -1); // 默认结果为 -1
        
        TreeSet validRoomIds = new TreeSet();
        int roomPtr = 0; // 指向 rooms 数组的指针
        
        // 3. 遍历每一个查询(按面积从大到小)
        for (int[] query : indexedQueries) {
            int originalIndex = query[0];
            int preferred = query[1];
            int minSize = query[2];
            
            // 将所有满足面积要求的房间加入 TreeSet
            // 因为 rooms 和 queries 都是降序的,所以之前的房间肯定也满足当前要求
            while (roomPtr = minSize) {
                validRoomIds.add(rooms[roomPtr][0]);
                roomPtr++;
            }
            
            // 如果没有满足面积要求的房间,结果保持 -1
            if (validRoomIds.isEmpty()) {
                continue;
            }
            
            // 4. 在 TreeSet 中查找最接近 preferred 的房间号
            // ceiling: 大于等于 preferred 的最小值 (右边的候选)
            Integer right = validRoomIds.ceiling(preferred);
            // floor: 小于等于 preferred 的最大值 (左边的候选)
            Integer left = validRoomIds.floor(preferred);
            
            int closestId = -1;
            int minDiff = Integer.MAX_VALUE;
            
            // 检查右边的候选
            if (right != null) {
                closestId = right;
                minDiff = right - preferred;
            }
            
            // 检查左边的候选
            if (left != null) {
                int diff = preferred - left;
                // 如果左边更近,或者距离相等但左边的房间号更小(题目要求)
                if (diff < minDiff || (diff == minDiff && left < closestId)) {
                    closestId = left;
                }
            }
            
            res[originalIndex] = closestId;
        }
        
        return res;
    }
}

关键点解析

1.  为什么要记录 originalIndex?
    *   因为我们对 queries 进行了排序,打乱了原来的顺序。但是题目要求返回的数组顺序必须和输入的 queries 顺序一致。所以我们在排序前把原始下标存起来,算出结果后填回正确的位置。

2.  TreeSet 的作用
    *   它就像一个自动排序的列表。
    *   ceiling(preferred) 帮我们找到了“右侧最近”的房间。
    *   floor(preferred) 帮我们找到了“左侧最近”的房间。
    *   我们只需要比较这两个房间哪个更近即可。

3.  时间复杂度
    *   排序房间:O(N log N)
    *   排序查询:O(Q log Q)
    *   处理查询:每个房间最多被加入 TreeSet 一次(O(N log N)),每次查询进行常数次 TreeSet 操作(O(log N))。
    *   总复杂度:O((N + Q) log N),非常高效。

 

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐