2940. Find Building Where Alice and Bob Can Meet
내 코드
Solution 참고..
Solution
Approach 1: Monotonic Stack
- 54ms, 262.74MB
- Complexity
- Let be the size of the array
heightsand be the number of queries in thequeriesarray. - Time Complexity:
- Space Complexity:
- Let be the size of the array
class Solution {
public:
vector<int> leftmostBuildingQueries(vector<int>& heights,
vector<vector<int>>& queries) {
vector<pair<int, int>> monoStack;
vector<int> result(queries.size(), -1);
vector<vector<pair<int, int>>> newQueries(heights.size());
for (int i = 0; i < queries.size(); i++) {
int a = queries[i][0];
int b = queries[i][1];
if (a > b) swap(a, b);
if (heights[b] > heights[a] || a == b)
result[i] = b;
else
newQueries[b].push_back({heights[a], i});
}
for (int i = heights.size() - 1; i >= 0; i--) {
int monoStackSize = monoStack.size();
for (auto& [a, b] : newQueries[i]) {
int position = search(a, monoStack);
if (position < monoStackSize && position >= 0)
result[b] = monoStack[position].second;
}
while (!monoStack.empty() && monoStack.back().first <= heights[i])
monoStack.pop_back();
monoStack.push_back({heights[i], i});
}
return result;
}
private:
int search(int height, vector<pair<int, int>>& monoStack) {
int left = 0;
int right = monoStack.size() - 1;
int ans = -1;
while (left <= right) {
int mid = (left + right) / 2;
if (monoStack[mid].first > height) {
ans = max(ans, mid);
left = mid + 1;
} else
right = mid - 1;
}
return ans;
}
};
GPT
아래 코드는 빌딩들의 높이를 나타내는 배열 heights에 대해, 쿼리로 주어진 구간에서 다음 조건을 만족하는 왼쪽에서 가장 오른쪽에 위치한 빌딩(인덱스)을 찾는 문제로 보입니다.
- 쿼리는 형태이지만, 내부적으로 로 정렬하며, 빌딩 의 높이보다 더 높은 빌딩이 구간 내에서 어디 있는지(혹은 찾지 못하면 기본적으로 를 반환) 등을 판별하는 로직을 수행하고 있습니다.
정확한 문제 설명이 주어지지는 않았지만, 코드 흐름 및 사용된 자료구조(모노톤 스택, 이진 탐색 등)를 통해 “왼쪽에서 시작하여 빌딩 와 비교했을 때 더 높은 빌딩이 b~? 사이 어디에 있는지 찾기” 혹은 “어느 지점에서 빌딩 보다 높은 빌딩을 처음 만나게 되는가?” 같은 문제로 추측할 수 있습니다.
아래에서는 코드의 흐름을 단계별로 살펴보겠습니다.
1. 쿼리 분류 & 초기 결과 설정
for (int i = 0; i < queries.size(); i++) {
int a = queries[i][0];
int b = queries[i][1];
if (a > b) swap(a, b);
// heights[b] > heights[a] 또는 a == b이면,
// 곧바로 결과 = b 로 설정
if (heights[b] > heights[a] || a == b)
result[i] = b;
else
// (b)번 인덱스를 key로 하는 리스트에 (heights[a], i) 푸시
newQueries[b].push_back({heights[a], i});
}
- 각 쿼리
[a, b]가 주어졌을 때,a > b라면 스왑해서 항상a <= b가 되도록 맞춥니다. - 만약
heights[b]가heights[a]보다 크거나,a == b라면,- 이미 답이 자명하기 때문에
result[i] = b로 처리하고 끝냅니다.
- 이미 답이 자명하기 때문에
- 그렇지 않은 경우(즉,
heights[b] <= heights[a]이고a != b),- 나중에
b위치에서 무언가 처리할 일이 생기므로,newQueries[b]에(heights[a], i)를 저장해 둡니다. - 여기서
i는 쿼리의 인덱스,heights[a]는 비교 기준이 될 높이.
- 나중에
즉, 즉시 결정될 수 있는 쿼리는 미리 답을 적어두고, 그렇지 않은 쿼리는 “나중에 b 위치를 역순으로 탐색하며” 해결한다는 방식을 쓰고 있습니다.
2. 모노톤 스택을 이용한 역방향 처리
for (int i = heights.size() - 1; i >= 0; i--) {
int monoStackSize = monoStack.size();
// newQueries[i] = {(heights[a], indexOfQuery), ...}
// i 위치에서 처리해야 할 쿼리들
for (auto& [a, queryIndex] : newQueries[i]) {
// 모노톤 스택 내부에서 'a보다 큰' 빌딩을 이진 탐색
int position = search(a, monoStack);
if (position < monoStackSize && position >= 0)
// 모노톤 스택에서 찾은 빌딩의 인덱스(monoStack[position].second)로 쿼리 결과 설정
result[queryIndex] = monoStack[position].second;
}
// 모노톤 스택을 유지: heights[i] 이하인 것들은 pop
while (!monoStack.empty() && monoStack.back().first <= heights[i])
monoStack.pop_back();
// 그 뒤 (heights[i], i)를 push
monoStack.push_back({heights[i], i});
}
(A) newQueries[i] 처리
- 인덱스 를 오른쪽에서 왼쪽(i=n-1 down to 0)으로 훑으면서,
newQueries[i]에 쌓아 두었던 “i 위치에서 처리해야 할 쿼리들”을 처리합니다.newQueries[i]에는(heights[a], queryIndex)형태의 데이터가 들어있는데, 이는 “빌딩a의 높이와 비교했을 때 더 큰 빌딩을 모노톤 스택에서 찾는다”는 뜻으로 해석할 수 있습니다.
(B) search(a, monoStack)
- 이 함수는 모노톤 스택(내림차순 정렬)에서 “
a보다 큰 높이(first > a)를 만족하는 원소 중 가능한 한 오른쪽(스택의 뒤쪽)?”을 찾는 이진 탐색 로직으로 보입니다.- 실제 코드를 보면
if (monoStack[mid].first > height) {...} else {...}형태입니다. ans를 갱신하면서,monoStack[mid].first > height이면ans = max(ans, mid); left = mid+1;처리를 합니다.- 이는 보다 오른쪽(인덱스가 큰) 위치를 찾아가는 방식.
- 최종
ans를 반환하면, 그 위치가 가장 오른쪽에서height를 초과하는 원소가 있는 인덱스가 됩니다. (또는 -1)
- 실제 코드를 보면
(C) 모노톤 스택 업데이트
- “내림차순”을 유지하려고 하는 것으로 보입니다.
- 현재 인덱스
i의 빌딩 높이heights[i]보다 작거나 같은 원소는 스택에서 pop. - 그런 다음
(heights[i], i)를 push. - 이렇게 하면 스택은 “오른쪽 → 왼쪽”으로 갈수록 늘어만 가는(non-decreasing) 높이 순서를 유지하게 됩니다.
- 검색할 때
search(a, monoStack)가a보다 큰(strictly greater) 요소의 위치를 찾으니, 사실상 “오른쪽에서 왼쪽으로 볼 때, 오름차순인 높이들” 을 스택에 저장하는 셈입니다.
- 현재 인덱스
결과적으로,
- 오른쪽 끝에서부터 왼쪽으로 진행하면서, 현재 위치
i에서 “자신보다 낮거나 같은” 빌딩을 스택에서 없애버림 → 스택에는 “나보다 더 높은 빌딩”만 남음. search(a, monoStack)를 통해a보다 더 높은 빌딩 중 가장 나중에(오른쪽에) 들어있는 빌딩의 인덱스를 찾을 수 있음.- 그 인덱스를 가져와 쿼리 결과에 대입.
3. search 함수 (이진 탐색)
int search(int height, vector<pair<int, int>>& monoStack) {
int left = 0;
int right = monoStack.size() - 1;
int ans = -1;
while (left <= right) {
int mid = (left + right) / 2;
if (monoStack[mid].first > height) {
ans = max(ans, mid);
left = mid + 1;
} else {
right = mid - 1;
}
}
return ans;
}
monoStack[mid].first > height이면,ans = max(ans, mid)하고 더 오른쪽도 가능할지 확인하기 위해left = mid+1;- 그렇지 않으면
right = mid - 1; - 결국 이진 탐색 종료 후
ans가 “가장 오른쪽에서height보다 큰 원소”의 인덱스 를 반환합니다. - 찾지 못하면 -1이 남게 됩니다.
스택이 내림차순 정렬이라고 할 때,
- 인덱스가 커질수록 높이가 낮아지는 구조를 상상할 수 있으나, 여기서는 스택의 0번이 오른쪽 끝, 마지막 push가 왼쪽 끝에 해당**할 수 있습니다.
- 코드 로직상 “오른쪽에서 왼쪽”으로 빌딩을 훑고 있으니, push도 “스택의 뒤에” 집어넣고, pop은 “스택의 뒤에서” 일어납니다.
- 따라서
monoStack[0]이 가장 오른쪽 빌딩,monoStack.back()이 현재 i번째 빌딩이 됩니다. - 이진 탐색으로
a보다 큰 빌딩 중 가장 인덱스가 큰(스택에서 맨 앞쪽) 원소를 찾는 방식으로 보입니다. (조금 헷갈리긴 하지만, 코드가 그런 구조를 취하고 있음)
요약 정리
-
쿼리 전처리
- ([a, b])에서
a > b면 스왑 → 항상 (a \le b). - 만약
heights[b] > heights[a]거나a == b면, 결과가 바로b로 정해짐. - 그렇지 않으면, “나중에 (b) 위치에서
heights[a]보다 더 높은 빌딩의 인덱스를 찾아야 한다” →newQueries[b].push_back({heights[a], 쿼리인덱스}).
- ([a, b])에서
-
역순 루프(i = n-1..0) + 모노톤 스택
newQueries[i]를 처리하면서, 각(heights[a], queryIndex)에 대해 스택에서a보다 더 높은 빌딩 중 가장 나중(오른쪽)의 인덱스를 이진 탐색.- 스택은 “
heights[i]보다 작거나 같은 빌딩”을 pop하여, 내림차순 성질을 유지. (heights[i], i)를 push.
-
이진 탐색
search(a, monoStack)monoStack에서 “a보다 큰 first를 가진 원소 중 스택 인덱스가 가장 큰(오른쪽)” 을 찾는다.- 찾으면 해당 빌딩의 인덱스(
second)를 결과로 반환.
결론적으로…
- 왼쪽 빌딩(a) 와 오른쪽 빌딩(b) 가 주어질 때, “오른쪽으로 진행하면서 ‘a’보다 더 높은 빌딩을 찾으면 그 인덱스가 답이 된다. 만약 못 찾으면 b가 답” 같은 상황을 역순으로 모노톤 스택을 구축하며 해결하는 기법입니다.
- 모노톤 스택에 쿼리를 나중에 한 번에 처리(
newQueries[b]목록 사용)함으로써, “어떤 시점(i)에서 처리해야 할 쿼리가 있으면, 현재 스택 상태를 통해 빠르게 답을 찾는다”는 구조를 취하고 있습니다. - 시간 복잡도는 대체로 수준(스택 유지 O(n), 쿼리 이진 탐색이 등)일 것으로 보이며, 쿼리의 수와 빌딩의 수에 비례해 동작합니다.
즉, 이 코드는 “오른쪽에서 왼쪽으로 빌딩 높이를 모노톤(내림차순)으로 스택에 저장”하면서, “각종 쿼리에서, 의 높이보다 더 높은 빌딩을 의 위치 근처에서(혹은 그보다 오른쪽에서) 찾고자 하는” 문제를 효율적으로 해결하는 방법이라고 이해할 수 있습니다.
Approach 2: Priority Queue
- 257ms, 321.31MB
- Complexity
- Let be the size of the array
heightsand be the number of queries in thequeriesarray. - Time Complexity:
- Space Complexity:
- Let be the size of the array
class Solution {
public:
vector<int> leftmostBuildingQueries(vector<int>& heights,
vector<vector<int>>& queries) {
vector<vector<vector<int>>> storeQueries(heights.size());
priority_queue<vector<int>, vector<vector<int>>, greater<vector<int>>>
maxIndex;
vector<int> result(queries.size(), -1);
// Store the mappings for all queries in storeQueries.
for (int currQuery = 0; currQuery < queries.size(); currQuery++) {
int a = queries[currQuery][0], b = queries[currQuery][1];
if (a < b && heights[a] < heights[b]) {
result[currQuery] = b;
} else if (a > b && heights[a] > heights[b]) {
result[currQuery] = a;
} else if (a == b) {
result[currQuery] = a;
} else {
storeQueries[max(a, b)].push_back(
{max(heights[a], heights[b]), currQuery});
}
}
for (int index = 0; index < heights.size(); index++) {
// If the priority queue's minimum pair value is less than the
// current index of height, it is an answer to the query.
while (!maxIndex.empty() && maxIndex.top()[0] < heights[index]) {
result[maxIndex.top()[1]] = index;
maxIndex.pop();
}
// Push the with their maximum index as the current index in the
// priority queue.
for (auto& element : storeQueries[index]) {
maxIndex.push(element);
}
}
return result;
}
};
GPT
아래 코드는 “두 빌딩 가 주어졌을 때, 어떤 조건에 따라 ‘왼쪽에서 오른쪽으로 스캔했을 때 처음으로 만나는, 보다 높은 빌딩의 인덱스’ 를 구하는 문제”로 추측할 수 있습니다.
다만 문제 원문이 주어지지 않았으므로, 코드를 통해 역으로 로직을 유추해보겠습니다.
전체 코드 흐름
vector<int> leftmostBuildingQueries(vector<int>& heights,
vector<vector<int>>& queries) {
// (1) 쿼리별로 "즉시 결정 가능한" 경우는 바로 result에 기록,
// 그렇지 않은 쿼리는 따로 storeQueries[max(a, b)]에 저장.
vector<vector<vector<int>>> storeQueries(heights.size());
priority_queue<vector<int>, vector<vector<int>>, greater<vector<int>>> maxIndex;
vector<int> result(queries.size(), -1);
for (int currQuery = 0; currQuery < queries.size(); currQuery++) {
int a = queries[currQuery][0], b = queries[currQuery][1];
// a < b 이고 heights[a] < heights[b] 이면 즉시 result = b
// a > b 이고 heights[a] > heights[b] 이면 즉시 result = a
// a == b이면 당연히 result = a
// 그 외 경우 => storeQueries[max(a,b)] 에 {max(heights[a], heights[b]), 쿼리번호} 저장
if (a < b && heights[a] < heights[b]) {
result[currQuery] = b;
} else if (a > b && heights[a] > heights[b]) {
result[currQuery] = a;
} else if (a == b) {
result[currQuery] = a;
} else {
storeQueries[max(a, b)].push_back({max(heights[a], heights[b]), currQuery});
}
}
// (2) 왼쪽부터 빌딩을 스캔하며,
// "이전에 스택(우선순위큐)에서 더 높은 빌딩을 기다리고 있던 쿼리들"을 해소
for (int index = 0; index < (int)heights.size(); index++) {
// (2a) 현재 빌딩의 높이(heights[index])가
// 우선순위큐 top에 있는 "목표 높이"보다 더 크다면, 그 쿼리는 index로 답변 가능
while (!maxIndex.empty() && maxIndex.top()[0] < heights[index]) {
int queryIndex = maxIndex.top()[1];
result[queryIndex] = index;
maxIndex.pop();
}
// (2b) 지금 index에서 "추가로 발생하는" 쿼리(= storeQueries[index])를 큐에 삽입
// 이 쿼리들은 "max(heights[a], heights[b])보다 큰 빌딩을 찾아야 함"
for (auto& element : storeQueries[index]) {
// element = {targetHeight, queryIndex}
maxIndex.push(element);
}
}
return result;
}
이 로직을 토대로, 단계별 작동 방식을 살펴보겠습니다.
1. 쿼리 전처리 (즉시 답변 가능한 경우)
for (int currQuery = 0; currQuery < queries.size(); currQuery++) {
int a = queries[currQuery][0], b = queries[currQuery][1];
if (a < b && heights[a] < heights[b]) {
result[currQuery] = b;
} else if (a > b && heights[a] > heights[b]) {
result[currQuery] = a;
} else if (a == b) {
result[currQuery] = a;
} else {
storeQueries[max(a, b)].push_back({max(heights[a], heights[b]), currQuery});
}
}
- 쿼리는 ((a, b)) 형태입니다.
- 다음과 같은 3가지 케이스면 즉시 답이 정해집니다.
- 그리고
- 결과 =
- 그리고
- 결과 =
-
- 결과 = (a) (즉 “자기 자신”)
- 그리고
- 그 외의 경우(즉, “상대방 빌딩이 즉시 답이 되지 않는 상황”)에는,
- 인덱스 위치의
storeQueries[max(a,b)]에 {, 쿼리번호} 형태로 저장해 둡니다. - 이것은 “나중에 왼쪽에서 오른쪽으로 스캔하다가, 보다 높은 빌딩이 나타나면 그 때가 답”이라는 의미로 볼 수 있습니다.
- 인덱스 위치의
간단히 말해, 건너편 빌딩이 더 높으면 바로 답이고, 그렇지 않으면 더 높은 빌딩을 찾으러 간다는 로직처럼 보입니다.
2. 왼쪽에서 오른쪽 스캔 & 우선순위 큐
for (int index = 0; index < heights.size(); index++) {
// (A) 현재 빌딩의 높이로 해결 가능한(기다리고 있던) 쿼리들 처리
while (!maxIndex.empty() && maxIndex.top()[0] < heights[index]) {
int queryIndex = maxIndex.top()[1];
result[queryIndex] = index;
maxIndex.pop();
}
// (B) 이제 index에 도달했으니,
// storeQueries[index]에 쿼리가 있으면 큐에 등록
for (auto& element : storeQueries[index]) {
// element = {targetHeight, queryIndex}
maxIndex.push(element);
}
}
maxIndex:priority_queue<vector<int>, vector<vector<int>>, greater<vector<int>>>- 즉, ‘가장 작은 vector
가 top()’ 이 되는 Min-Heap입니다. - vector
의 형태는 {targetHeight, queryIndex}. greater<vector<int>>비교 시, 첫 번째 원소(targetHeight)를 우선 비교하고, 같으면 두 번째 원소(queryIndex)를 비교합니다.
- 즉, ‘가장 작은 vector
(A) 현재 빌딩으로 해결할 수 있는 쿼리
while (!maxIndex.empty() && maxIndex.top()[0] < heights[index]) { ... }- 즉, 큐의 top(최소
targetHeight)이heights[index]보다 작으면,- “보다 더 큰 빌딩”을 찾는 쿼리는 현재
index로 해결 가능하다는 뜻 →result[queryIndex] = index; - 해당 쿼리를 큐에서 pop.
- “보다 더 큰 빌딩”을 찾는 쿼리는 현재
- 이 과정을
top이 더 이상heights[index]보다 작지 않을 때까지 반복합니다.
- 즉, 큐의 top(최소
(B) storeQueries[index]에 쌓인 쿼리 등록
- 전처리 단계에서 “ 위치에서 (targetHeight, queryIndex)를 큐에 넣는다”라고 했는데, 그 “ 위치”가 바로
index가 될 때, 이제야 큐에 들어갑니다. - 즉,
index에 도달하기 전에는 해결할 수도, 큐에 들어가지도 않은 상태였다가,index가 되어서야 “이제부터index이후 빌딩 중 보다 높은 빌딩이 있으면 답”이라는 식으로 큐에 들어간다는 로직입니다.
정리: “왼쪽에서 첫 번째로 더 높은 빌딩” 찾기?
코드를 통해 유추하면 대략 이런 식의 문제를 푸는 것으로 보입니다:
- 쿼리 가 주어졌을 때,
- 만약 빌딩 (b)가 빌딩 (a)보다 (인덱스상 오른쪽이면서) 더 높다면, 답은 (b).
- 즉시
result = b.
- 즉시
- 만약 빌딩 (a)가 빌딩 (b)보다 (인덱스상 왼쪽이면서) 더 높다면, 답은 (a).
- 즉시
result = a.
- 즉시
- 만약 면 자기 자신이 답.
- 그 외 경우(즉, 쪽 빌딩이 당장 더 높지 않다면) → “”보다 큰 빌딩을 왼쪽부터 차례대로 찾는다. 제일 처음 발견한 인덱스가 답. (못 찾으면 -1)
- 만약 빌딩 (b)가 빌딩 (a)보다 (인덱스상 오른쪽이면서) 더 높다면, 답은 (b).
이 로직을 지원하기 위해,
- (1) 즉시 판별 가능하면
result에 바로 기록. - (2) 나머지는
storeQueries[ max(a,b) ]에 형태로 저장. - (3)
index=0부터index=n-1까지 순회:- 이미 큐에 들어와 있던 쿼리들 중 “
targetHeight < heights[index]”는index로 해결(= 첫 번째로 만난 더 높은 빌딩). - 그런 뒤
storeQueries[index]에 새로 등록된(“index에서 시작하는” 느낌의) 쿼리들을 큐에 push → 다음 인덱스들에서 해결될 수 있도록.
- 이미 큐에 들어와 있던 쿼리들 중 “
이로써 모든 쿼리에 대해, 왼쪽에서 오른쪽으로 스캔하면서 처음 발견되는 “heights[index] > targetHeight” 빌딩의 위치를 구할 수 있게 됩니다.
핵심 포인트 요약
-
즉시 답변 가능한 쿼리 처리
- 중 “오른쪽 빌딩이 더 높은 경우”
result = b - “왼쪽 빌딩이 더 높은 경우”
result = a - “둘이 같은 인덱스일 경우”
result = a - (이후 설명에서 “인덱스가 오른쪽”이라는 것은
a<b일 때를 가정)
- 중 “오른쪽 빌딩이 더 높은 경우”
-
그렇지 않은 쿼리는
- “보다 더 높은 빌딩을 찾아야 함”
- 이 요청을
storeQueries[max(a,b)]에 저장.
-
왼→오 스캔
- 각 인덱스
i에 도달하면, 지금까지 큐에 쌓여있던 쿼리들 중, “목표 높이 < heights[i]” 인 것들을result = i로 해결 & 큐에서 제거. storeQueries[i]에 쌓여 있던 쿼리(지금부터 i 이후를 탐색해야 하는 것들)를 큐에 등록.
- 각 인덱스
-
우선순위 큐(Min-Heap)는 “
targetHeight가 작은 순”대로 정렬되므로, “현재 빌딩이 그보다 크면 즉시 해결”하는 식의 그리디 처리 가능. -
최종적으로
- 스캔이 끝날 때까지 “더 높은 빌딩”을 찾지 못한 쿼리는
-1로 남습니다. - 찾았다면 해당 인덱스로 결과가 갱신됩니다.
- 스캔이 끝날 때까지 “더 높은 빌딩”을 찾지 못한 쿼리는
결론적으로
이 코드는
- (1) 특정한 빠른 판별 조건으로 일부 쿼리를 즉시 해결하고,
- (2) 나머지는 “보다 큰 빌딩을 왼쪽에서부터 찾겠다” 라는 아이디어로 처리,
- (3) 왼→오 순회하면서 현재 빌딩이 기다리던 목표 높이들보다 큰지 확인 후, 맞으면 “처음으로 만난 더 높은 빌딩” 인덱스를 결과로 설정
하는 그리디 + 우선순위 큐 방식이라 볼 수 있습니다.
“Leftmost Building Queries”라는 이름처럼, “어떤 목표 높이보다 ‘더 높은 빌딩’을 왼쪽부터 최초로 발견하는 위치”를 빠르게 찾아주는 로직으로 추정됩니다.
- 즉시 답이 정해지는 경우(건너편 빌딩이 더 높거나 같을 때) → 바로 답
- 그 외 경우 → 전체를 왼쪽부터 스캔하며 처음으로 목표 높이보다 큰 빌딩을 만나면 그 인덱스를 답으로.
- 만약 끝까지 없으면
-1.