Study/Algorithm

프로그래머스 (C/C++) 250136 : [PCCP 기출문제] 2번 / 석유 시추

hwooo 2024. 4. 3. 02:16

https://school.programmers.co.kr/learn/courses/30/lessons/250136

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr


풀이

BFS로 배열 전체를 순회하며 석유가 있는 구간과 해당 구간에서 얻을 수 있는 석유 양을 추출했다.

이 때 석유가 있는 열의 범위를 구해 범위의 끝을 석유 양을 저장하여 표시해줬고, 가로 열을 순회하며 가장 양이 많은 열의 값을 반환했다.

 

코드를 조금씩 바꾸며 내 코드가 얼마나 비효율적인지를 깨달을 수 있었던 좋은 시간이었다..

생각을 더 해 본 후 코드를 짜는 습관을 들여야겠다.


답안 코드

#include <vector>
#include <algorithm>
#include <queue>

using namespace std;

#define MAX_LAND 510
typedef pair<int,int> pp;

bool visited[MAX_LAND][MAX_LAND];
int oils[MAX_LAND];
int N, M, miny, maxy;
queue <pp> Q;

// move to left, right, up, down
int dx[4] = {0, 0, -1, 1}, dy[4] = {-1, 1, 0, 0};

int bfs(vector<vector<int>>& land, int x, int y) {
    int total = 0;

    Q.push({x, y});
    visited[x][y] = true;
    
    while (!Q.empty()) {
        x = Q.front().first, y = Q.front().second;
        Q.pop();
        
       // 해당 구역이 걸쳐있는 가로줄 구함
        miny = min(y, miny), maxy = max(y, maxy);
        
        // 석유 양 저장
        total++;
        
        for (int i = 0; i < 4; i++) {
            int nx = x + dx[i], ny = y + dy[i];
            if (nx < 0 || N <= nx || ny < 0 || M <= ny || !land[nx][ny] || visited[nx][ny]) continue;

            visited[nx][ny] = true;
            Q.push({nx, ny});
        }
    }
    return total;
}

int solution(vector<vector<int>> land) {
    int answer = 0;

    N = land.size(), M = land[0].size();

    for (int i = 0; i < N; i++) {
        for (int j = 0; j < M; j++) {
            if (!land[i][j] || visited[i][j]) continue;
            
            miny = MAX_LAND, maxy = -1;
            
            // 해당 구역의 석유 양 추출 및 해당 구역의 가로 범위 저장
            int amount = bfs(land, i, j);       
            oils[miny] += amount, oils[maxy + 1] -= amount;
        }
    }
    
    int oilsInLine = 0;
    for (int i = 0; i < M; i++) {
        oilsInLine += oils[i];
        answer = max(answer, oilsInLine);
    }
    return answer;
}

 

 

 

+ DFS 탐색도 가능!

#include <vector>
#include <algorithm>

using namespace std;

#define MAX_LAND 510
typedef pair<int,int> pp;

bool visited[MAX_LAND][MAX_LAND];
int oils[MAX_LAND];
int N, M, miny, maxy;

// move to left, right, up, down
int dx[4] = {0, 0, -1, 1}, dy[4] = {-1, 1, 0, 0};

int dfs(vector<vector<int>>&land, int x, int y) {
    int totalOilInField = 1;
    
     // 해당 지점 방문 표시
    visited[x][y] = true;

    // 해당 구역이 걸쳐있는 가로줄 구함
    miny = min(y, miny), maxy = max(y, maxy);
    
    for (int i = 0; i < 4; i++) {
        int nx = x + dx[i], ny = y + dy[i];
        if (nx < 0 || N <= nx || ny < 0 || M <= ny || !land[nx][ny] || visited[nx][ny]) continue;
        
        // 현재 구역의 석유 양 계산 
        totalOilInField += dfs(land, nx, ny);
    }

    return totalOilInField;
}

int solution(vector<vector<int>> land) {
    int answer = 0;

    N = land.size(), M = land[0].size();

    for (int i = 0; i < N; i++) {
        for (int j = 0; j < M; j++) {
            if (!land[i][j] || visited[i][j]) continue;
            
            miny = MAX_LAND, maxy = -1;
            
            // 해당 구역의 석유 양 추출 및 해당 구역의 가로 범위 저장
            int amount = dfs(land, i, j);       
            oils[miny] += amount, oils[maxy + 1] -= amount;
        }
    }
    
    int oilsInLine = 0;
    for (int i = 0; i < M; i++) {
        oilsInLine += oils[i];
        answer = max(answer, oilsInLine);
    }
        
    return answer;
}


코드 1 - 메모리 부족

- 석유가 있는 각 구역마다 번호를 매겨 이를 각 열마다 스택에 저장해뒀고, 순회하며 각 열에 저장된 구역 번호의 양을 더해주며 계산했다. 

 

정확성 테스트 마지막에서 (signal: aborted (core dumped)) 실패, 모든 효율성 테스트케이스에서 탈락

위의 오류가 저장공간 부족이라는 의미인 것 같아 스택을 쓰면 안 된다고 판단, 다른 방법을 찾아봤다.

#include <string>
#include <vector>
#include <stack>
#include <algorithm>

#define MAX_LAND 500

using namespace std;

bool visited[MAX_LAND][MAX_LAND];
stack <int> oilsInLine[MAX_LAND];
int N, M, fieldNum;

// move to left, right, up, down
int dx[4] = {0, 0, -1, 1}, dy[4] = {-1, 1, 0, 0};

int dfs(vector<vector<int>> land, int x, int y) {
    int totalOilInField = 1;
    
    // 해당 지점 방문 표시
    visited[x][y] = true;
    
    // 가로줄에 해당 구역의 정보가 없다면 추가
    if (oilsInLine[y].empty() || (oilsInLine[y].size() && oilsInLine[y].top() != fieldNum)) {
        oilsInLine[y].push(fieldNum);
    }

    for (int i = 0; i < 4; i++) {
        int nx = x + dx[i], ny = y + dy[i];
        if (nx < 0 || N <= nx || ny < 0 || M <= ny || !land[nx][ny] || visited[nx][ny]) continue;
        
        // 현재 구역의 석유 양 계산 
        totalOilInField += dfs(land, nx, ny);
    }
    
    // 현재까지 구한 석유 양 반환
    return totalOilInField;
}

int solution(vector<vector<int>> land) {
    int answer = 0;
    vector <int> oilsInField(MAX_LAND);

    N = land.size(), M = land[0].size();

    for (int i = 0; i < N; i++) {
        for (int j = 0; j < M; j++) {
            if (!land[i][j] || visited[i][j]) continue;
            
            // 석유가 있는 구역이라면 해당 구역의 석유 양 추출(by dfs) 및 저장
            oilsInField[fieldNum] = dfs(land, i, j);
            fieldNum++;
        }
    }
    
    // 가로 줄을 순회
    for (int i = 0; i < M; i++) {
        int amount = 0;
        
        // 해당 가로줄에서 얻을 수 있는 총 석유의 양 계산 후 최대 값 찾기
        while (!oilsInLine[i].empty()) {
            amount += oilsInField[oilsInLine[i].top()];
            oilsInLine[i].pop();
        }
        answer = max(amount, answer);
    }
    return answer;
}

 


코드 2 - 시간 초과

- 코드 1과 원리는 동일하나 각 구역의 석유 양을 구할 때 y축 범위의 min ~ max를 구했고,

구역 1개를 구할 때마다 min_y ~max_y 범위에 석유의 양을 더해주었다.

 

정확성 테스트 통과, 효율성 테스트 2, 3 시간초과

 

#include <string>
#include <vector>
#include <algorithm>

#define MAX_LAND 500

using namespace std;

bool visited[MAX_LAND][MAX_LAND];
int oilsInLine[MAX_LAND];
int N, M;
int miny, maxy;

// move to left, right, up, down
int dx[4] = {0, 0, -1, 1}, dy[4] = {-1, 1, 0, 0};

int dfs(vector<vector<int>> land, int x, int y) {
    int totalOilInField = 1;

    for (int i = 0; i < 4; i++) {
        int nx = x + dx[i], ny = y + dy[i];
        if (nx < 0 || N <= nx || ny < 0 || M <= ny || !land[nx][ny] || visited[nx][ny]) continue;
        
         // 해당 지점 방문 표시
        visited[nx][ny] = true;

        // 해당 구역이 걸쳐있는 가로줄 구함
        miny = min(ny, miny), maxy = max(ny, maxy);
        
        // 현재 구역의 석유 양 계산 
        totalOilInField += dfs(land, nx, ny);
    }

    return totalOilInField;
}

int solution(vector<vector<int>> land) {
    int answer = 0;

    N = land.size(), M = land[0].size();

    for (int i = 0; i < N; i++) {
        for (int j = 0; j < M; j++) {
            if (!land[i][j] || visited[i][j]) continue;
            
            miny = MAX_LAND, maxy = -1;
            
            // 해당 구역의 석유 양 추출 및 해당 라인에 석유 양 저장
            int amount = dfs(land, i, j);        
            for (int k = miny; k <= maxy; k++)
                oilsInLine[k] += amount;
        }
    }
    
    // 가로 줄을 순회하며 가장 많은 양을 시추할 수 있는 열 구함
    for (int i = 0; i < M; i++)
        answer = max(answer, oilsInLine[i]);
    return answer;
}

 


코드 3 - 효율성 테스트 실패

- 탐색 방법을 DFS -> BFS로 바꿨다.

- 석유 구역의 범위에 값을 모두 저장하는 것이 아닌, 양 끝에만 표시하여 반복문을 한 번만 돌아 답을 구하는 방식 (누적합)으로 바꿨다.

 

효율성 테스트 2, 3번 실패

시간 초과면 초과지 실패는 도대체 뭘까...
-------> 구역의 끝을 표시할 때 max_y 값을 구해 max_y + 1 인덱스에 값을 저장했는데, 배열의 크기가 주어진 MAX 값까지라 실패였다. MAX_LAND의 크기를 키워주니 시간초과 오류로 바뀜

#include <string>
#include <vector>
#include <algorithm>
#include <queue>

using namespace std;

#define MAX_LAND 500
typedef pair<int,int> pp;


bool visited[MAX_LAND][MAX_LAND];
int oils[MAX_LAND];
int N, M;
int miny, maxy;
queue <pp> Q;

// move to left, right, up, down
int dx[4] = {0, 0, -1, 1}, dy[4] = {-1, 1, 0, 0};

int bfs(vector<vector<int>> land, int x, int y) {
    int total = 0;

    Q.push({x, y});
    visited[x][y] = true;
    
    while (!Q.empty()) {
        x = Q.front().first, y = Q.front().second;
        Q.pop();
        
        total++;
        
       // 해당 구역이 걸쳐있는 가로줄 구함
        miny = min(y, miny), maxy = max(y, maxy);
        
        for (int i = 0; i < 4; i++) {
            int nx = x + dx[i], ny = y + dy[i];
            if (nx < 0 || N <= nx || ny < 0 || M <= ny || !land[nx][ny] || visited[nx][ny]) continue;

            // 해당 지점 방문 표시
            visited[nx][ny] = true;
            Q.push({nx, ny});
        }
    }
    return total;
}

int solution(vector<vector<int>> land) {
    int answer = 0;

    N = land.size(), M = land[0].size();

    for (int i = 0; i < N; i++) {
        for (int j = 0; j < M; j++) {
            if (!land[i][j] || visited[i][j]) continue;
            
            miny = MAX_LAND, maxy = -1;
            
            // 해당 구역의 석유 양 추출 및 해당 구역의 가로 범위 저장
            int amount = bfs(land, i, j);       
            oils[miny] += amount, oils[maxy + 1] -= amount;
        }
    }
    
    int oilsInLine = 0;
    for (int i = 0; i < M; i++) {
        oilsInLine += oils[i];
        answer = max(answer, oilsInLine);
    }
        
    return answer;
}

 


코드 4 - 답안 코드 (제일 위의 코드와 동일)

코드 3에서 주어진 land 벡터를 함수에 불러올 때 참조자로 불러오니 통과했다. (해당 부분만 변경)

앞으로도 참조자를 쓰는 습관을 들여야겠다..