Eat Study Love

먹고 공부하고 사랑하라

Coding_Practice

Find Building Where Alice and Bob Can Meet(Array,Binary Search,Stack,Binary Indexed Tree,Segment Tree,Heap (Priority Queue),Monotonic Stack)

eatplaylove 2024. 12. 22. 22:28

 

class Solution:
    def leftmostBuildingQueries(self, heights: List[int], queries: List[List[int]]) -> List[int]:
        ans = []
        # O(n^2 sol..)
        for i in range(len(queries)):
            # low & high are index
            high = max(queries[i])
            low = min(queries[i])

            if high == low or heights[high] > heights[low] :
                ans.append(high)
                continue
            else:
                cond = False
                for j in range(high+1,len(heights)):
                    if heights[j] > heights[low] and heights[j] > heights[high]:
                        cond = True
                        ans.append(j)
                        break
                if not cond :
                    ans.append(-1) 

        return ans

https://leetcode.com/problems/find-building-where-alice-and-bob-can-meet/description/?envType=daily-question&envId=2024-12-22

You are given a 0-indexed array heights of positive integers, where heights[i] represents the height of the ith building.

If a person is in building i, they can move to any other building j if and only if i < j and heights[i] < heights[j].

You are also given another array queries where queries[i] = [ai, bi]. On the ith query, Alice is in building ai while Bob is in building bi.

Return an array ans where ans[i] is the index of the leftmost building where Alice and Bob can meet on the ith query. If Alice and Bob cannot move to a common building on query i, set ans[i] to -1.

 

Example 1:

Input: heights = [6,4,8,5,2,7], queries = [[0,1],[0,3],[2,4],[3,4],[2,2]]
Output: [2,5,-1,5,2]
Explanation: In the first query, Alice and Bob can move to building 2 since heights[0] < heights[2] and heights[1] < heights[2]. 
In the second query, Alice and Bob can move to building 5 since heights[0] < heights[5] and heights[3] < heights[5]. 
In the third query, Alice cannot meet Bob since Alice cannot move to any other building.
In the fourth query, Alice and Bob can move to building 5 since heights[3] < heights[5] and heights[4] < heights[5].
In the fifth query, Alice and Bob are already in the same building.  
For ans[i] != -1, It can be shown that ans[i] is the leftmost building where Alice and Bob can meet.
For ans[i] == -1, It can be shown that there is no building where Alice and Bob can meet.

Example 2:

Input: heights = [5,3,8,2,6,1,4,6], queries = [[0,7],[3,5],[5,2],[3,0],[1,6]]
Output: [7,6,-1,4,6]
Explanation: In the first query, Alice can directly move to Bob's building since heights[0] < heights[7].
In the second query, Alice and Bob can move to building 6 since heights[3] < heights[6] and heights[5] < heights[6].
In the third query, Alice cannot meet Bob since Bob cannot move to any other building.
In the fourth query, Alice and Bob can move to building 4 since heights[3] < heights[4] and heights[0] < heights[4].
In the fifth query, Alice can directly move to Bob's building since heights[1] < heights[6].
For ans[i] != -1, It can be shown that ans[i] is the leftmost building where Alice and Bob can meet.
For ans[i] == -1, It can be shown that there is no building where Alice and Bob can meet.

 

Constraints:

  • 1 <= heights.length <= 5 * 104
  • 1 <= heights[i] <= 109
  • 1 <= queries.length <= 5 * 104
  • queries[i] = [ai, bi]
  • 0 <= ai, bi <= heights.length - 1

항상 사람을 괴롭히는 Alice와 Bob이다.

딱 봐도 쉽지 않아 보이는 문제지만, 일단 try는 해보기

 

뭔가 문제 Description을 읽어보면 할만할 거 같은데, 또 time limit에 걸릴 거 같긴하다..

 

1. Python

역시나.. 가벼운 test case는 다 풀었는데, 이상한 곳에서 Wrong Answer이 떴다. 뭐고 이거

class Solution:
    def leftmostBuildingQueries(self, heights: List[int], queries: List[List[int]]) -> List[int]:
        ans = []
        # O(n^2 sol..)
        for i in range(len(queries)):
            # low & high are index
            high = max(queries[i])
            low = min(queries[i])

            if heights[high] >= heights[low] :
                ans.append(high)
                continue
            else:
                cond = False
                for j in range(high+1,len(heights)):
                    if heights[j] > heights[low] and heights[j] > heights[high]:
                        cond = True
                        ans.append(j)
                        break
                if not cond :
                    ans.append(-1) 

        return ans

아오...

뭔가 Naive하게 잘 풀릴 거 같은데, Time limit이 뜨는 것들은 다 이유가 있다.

괜히 Hard난이도 문제가 아님..

 

근데 어떻게 시간효율을 줄일 수 있을까

 

솔직히 for 문 2개 겹쳐서 쓸 때부터 위 결과는 예상했다.

 

답을 찾아봤는데,, 이것들 뭐냐..

from bisect import bisect_left

class Solution:
    def leftmostBuildingQueries(self, heights: List[int], queries: List[List[int]]) -> List[int]:
        n = len(heights)
        st = [[0] * 20 for _ in range(n)]
        Log = [-1] * (n + 1)
        Log[0] = -1
        for i in range(1, n + 1):
            Log[i] = Log[i >> 1] + 1
        for i in range(n):
            st[i][0] = heights[i]
        for i in range(1, 20):
            for j in range(n):
                if j + (1 << i) <= n:
                    st[j][i] = max(st[j][i - 1], st[j + (1 << (i - 1))][i - 1])

        def Ask(l, r):
            k = Log[r - l + 1]
            return max(st[l][k], st[r - (1 << k) + 1][k])

        res = []
        for l, r in queries:
            if l > r:
                l, r = r, l
            if l == r:
                res.append(l)
                continue
            if heights[r] > heights[l]:
                res.append(r)
                continue
            max_height = max(heights[r], heights[l])
            left, right = r + 1, n
            while left < right:
                mid = (left + right) // 2
                if Ask(r + 1, mid) > max_height:
                    right = mid
                else:
                    left = mid + 1
            res.append(left if left != n else -1)
        return res
class Solution:
    def leftmostBuildingQueries(self, heights, queries):
        n, q = len(heights), len(queries)
        result = [-1] * q
        deferred = [[] for _ in range(n)]
        pq = []
        # 작은 수 a , 큰 수 b
        for i in range(q):
            a, b = queries[i]
            if a > b:
                a, b = b, a
            if a == b or heights[a] < heights[b]:
                result[i] = b
            else:
                deferred[b].append((heights[a], i))
        
        # 얘가 이해가 안 간다..
        for i in range(n):
            for query in deferred[i]:
                heapq.heappush(pq, query)
            while pq and pq[0][0] < heights[i]:
                result[pq[0][1]] = i
                heapq.heappop(pq)

        return result

 

heap을 쓰는 건 조금 이해가 가긴 한다.

 

다시 보니 이해가 안 간다..

 

뒤에 for문에서 deferred를 건져내는 부분을 내가 과연 생각해낼 수 있을까 싶다.

 

코드를 대놓고 봐도 어려운데;;

 

ㅠㅠ

 

2. C++

// #C++
class Solution {
public:
    int n, sparseTable[50010][20], logValues[50010];

    int queryMaximum(int left, int right) {
        int k = logValues[right - left + 1];
        return max(sparseTable[left][k], sparseTable[right - (1 << k) + 1][k]);
    }

    vector<int> leftmostBuildingQueries(vector<int>& heights, vector<vector<int>>& queries) {
        int n = heights.size();
        sparseTable[n][0] = 2e9;
        logValues[0] = -1;
        for (int i = 1; i <= n; i++) {
            logValues[i] = logValues[i >> 1] + 1;
        }
        for (int i = 0; i < n; i++) {
            sparseTable[i][0] = heights[i];
        }
        for (int i = 1; i < 20; i++) {
            for (int j = 0; j + (1 << i) - 1 <= n; j++) {
                sparseTable[j][i] = max(sparseTable[j][i - 1], sparseTable[j + (1 << (i - 1))][i - 1]);
            }
        }
        int numQueries = queries.size();
        vector<int> results(numQueries);
        for (int queryIndex = 0; queryIndex < numQueries; queryIndex++) {
            int left = queries[queryIndex][0], right = queries[queryIndex][1];
            if (left > right) swap(left, right);
            if (left == right) {
                results[queryIndex] = left;
                continue;
            }
            if (heights[right] > heights[left]) {
                results[queryIndex] = right;
                continue;
            }
            int maxHeight = max(heights[right], heights[left]);
            int low = right, high = n, mid;
            while (low < high) {
                mid = (low + high) >> 1;
                if (queryMaximum(right, mid) > maxHeight) {
                    high = mid;
                } else {
                    low = mid + 1;
                }
            }
            results[queryIndex] = (low == n) ? -1 : low;
        }
        return results;
    }
};

 

C++ heap으로 풀면 아래와 같다

 

min-heap

#include <vector>
#include <queue>
#include <utility>
using namespace std;

class Solution {
public:
    vector<int> leftmostBuildingQueries(vector<int>& heights, vector<vector<int>>& queries) {
        int n = heights.size();
        int q = queries.size();
        vector<int> result(q, -1);
        vector<vector<pair<int, int>>> deferred(n); // 각 빌딩별 처리해야 할 쿼리 저장
        priority_queue<pair<int, int>, vector<pair<int, int>>, greater<>> pq; // 최소 힙
        
        // 1. 쿼리 분류
        for (int i = 0; i < q; ++i) {
            int a = queries[i][0];
            int b = queries[i][1];
            if (a > b) swap(a, b); // 항상 a <= b 유지
            if (a == b || heights[a] < heights[b]) {
                result[i] = b; // 바로 만날 수 있는 경우
            } else {
                deferred[b].emplace_back(heights[a], i); // deferred[b]에 쿼리 저장
            }
        }
        
        // 2. 빌딩 순회
        for (int i = 0; i < n; ++i) {
            // 현재 빌딩에서 처리해야 할 쿼리를 힙에 추가
            for (auto& query : deferred[i]) {
                pq.push(query); // (Alice의 높이, 쿼리 인덱스)
            }
            
            // 힙에서 조건을 만족하는 쿼리를 처리
            while (!pq.empty() && pq.top().first < heights[i]) {
                result[pq.top().second] = i; // 현재 빌딩에서 Alice와 Bob이 만날 수 있음
                pq.pop();
            }
        }
        
        return result;
    }
};