力扣刷题之2940.找到Alice和Bob可能相遇的建筑

题干描述

给你一个下标从 0 开始的正整数数组 heights ,其中 heights[i] 表示第 i 栋建筑的高度。

如果一个人在建筑 i ,且存在 i < j 的建筑 j 满足 heights[i] < heights[j] ,那么这个人可以移动到建筑 j 。

给你另外一个数组 queries ,其中 queries[i] = [ai, bi] 。第 i 个查询中,Alice 在建筑 ai ,Bob 在建筑 bi 。

请你能返回一个数组 ans ,其中 ans[i] 是第 i 个查询中,Alice 和 Bob 可以相遇的 最左边的建筑 。如果对于查询 i ,Alice  Bob 不能相遇,令 ans[i] 为 -1 。

示例 1:

输入:heights = [6,4,8,5,2,7], queries = [[0,1],[0,3],[2,4],[3,4],[2,2]]
输出:[2,5,-1,5,2]
解释:第一个查询中,Alice 和 Bob 可以移动到建筑 2 ,因为 heights[0] < heights[2] 且 heights[1] < heights[2] 。
第二个查询中,Alice 和 Bob 可以移动到建筑 5 ,因为 heights[0] < heights[5] 且 heights[3] < heights[5] 。
第三个查询中,Alice 无法与 Bob 相遇,因为 Alice 不能移动到任何其他建筑。
第四个查询中,Alice 和 Bob 可以移动到建筑 5 ,因为 heights[3] < heights[5] 且 heights[4] < heights[5] 。
第五个查询中,Alice 和 Bob 已经在同一栋建筑中。
对于 ans[i] != -1 ,ans[i] 是 Alice 和 Bob 可以相遇的建筑中最左边建筑的下标。
对于 ans[i] == -1 ,不存在 Alice 和 Bob 可以相遇的建筑。

示例 2:

输入:heights = [5,3,8,2,6,1,4,6], queries = [[0,7],[3,5],[5,2],[3,0],[1,6]]
输出:[7,6,-1,4,6]
解释:第一个查询中,Alice 可以直接移动到 Bob 的建筑,因为 heights[0] < heights[7] 。
第二个查询中,Alice 和 Bob 可以移动到建筑 6 ,因为 heights[3] < heights[6] 且 heights[5] < heights[6] 。
第三个查询中,Alice 无法与 Bob 相遇,因为 Bob 不能移动到任何其他建筑。
第四个查询中,Alice 和 Bob 可以移动到建筑 4 ,因为 heights[3] < heights[4] 且 heights[0] < heights[4] 。
第五个查询中,Alice 可以直接移动到 Bob 的建筑,因为 heights[1] < heights[6] 。
对于 ans[i] != -1 ,ans[i] 是 Alice 和 Bob 可以相遇的建筑中最左边建筑的下标。
对于 ans[i] == -1 ,不存在 Alice 和 Bob 可以相遇的建筑。

题干分析

问题解析

        我们有一个数组heights,其中每个元素代表着建筑物的高度。给定一个查询数组queries,每个查询包含两个位置(ai, bi),表示Alice在ai位置,Bob在bi位置。我们的目标是找到一个最左边的位置,使得Alice和Bob都能通过移动到该位置相遇。如果不能相遇,返回-1。

解题思路

        为了解决这个问题我们使用线段树来有效地查找每个位置右侧的最高建筑。这样可以快速判断Alice是否能与Bob相遇。

       这里简单解释一下什么是线段树:线段树是一种数据结构,允许我们在对数时间内找到某个区间的最大值。这非常适合处理需要多次从进行范围查询的问题。

代码步骤

1.构建线段树
  • 目的:构建一棵线段树用于存储每个区间的最大高度。
  • 叶子结点:表示每个建筑物的高度。
  • 内部节点:表示其子节点的最大值,这样可以快速查询某一区间的最大高度。
// 构建线段树,用于存储建筑高度信息
void build(int l, int r, int rt, int heights[], int n, int* zd) {
    if (l == r) {  // 叶子节点:存储heights数组的值
        zd[rt] = heights[l - 1];
        return;
    }
    int mid = (l + r) >> 1;
    build(l, mid, rt << 1, heights, n, zd);  // 递归构建左子树
    build(mid + 1, r, rt << 1 | 1, heights, n, zd);  // 递归构建右子树
    zd[rt] = (zd[rt << 1] > zd[rt << 1 | 1]) ? zd[rt << 1] : zd[rt << 1 | 1];  // 保存左右子树的最大值
}
 2.查询最左位置

目的:

  • 找到右侧第一个高度大于val的位置。

步骤:

1.如果当前区间的最大高度小于等于val,返回0表示无法找到满足条件的建筑。

  • 解释:如果当前区间的最大高度小于等于val,意味着从这个区间的所有建筑高度都无法满足条件(即高度都小于等于val)。因此,我们返回0表示在这个区间内无法找到合适的建筑。

2.如果当前区间是一个叶子节点,返回这个叶子结点的索引。

  • 解释:如果当前区间已经是一个叶子节点(即只包含一个建筑),那么如果这个建筑高度大于val,我们找到了满足条件的建筑,返回这个建筑的索引。

3.否则递归查询左子区间;如果左子区间没有找到合适的位置,继续查询右子区间。

  • 解释:我们需要检查左子区间是否存在满足条件的建筑。如果左子区间找到了符合条件的建筑,我们可以你直接返回结果。
  • 如果左子区间没有找到合适的位置,则继续查询右子区间。这个步骤的关键是确保我们找到的建筑是最左侧的(即最早可以相遇的位置)。

4.为什么这么做?

       当Alice和Bob分别在建筑ai和bi时,我们需要找到从bi开始向右,第一个高度大于heights[ai]的建筑,以便于确定他们能否相遇。

       如果heights[bi]<heights[ai],Alice不能直接移动到Bob所在的建筑。因此,我们需要找到Bob右侧第一个比heights[ai]高的建筑,这样Alice才能移动到这个为止,从而满足相遇的条件。

// 查询给定值val的第一个可达位置
int query(int pos, int val, int l, int r, int rt, int* zd) {
    if (val >= zd[rt]) {
        return 0;  // 如果查询值大于等于区间最大值,返回0表示无法到达
    }

    if (l == r) {
        return l;  // 如果到达叶子节点,返回该位置
    }

    int mid = (l + r) >> 1;
    if (pos <= mid) {
        int res = query(pos, val, l, mid, rt << 1, zd);  // 先查询左子区间
        if (res != 0) {
            return res;  // 如果左子区间有结果,直接返回
        }
    }
    return query(pos, val, mid + 1, r, rt << 1 | 1, zd);  // 查询右子区间
}
3.处理查询

目的:

  • 根据每个查询(ai,bi)找出Alice和Bob能相遇的最左侧建筑。

步骤: 

  • 如果a == b,Alice和Bob已经相遇,直接返回b。
  • 如果heights[a] < heights[b],Alice可以直接移动到Bob的位置。
  • 否则,通过线段树查询从b+1开始,第一个高度大于heights[a]的位置。
  • 将结果存入ans数组。
// 主函数,处理查询并返回结果数组
int* leftmostBuildingQueries(int* heights, int heightsSize, int** queries, int queriesSize, int* queriesColSize, int* returnSize) {
    int n = heightsSize;
    int zd[n * 4];
    memset(zd, 0, sizeof(zd));
    build(1, n, 1, heights, n, zd);  // 构建线段树

    int* ans = (int*)malloc(queriesSize * sizeof(int));
    for (int i = 0; i < queriesSize; i++) {
        int a = queries[i][0];
        int b = queries[i][1];
        if (a > b) {  // 保证a小于b
            int temp = a;
            a = b;
            b = temp;
        }
        if (a == b || heights[a] < heights[b]) {
            ans[i] = b;  // 如果a和b相等或a位置高度小于b位置高度,b即为答案
            continue;
        }
        ans[i] = query(b + 1, heights[a], 1, n, 1, zd) - 1;  // 查询b右侧高度大于a位置高度的最左位置
    }
    *returnSize = queriesSize;
    return ans;
}

完整代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 构建线段树,用于存储建筑高度信息
void build(int l, int r, int rt, int heights[], int n, int* zd) {
    if (l == r) {  // 叶子节点:存储heights数组的值
        zd[rt] = heights[l - 1];
        return;
    }
    int mid = (l + r) >> 1;
    build(l, mid, rt << 1, heights, n, zd);  // 递归构建左子树
    build(mid + 1, r, rt << 1 | 1, heights, n, zd);  // 递归构建右子树
    zd[rt] = (zd[rt << 1] > zd[rt << 1 | 1]) ? zd[rt << 1] : zd[rt << 1 | 1];  // 保存左右子树的最大值
}

// 查询给定值val的第一个可达位置
int query(int pos, int val, int l, int r, int rt, int* zd) {
    if (val >= zd[rt]) {
        return 0;  // 如果查询值大于等于区间最大值,返回0表示无法到达
    }

    if (l == r) {
        return l;  // 如果到达叶子节点,返回该位置
    }

    int mid = (l + r) >> 1;
    if (pos <= mid) {
        int res = query(pos, val, l, mid, rt << 1, zd);  // 先查询左子区间
        if (res != 0) {
            return res;  // 如果左子区间有结果,直接返回
        }
    }
    return query(pos, val, mid + 1, r, rt << 1 | 1, zd);  // 查询右子区间
}

// 主函数,处理查询并返回结果数组
int* leftmostBuildingQueries(int* heights, int heightsSize, int** queries, int queriesSize, int* queriesColSize, int* returnSize) {
    int n = heightsSize;
    int zd[n * 4];
    memset(zd, 0, sizeof(zd));
    build(1, n, 1, heights, n, zd);  // 构建线段树

    int* ans = (int*)malloc(queriesSize * sizeof(int));
    for (int i = 0; i < queriesSize; i++) {
        int a = queries[i][0];
        int b = queries[i][1];
        if (a > b) {  // 保证a小于b
            int temp = a;
            a = b;
            b = temp;
        }
        if (a == b || heights[a] < heights[b]) {
            ans[i] = b;  // 如果a和b相等或a位置高度小于b位置高度,b即为答案
            continue;
        }
        ans[i] = query(b + 1, heights[a], 1, n, 1, zd) - 1;  // 查询b右侧高度大于a位置高度的最左位置
    }
    *returnSize = queriesSize;
    return ans;
}

// 测试代码
int main() {
    int heights[] = { 10, 12, 5, 14, 15 };
    int heightsSize = sizeof(heights) / sizeof(heights[0]);
    int queriesSize = 2;
    int queriesColSize[] = { 2, 2 };
    int* queries[] = { (int[]) { 1, 3 }, (int[]) { 4, 2 } };
    int returnSize;

    int* result = leftmostBuildingQueries(heights, heightsSize, queries, queriesSize, queriesColSize, &returnSize);
    for (int i = 0; i < returnSize; i++) {
        printf("%d\n", result[i]);
    }
    free(result);

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值