Leecode刷题C语言之最近的房间

发布于:2024-12-18 ⋅ 阅读:(78) ⋅ 点赞:(0)

执行结果:通过

执行用时和内存消耗如下:

 

 

代码如下: 

#define FATHER_NODE(i)      (0 == (i) ? -1 : (i) - 1 >> 1)
#define LEFT_NODE(i)        (((i) << 1) + 1)
#define RIGHT_NODE(i)       (((i) << 1) + 2)
#define HIGH_INT(i)         ((i) >> 32)
#define LOW_INT(i)          ((i) & 0xFFFFFFFF)
#define MERGE_LONG(i, j)    ((long long)(i) << 32 | (long long)(j))
#define MAX_VAL(i, j)       ((i) > (j) ? (i) : (j))
#define MIN_VAL(i, j)       ((i) < (j) ? (i) : (j))

/* 优先队列(大根堆)。 */
typedef struct
{
    long long *arr;
    int arrSize;
}
PriorityQueue;

/* 二进制树。 */
typedef struct
{
    int *arr;
    int highestBit;
}
BinaryTree;

static void queuePush(PriorityQueue *queue, long long value);
static void queuePop(PriorityQueue *queue);
static int mapSearchSmallerEqual(int *arr, int arrSize, int value);
static int mapSearchBiggerEqual(int *arr, int arrSize, int value);
static int treeHighestBit(int value);
static void treePush(BinaryTree *tree, int value);
static int treeSmallerEqual(BinaryTree *tree, int value);
static int treeBiggerEqual(BinaryTree *tree, int value);

/* 主函数。 */
int *closestRoom(int **rooms, int roomsSize, int *roomsColSize, int **queries, int queriesSize, int *queriesColSize, int *returnSize)
{
    const int treeSize = roomsSize * 3;
    int i = 0, j = INT_MAX, k = 0, idMapSize = 0, minId = INT_MAX, maxId = INT_MIN;
    int arr1[treeSize], idMap[roomsSize];
    long long arr2[roomsSize], arr3[queriesSize];
    BinaryTree tree;
    PriorityQueue rQueue, qQueue;
    int *result = NULL;
    /* 初始化。 */
    memset(arr1, 0, sizeof(arr1));
    tree.arr = arr1;
    tree.highestBit = treeHighestBit(roomsSize - 1);
    rQueue.arr = arr2;
    rQueue.arrSize = 0;
    qQueue.arr = arr3;
    qQueue.arrSize = 0;
    result = (int *)malloc(sizeof(int) * queriesSize);
    if(NULL == result)
    {
        *returnSize = 0;
        return result;
    }
    /* 对rooms数组中所有的ID进行离散化。注意由于是借用的大根堆处理离散化,所以取相反数以达到结果从小到大取值的效果。 */
    for(i = 0; roomsSize > i; i++)
    {
        queuePush(&rQueue, -rooms[i][0]);
    }
    while(0 < rQueue.arrSize)
    {
        /* 这里j初始化为最大值,以表示上一个idMap值,以此保证idMap里面的数值无重复。注意idMap取相反数。 */
        if(j > rQueue.arr[0])
        {
            j = rQueue.arr[0];
            idMap[idMapSize] = -j;
            idMapSize++;
        }
        queuePop(&rQueue);
    }
    /* 然后把rooms数组的id、size组合,queries数组的minSize、下标组合,放入各自的大根堆。 */
    for(i = 0; roomsSize > i; i++)
    {
        queuePush(&rQueue, MERGE_LONG(rooms[i][1], rooms[i][0]));
    }
    for(i = 0; queriesSize > i; i++)
    {
        queuePush(&qQueue, MERGE_LONG(queries[i][1], i));
    }
    /* 然后给queries的堆进行逐个出堆检查。 */
    while(0 < qQueue.arrSize)
    {
        /* 取出当前要处理的queries的下标。 */
        i = LOW_INT(qQueue.arr[0]);
        /* 把所有当前面积大于等于queries[i].minSize的房间出堆。 */
        while(0 < rQueue.arrSize && queries[i][1] <= HIGH_INT(rQueue.arr[0]))
        {
            k = LOW_INT(rQueue.arr[0]);
            /* 这里存入树中的id,只存映射的离散值。同时更新最大id与最小id。 */
            j = mapSearchSmallerEqual(idMap, idMapSize, k);
            treePush(&tree, j);
            maxId = MAX_VAL(maxId, k);
            minId = MIN_VAL(minId, k);
            /* 出堆。 */
            queuePop(&rQueue);
        }
        /* 处理当前这一次查询的结果。如果当前的rQueue仍然满员,说明没有任何满足条件的房间被释放出来。 */
        if(roomsSize == rQueue.arrSize)
        {
            result[i] = -1;
        }
        /* 如果已有的最大id都小于等于当前preferId,则说明只能取这个最大id。 */
        else if(maxId <= queries[i][0])
        {
            result[i] = maxId;
        }
        /* 如果已有的最小id都大于等于当前preferId,则说明只能取这个最小id。 */
        else if(minId >= queries[i][0])
        {
            result[i] = minId;
        }
        /* 除上面几种情况以外,接下来都存在左右两个id,此时取较近的,如果两者一样近,取左边的。 */
        else
        {
            /* 由于是离散化处理的,所以这个preferId也先找小于等于自己的最接近的离散化值。 */
            j = mapSearchSmallerEqual(idMap, idMapSize, queries[i][0]);
            j = treeSmallerEqual(&tree, j);
            /* 同上,查找大于等于自己的数值时,也先离散化。 */
            k = mapSearchBiggerEqual(idMap, idMapSize, queries[i][0]);
            k = treeBiggerEqual(&tree, k);
            result[i] = (idMap[k] - queries[i][0] < queries[i][0] - idMap[j]) ? idMap[k] : idMap[j];
        }
        /* 出堆。 */
        queuePop(&qQueue);
    }
    /* 结果数组的长度等于queriesSize。 */
    *returnSize = queriesSize;
    return result;
}

/* 大根堆入队。 */
static void queuePush(PriorityQueue *queue, long long value)
{
    int son = queue->arrSize, father = FATHER_NODE(son);
    queue->arrSize++;
    while(-1 != father && value > queue->arr[father])
    {
        queue->arr[son] = queue->arr[father];
        son = father;
        father = FATHER_NODE(son);
    }
    queue->arr[son] = value;
    return;
}

/* 大根堆出队。 */
static void queuePop(PriorityQueue *queue)
{
    int father = 0, left = LEFT_NODE(father), right = RIGHT_NODE(father), son = 0;
    queue->arrSize--;
    while((queue->arrSize > left && queue->arr[queue->arrSize] < queue->arr[left])
        || (queue->arrSize > right && queue->arr[queue->arrSize] < queue->arr[right]))
    {
        son = (queue->arrSize > right && queue->arr[left] < queue->arr[right]) ? right : left;
        queue->arr[father] = queue->arr[son];
        father = son;
        left = LEFT_NODE(father);
        right = RIGHT_NODE(father);
    }
    queue->arr[father] = queue->arr[queue->arrSize];
    return;
}

/* 在一个由小到大且无重复的数组中,查找小于等于value的最大位置。题目保证能找到。 */
static int mapSearchSmallerEqual(int *arr, int arrSize, int value)
{
    int mid = 0, left = 0, right = arrSize - 1;
    while(left < right)
    {
        mid = left + right + 1 >> 1;
        if(value < arr[mid])
        {
            right = mid - 1;
        }
        else
        {
            left = mid;
        }
    }
    return left;
}

/* 在一个由小到大且无重复的数组中,查找大于等于value的最小位置。题目保证能找到。 */
static int mapSearchBiggerEqual(int *arr, int arrSize, int value)
{
    int mid = 0, left = 0, right = arrSize - 1;
    while(left < right)
    {
        mid = left + right >> 1;
        if(value > arr[mid])
        {
            left = mid + 1;
        }
        else
        {
            right = mid;
        }
    }
    return right;
}

/* 在二进制树中,可能出现的最大值需要占据的最高比特。如果是0,则标记为1。 */
static int treeHighestBit(int value)
{
    int result = 1;
    if(0 != value)
    {
        while(0 != value)
        {
            result++;
            value = value >> 1;
        }
        result = 1 << result - 2;
    }
    return result;
}

/* 将一个数值放到二进制树中。 */
static void treePush(BinaryTree *tree, int value)
{
    int i = 0, bit = tree->highestBit;
    while(0 != bit)
    {
        i = (bit & value) ? RIGHT_NODE(i) : LEFT_NODE(i);
        tree->arr[i]++;
        bit = bit >> 1;
    }
    return;
}

/* 查找树中小于等于value的最大值。题目保证其存在。 */
static int treeSmallerEqual(BinaryTree *tree, int value)
{
    int i = 0, bit = tree->highestBit, ri = -1, rbit = -1, result = 0;
    while(0 != bit)
    {
        if(bit & value)
        {
            if(0 != tree->arr[LEFT_NODE(i)])
            {
                ri = LEFT_NODE(i);
                rbit = bit;
            }
            i = RIGHT_NODE(i);
        }
        else
        {
            i = LEFT_NODE(i);
        }
        bit = bit >> 1;
    }
    /* 如果该数值本身存在于树中,则就是它本身。 */
    if(0 != tree->arr[i])
    {
        result = value;
    }
    /* 否则从最后一次分支的地方开始尽量找最大值。 */
    else
    {
        result = value & ~(rbit | rbit - 1);
        rbit = rbit >> 1;
        while(0 != rbit)
        {
            if(0 != tree->arr[RIGHT_NODE(ri)])
            {
                result |= rbit;
                ri = RIGHT_NODE(ri);
            }
            else
            {
                ri = LEFT_NODE(ri);
            }
            rbit = rbit >> 1;
        }
    }
    return result;
}

/* 查找树中大于等于value的最小值。题目保证其存在。 */
static int treeBiggerEqual(BinaryTree *tree, int value)
{
    int i = 0, bit = tree->highestBit, ri = -1, rbit = -1, result = 0;
    while(0 != bit)
    {
        if(bit & value)
        {
            i = RIGHT_NODE(i);
        }
        else
        {
            if(0 != tree->arr[RIGHT_NODE(i)])
            {
                ri = RIGHT_NODE(i);
                rbit = bit;
            }
            i = LEFT_NODE(i);
        }
        bit = bit >> 1;
    }
    /* 如果该数值本身存在于树中,则就是它本身。 */
    if(0 != tree->arr[i])
    {
        result = value;
    }
    /* 否则从最后一次分支的地方开始尽量找最小值。 */
    else
    {
        result = value & ~(rbit - 1) | rbit;
        rbit = rbit >> 1;
        while(0 != rbit)
        {
            if(0 != tree->arr[LEFT_NODE(ri)])
            {
                ri = LEFT_NODE(ri);
            }
            else
            {
                result |= rbit;
                ri = RIGHT_NODE(ri);
            }
            rbit = rbit >> 1;
        }
    }
    return result;
}

解题思路:

离线查询。
1.题目中,对于每一个查询queries[x],必须在面积大于等于queries[x][1]的房间之中,查找其中ID离自己的ID,即queries[x][0]最接近的。也就是说,所有面积小于queries[x][1]的房间,即便它的ID再怎么靠近queries[x][0],也是不被考虑的。那么,我们就需要按照面积从大到小的顺序查看每一个queries[x],然后同样的,从大到小的顺序过滤每一个面积大于等于当前queries[x][1]的房间。

2.这就是离线查询的思路,即,我们计算的实际顺序,和queries[][]数组本身的顺序不一致,是按照queries[x][1]从大到小的顺序计算的,每次遍历到queries[x]的时候,就将面积大于等于queries[x][1]的房间给释放出来。
 

优先队列(大根堆)。
我们将所有的房间(下标),和查询(下标)都存到各自的优先队列之中,然后,每当从查询优先队列的队顶pop一个查询的时候,就把房间优先队列中,面积大于等于该查询面积的房间pop出来。这样就达到了已经释放出来的所有房间,其面积一定都是大于等于当前的查询面积的。
 

二进制树(01字典树)。
1.我们将所有已经释放出来的房间的ID,都存放到一个数据结构中,在这个数据结构中,我们需要能够快速地确定,所有的这些ID,哪个离当前查询的ID最接近,如果有左右两侧两个数值都和查询ID一样近,则取左边的那个。
当我们需要往二进制树中存入一个数值的时候(本题中存入的是房间ID),我们从这个数值的最高比特开始,如果最高比特为1,则从根节点往右存储,当右节点为空的时候,新建右节点,如果最高比特为0,则从根节点往左存储,当左节点为空的时候,新建左节点。然后到第二高比特,类似的,如果该比特为1,则从当前节点往右存储,当右节点为空的时候,新建右节点,如果当前比特为0,则从当前节点往左存储,当左节点为空的时候,新建左节点。继续第三高比特,处理逻辑同上,依此类推,直到最低比特结束。下图中,就是我们存入2,4,6,8,9,11,13,14这几个数值之后的示意图。


2.这里,我们只计算了最低4位的比特。题目中ID的取值最大可以到10^7,即最多达到24比特。这里就不展开了,其处理逻辑是一样的。
上面讲了往二进制树中存入数值的处理方法,那么,我们怎么查找这棵树中,小于等于某个数值的最大值,和大于等于某个数值的最小值呢?也就是题目中要求的,最接近queries[x][0]的房间ID。
3.我们先看小于等于某个数值的最大值。还是以这个图为例,假如现在我们要在已经存入树中的2,4,6,8,9,11,13,14这几个数值中,查找小于等于10的最大值,凭我们肉眼查看,这个值应该就是9,那么我们怎么在二叉树中用代码来实现呢?

3.我们可以这样处理,把这个要比较的数值10,从最高位到最低位的顺序往下罗列,二进制是1010(b),看看从上到下的过程中,最后一次“1010(b)往右分支走的同时左分支不为空的分叉点”在哪里。听起来有点拗口是不是,没关系,我们在图中看,第二高位这一层,我们标记红色字母(a)的这个节点的左节点,就是我们要找的。在节点(a)处,1010(b)要往右,但同时左分支不为空,我们就把这个节点(a)的左分支记录下来。然后,这个左分支开始往下,能靠右尽量靠右找,右分支不存在的情况下再靠左,按照这个逻辑,找到的就是1001(b),即十进制的9。我这个图里只画了四层,如果有从上到下有好多比特,则记录最下面一次分叉的位置,也就是越晚分叉,其差值越小。
类似的,大于等于某个数值的最大值,其处理逻辑可以参考上面的过程,读者可以自行展开。

4.特别留意一下,上面所述的“大于等于某个数值的最小值”,和“小于等于某个数值的最大值”,都包含了“等于”。也就是说,如果查询的这个数值本身就在二进制树里面,那么就直接返回这个数值本身。

【代码】

时间复杂度,O(R*log(R) + Q*log(Q))。
空间复杂度,O(R+Q)。
其中,R表示房间数组的长度,Q表示查询数组的长度。


网站公告

今日签到

点亮在社区的每一天
去签到