hyelie
hyelie
Hyeil Jeong
       
글쓰기    관리    수식입력
  • 전체보기 (495)
    • PS (283)
      • Algorithm (28)
      • PS Log (244)
      • Contest (6)
      • Tips (5)
    • Development (52)
      • Java (14)
      • Spring (23)
      • SQL (2)
      • Node.js (2)
      • Socket.io (3)
      • Study (4)
      • Utils (4)
    • DevOps (36)
      • Git (5)
      • Docker (4)
      • Kubernetes (2)
      • GCP (3)
      • Environment Set Up (8)
      • Tutorial (12)
      • Figma (2)
    • CS (74)
      • OOP (7)
      • OS (24)
      • DB (2)
      • Network (24)
      • Architecture (0)
      • Security (2)
      • Software Design (0)
      • Parallel Computing (15)
    • Project (15)
      • Project N2T (5)
      • Project ASG (0)
      • Project Meerkat (1)
      • Model Checking (7)
      • Ideas (2)
    • 내가 하고싶은 것! (34)
      • Plan (16)
      • Software Maestro (10)
      • 취준 (8)
hELLO · Designed By 정상우.
hyelie

hyelie

PS/PS Log

23.03.06. 풀었던 문제들

Leetcode 1539. Kth Missing Positive Number

 증가수열 arr과 숫자 k가 주어질 때 arr에 있지 않은 k번째 배열을 찾는 문제. O(n)으로 풀면 바로 풀린다.

 

O(n) solution

// Runtime 6 ms Beats 53.33%
// Memory 9.7 MB Beats 38.5%

class Solution {
public:
    int findKthPositive(vector<int>& arr, int k) {
        vector<int> missings;
        int num = 1, arridx = 0;
        while(1){
            if(arridx < arr.size() && num == arr[arridx]){ // not missing
                num++;
                arridx++;
            }
            else{ // missing
                k--;
                if(k <= 0) break;
                num++;
            }
        }
        return num;
    }
};

 위 코드와 같이 앞에서부터 시뮬레이션 해 가면 쉽게 풀린다.

 

O(logn) solution

 그러나 이 문제는 이게 본 문제가 아니다. O(logn)으로 푸는 게 진짜인 문제. 이딴 게 easy..? binary search를 써야 하는 건 알겠는데 어떻게 써야 할지 감도 오지 않았다.

 어떤 수 n이 몇 번째 missing 숫자인지 찾는 것은 쉽다. n - index이며 이 때 index는 array에서 n의 upper bound이다. index가 n의 upper bound일 때 n - ub(n) = k인 n을 찾아야 했다. 이렇게 풀면 n을 조작해야 하기 때문에 O(logn)으로 풀 수 없다. 잘못된 접근.

 

 한참을 고민하다 solution을 봤다.

 arr[i] - i - 1은 arr[i] 이전에 오는 missing element 개수이다. 이건 당연히 쉽게 나오는 게, 문제 정의기 때문이다. arr[i]까지는 arr[i]개의 숫자가 있고, arr[i]의 index인 i는 존재하는 element이다. 따라서 arr[i] - i - 1은 arr[i] 이전에 오는 missing element 개수이다.

예를 들어
2, 3, 4, 7, 11인 경우
1, 1, 1, 3, 6이다.

 

 이 식을 이용해서 문제에 접근한다. 이 정보를 이용해서 어떤 element가 missing인지 알 수 있나? 우리는 k번째 missing element를 찾고 싶고, missing element 개수의 increasing array를 가지고 있다. (arr[i] - i - 1)

  • lower bound로 이 i를 찾았다고 하자. k의 lower bound이기 때문에 arr[i] - i - 1 >= k이다. 또한, arr[i - 1] - i < k이다.
    • upper bound 하면 missing element 개수가 k일 때 찾지 못하므로 lower bound를 쓴다.
  • arr[i-1] - i < k <= arr[i] - i - 1이고, arr[i-1] <= k < arr[i]이다.
    • 이 때 arr[i-1]은 missing value가 아니므로 arr[i-1] < k < arr[i]이다.
  • arr[i-1] 이전에 오는 missing element 개수 = arr[i-1] - i개이다.
  • arr[i-1]부터 k번째 missing value까지 k - arr[i-1] + i개이다.
  • 따라서 k번째 missing vlaue = arr[i-1] + k - arr[i-1] + i = k + i이다.

 그러면 arr[i] - i - 1의 lower bound만 찾으면 된다. 새로운 배열을 만들면 O(n)이고, 굳이 새로운 배열도 만들 필요 없다. arr[i] - i - 1도 monotonic increasing되어있기 때문.

// Runtime 4 ms Beats 73.18%
// Memory 9.5 MB Beats 94.95%

class Solution {
public:
    int findKthPositive(vector<int>& arr, int k) {
        int start = 0, end = arr.size();
        while(start < end){
            int mid = (start + end) / 2;
            if(arr[mid] - mid - 1 >= k){ // k가 더 작으므로 앞쪽을 탐색해야 함
                end = mid;
            }
            else start = mid + 1;
        }
        return end + k;
    }
};

 

시간복잡도

 binary search이므로 O(n)

 

공간복잡도

 O(1)이다. 추가 공간 사용하지 않는다.

 

후기

 대체 이런 걸 어떻게 생각해 내는거지..? bsearch인 걸 알아도 접근할 수가 없다... arr[i] - i - 1가 arr[i] 이전에 오는 missing element 개수임을 알아도 한 단계 접근이 더 필요했다.

 

 

 

프로그래머스 부대 복귀

 전형적인 BFS 문제. 시작점으로부터 찾을 생각 하지 말고 끝에서부터 거리를 찾으면 된다.

 이 문제의 경우

  • edge weight가 1이므로 BFS가 적합하다.
  • BFS의 경우 dist 값 갱신은 q에 넣는 순간에 하면 된다.
  • 탐색 시 unvisited로만 간다.

만 조심해 주면 된다.

int UNVISITED = -1;

vector<int> solution(int n, vector<vector<int>> roads, vector<int> sources, int destination) {
    // initialize
    vector<int> dist(n + 1, UNVISITED);
    map<int, vector<int>> edges; // edges[i] : i를 출발점으로 하는 edge
    for(vector<int> road : roads){
        edges[road[0]].push_back(road[1]);
        edges[road[1]].push_back(road[0]);
    }
    
    // BFS init
    queue<int> q;
    q.push(destination);
    dist[destination] = 0; // dist 초기화는 q에 넣는 순간
    
    // BFS
    while(!q.empty()){
        int cur = q.front(); q.pop();
        for(int next : edges[cur]){
            if(dist[next] == UNVISITED){
                dist[next] = dist[cur] + 1;
                q.push(next);
            }
        }
    }
    
    // get answer
    vector<int> answer;
    for(int s : sources){
        answer.push_back(dist[s]);
    }
    return answer;
}


// weight가 1이니까, BFS가 적절할 것 같다.
// destination부터 탐색

 

시간복잡도

 BFS이므로 O(V + E), V = 10만, E = 50만이므로 시간제한 내에 풀린다.

 

 

 

프로그래머스 인사고과

 정렬에 대해 잘 알고 있어야 하는 문제. 트릭trick이 하나 있다.

 먼저 strictly 더 작은 element는 무조건 삭제해야 한다. 예를 들어 [[4, 1], [2, 4], [3, 5]]와 같은 예외가 있을 수 있기 때문이다. [2, 4]는 [4, 1]보다 점수가 더 높지만 [3, 5]보다 strictly 더 작아서 없어져야 하고, 그러면 등수가 바뀐다.

 이제 이걸 어떻게 삭제하느냐? 생각보다 간단한 트릭이 있다. 두 점수를 [a, b]라고 하면

  • a는 내림차순 정렬, b는 오름차순 정렬 한다.
    • 따라서 arr[i].a >= arr[i+1].a는 보장된다.
  • 이 때 arr[i].b > arr[i+1].b라면 a의 값이 바뀌었다는 뜻. 즉슨 arr[i].a > arr[i+1].a이다.
    • 문제 조건에 따라서 strictly 더 작다. 해당 원소는 pass.
  • 따라서 이전 element의 b 값을 저장해 두고, 앞에서부터 순회한다.
    • 탐색한 element.b가 더 작은 경우 -> 해당 원소는 strictly 더 작다. pass
    • 그렇지 않은 경우 -> element b 값을 저장해 둔다.

 이 트릭이 말도 안됐다.

 

 참고로 조건이 3개인 경우는 seg tree를 써야 한다고 한다.

예시)
3, 1
3, 2
2, 1
2, 2
1, 4
bool cmp1(vector<int> &a, vector<int>& b){ // a[0] 내림차순, b[0] 내림차순
    if(a[0] == b[0]){
        return a[1] < b[1];
    }
    return a[0] > b[0];
}

bool cmp2(vector<int> &a, vector<int> &b){
    if(a[0] + a[1] == b[0] + b[1]){
        return a[0] > b[0];
    }
    if(a[0] + a[1] > b[0] + b[1]) return true;
    else return false;
}

int solution(vector<vector<int>> scores) {
    vector<int> target = scores[0];
    
    // 1차 : 못 받는 애들 다 자름
    sort(scores.begin(), scores.end(), cmp1);
    // first는 내림차순, second는 오름차순.
    // a, b가 있을 때 a.first > b.first는 보장됨
    // a.second > b.second면 둘 다 작은 거임. 그건 pass
    vector<vector<int>> sieve;
    int prevSecond = scores[0][1];
    for(int i = 0; i<scores.size(); i++){
        if(prevSecond > scores[i][1]){
            if(target[0] == scores[i][0] && target[1] == scores[i][1]) return -1;
            continue;
        }
        else{
            prevSecond = scores[i][1];
            sieve.push_back(scores[i]);
        }
    }
    
    // 2차 : 등수 매김
    sort(sieve.begin(), sieve.end(), cmp2);
    for(int i = 0; i<sieve.size(); i++){
        if(sieve[i][0] + sieve[i][1] == target[0] + target[1]) return i+1;
    }
    

    return -1;
}

 

시간복잡도

 sort는 O(nlogn), 순회는 O(n)이므로 O(nlogn)이다.

 

후기

 죽고싶다. 인센 못 받는 애들을 다 잘라야 하는 건 알고 있었는데 어떻게 자를지 감도 못 잡고 있어서 solution을 봤다. 자괴감들고 괴로워...

 그리고 2차로 등수 매길 때도 [현 등수, 해당 등수에 몇 명이 있는지] 이렇게 쓰고 for문으로 순회했는데 그럴 필요가 없다.동석차의 수만큼 건너뛴다는 건, 등수가 바뀐 순간에는 무조건 i+1등이라는 거다. 그래서 [완호의 점수 == 현재 탐색 중인 사람 점수 합]이 되는 순간에 i+1을 리턴하면 그게 답이다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

저작자표시 (새창열림)

'PS > PS Log' 카테고리의 다른 글

23.03.08. 풀었던 문제들  (0) 2023.03.08
23.03.07. 풀었던 문제들  (0) 2023.03.07
23.03.05. 풀었던 문제들  (0) 2023.03.06
23.03.04. 풀었던 문제들  (0) 2023.03.05
23.03.03. 풀었던 문제들  (0) 2023.03.03
    hyelie
    hyelie

    티스토리툴바