Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

4-kangrae-jo #12

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open

4-kangrae-jo #12

wants to merge 5 commits into from

Conversation

kangrae-jo
Copy link
Collaborator

🔗 문제 링크

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

✔️ 소요된 시간

1h 40m

✨ 수도 코드

문제

오늘 문제는 BFS로 통일했습니다. 연속적으로 하면 감이 잘 잡힐 것 같아서...

문제에 첨부된 이미지가 많아서 문제에서 요구하는 바를 이해하는 것에는 어려움이 없었습니다.
아마 여러분도 문제를 읽으면서 동시에 이해를 했다고 생각합니다.

그럼 바로 문제 풀이의 핵심 2가지를 소개하겠습니다.
사실 진짜진짜 핵심이 하나 더 있는데 나중에 알려드리겠습니다.

핵심 points!!

  1. 문제에서 제공하는 rectangle의 제약조건이 좋다.
    2개로 분리된 직사각형이 없다거나, 변이 겹치는 경우가 없다거나, 꼭짓점끼리 만난다거나... 등등

  2. rectangle변수에 배열에 사각형은 순서가 있다.
    꺼내서 내가 만든 board배열에 옮길때 WALL로 가려야하는부분, PATH인 부분을 잘 구분해야한다.
    (WALL == rectangle 내부, PATH == 테두리)

스크린샷 2024-10-05 22 18 02

위 그림에서는 직사각형이 순서대로 쌓여 있어서 길 처럼 보이는 부분이 있다.
하지만 이것은 약간 페이크이다.
우리는 '테두리'로만 이동할 수 있다. (이해가 잘 되시나요?)

직사각형이 다 쌓이기전에 테두리였던 부분은 다른 부분으로 덮힐 수 있다.
(그 반대의 경우도 있음)

모든 경우를 나타내 보자면 다음과 같다.
PATH일때 PATH면 PATH
WALL일때 PATH면 WALL
PATH일때 WALL면 WALL
WALL일때 WALL면 WALL

여기까지가 문제의 핵심이고 이를 이해하셨다면 다음 단계로 넘어오는 것을 추천드립니다.
(BFS에 대한 설명은 직전 pr에서 했으니 pass~)

수도 코드

  1. rectangle 배열에서 직사각형을 board로 옮기기
  2. 주어진 character좌표를 queue에 넣기
  3. 현재 queue에 들어있는 좌표를 하나씩 꺼내기
    3-1. 꺼낸 좌표 기준으로 상하좌우 PATH인지 확인
    3-2. PATH가 맞다면 VISITED처리 후 queue에 넣기
  4. 2번 과정 반복하며 item좌표 == character좌표이면 answer반환하며 종료
    // init
    // 수도코드 1번 과정

    // BFS
    q.push(make_pair(characterY, characterX));

    while (!q.empty()) {
        int size = q.size();

        while (size--){
            int cY = q.front().first;
            int cX = q.front().second;
    
            board[cY][cX] = VISITED;
            q.pop();
    
            if (check(cX, cY, itemX, itemY)) return answer / 2;
    
            for (int dir = 0; dir < 4; dir++) move(cX, cY, dir);
        }
        answer++;
    }

    return answer / 2;

구조는 직전 pr인 토마토문제와 매우매우 비슷합니다.
제가 미로문제같은 좌표를 다루는 BFS를 풀 때 자주쓰는 코드 구조입니다.

추가 설명

그런데 이 설명에 따른 구현으로는 아직 부족합니다.
제가 숨겨진 진짜진짜 핵심이 있다고 했었죠. 다음과 같습니다.

"좌표와 관련된 것 모두 2배"

이게 무슨 소리지 하실 수 도 있습니다. 제가 그랬습니다.

이제 설명하겠습니다.
문제를 잘 구현했는데 몇가지 테스트 케이스에서 자꾸 틀리는 것입니다.

답답해서 그림을 그려보며, 배치된 직사각형을 평행이동하며 점점 좁혀나갔는데, 뭔가 번뜩였습니다.
'만약 이렇게 된다면?' 내 의도와는 다르게 구현될 수 있다는 것을 알았습니다.
image
(혹시 이게 왜 문제가 되는지 모르시겠는 분은 BFS가 어떻게 동작하고 있는지,
우리가 가야하는 PATH는 뭔지를 알아보시는게 좋을 것 같아요.
그래도 모르시겠으면 코멘트 하십시오. 정성껏 답변해드립니다.)

이거를 해결할 수 있는 방법이 도저히 생각나지 않았습니다.
해결방법이 나왔고 그 방법이 제가 말한 숨겨진 핵심입니다.
(결국 구글링..)

사실 눈치 빠르신 분들은 수도코드 설명에서 알아차리셨을 겁니다.
수도 코드 1번 과정을 비교적 빠르게 넘어갔고, answer을 나누기 2 했다는 것 떄문입니다.

어쨌거나 좌표를 2배로 늘리면 다음과 같은 좌표가 됩니다.
그럼 이제 간격이 생기며 원래 의도 하던 대로 코드가 동작할 것입니다.
스크린샷 2024-10-05 22 37 28
( 직접 그리고 싶었는데 머리가 안돌아가네요. 아래 첨부된 블로그에서 퍼온 이미지입니다.)

📚 새롭게 알게된 내용

좌표에 2를 곱하는 건 생각도 못했는데 놀랐습니다.
그래도 구글링 하기전에 '어디서 문제가 생기는지' 까지는 알아내서 기분은 좋네요.

https://velog.io/@hhj3258/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4C-%EC%9C%84%ED%81%B4%EB%A6%AC-%EC%B1%8C%EB%A6%B0%EC%A7%80-11%EC%A3%BC%EC%B0%A8-%EC%95%84%EC%9D%B4%ED%85%9C-%EC%A4%8D%EA%B8%B0
이 블로그 보고 배웠습니다.
제 설명이 이해가 안가는 분은 이 블로그 한 보시면 좋습니다.

@kangrae-jo kangrae-jo changed the title 2024-10-05 아이템 줍기 4-kangrae-jo Oct 5, 2024
@kangrae-jo kangrae-jo self-assigned this Oct 5, 2024
Copy link
Collaborator

@g0rnn g0rnn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니다!! 조금 늦었지만 열심히 풀어보았씁니다.!
그리고 리드미가 계속 충돌이 일어나는거 같은데 브런치를 팔 때 merge가 안된 main에서 파게 되면 기존에 작성했떤 README가 없어져서 충돌이 일어날 수 있는거 같아요. 가장 최신 브랜치나 최신 로컬 파일에서 브런치를 파는 것을 추천드립니당 :)
(아니면 수기로 2차시부터 입력해도 되요)

Comment on lines +58 to +73
while (!q.empty()) {
int size = q.size();

while (size--){
int cY = q.front().first;
int cX = q.front().second;

board[cY][cX] = VISITED;
q.pop();

if (check(cX, cY, itemX, itemY)) return answer / 2;

for (int dir = 0; dir < 4; dir++) move(cX, cY, dir);
}
answer++;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

메서드로 분리하니 보기가 편하네용 👍

Comment on lines +17 to +20
bool check(int cX, int cY, int itemX, int itemY) {
if (cX == itemX && cY == itemY) return true;
else return false;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 함수는 조금 더 짧아도 되지 않을까요?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오늘 회의가 없었다면 해결하지 못했을만한 문제였습니다..ㄷㄷ 재밌는 문제를 찾아주셔서 감사해용 😄
좌표를 두배하는 연산에 대해서는 조금더 간결하게 시도했습니다!!
저는 처음 사각형을 좌표위에 그리는게 조금 헷갈리더라구요.. 그리고 bfs/dfs도 많이 약하다는걸 알게되는 문제였습니다.

code
#include <string>
#include <vector>
#include <queue>

#define EMPTY 0
#define OUTSIDE 1
#define INSIDE 2
#define VISITED 3

using namespace std;

vector<vector<int>> board;
queue<pair<int, int>> q;
int offset[4][2] = { {1, 0}, {0, 1}, {-1, 0}, {0, -1}};

bool canMove(int _x, int _y, int dir) {
    int x = _x + offset[dir][0];
    int y = _y + offset[dir][1];
    if(x>100 || x<0 || y>100 || y<0 || board[y][x] == VISITED) return false;
    return board[y][x] == OUTSIDE;
}

void makeBoard(const vector<vector<int>>& rectangle) {
    for (int k=0; k<rectangle.size(); k++) {
        int s_x = rectangle[k][0];
        int s_y = rectangle[k][1];
        int d_x = rectangle[k][2];
        int d_y = rectangle[k][3];
        
        for(int i=s_x;i<=d_x;i++) {
            for(int j=s_y;j<=d_y;j++) {
                if(board[j][i] == INSIDE) continue;
                else if(i == s_x || i == d_x || j == s_y || j == d_y) board[j][i] = OUTSIDE;
                else board[j][i] = INSIDE;
            }
        }
    }
}

void stretch(vector<vector<int>>& rect, int &cx, int &cy, int &ix, int &iy) {
    for(auto& r : rect) {
        r[0] *= 2;
        r[1] *= 2;
        r[2] *= 2;
        r[3] *= 2;
    }
    cx*=2; cy*=2; ix*=2; iy*=2;
}

int solution(vector<vector<int>> rectangle, int cx, int cy, int itemX, int itemY) {
    int answer = 0;
    board = vector<vector<int>>(101, vector<int>(101, EMPTY));
    stretch(rectangle, cx, cy, itemX, itemY);
    makeBoard(rectangle);
    
    q.push(make_pair(cx, cy));
    while(!q.empty()) {
        int size = q.size();
        
        while(size--) {
            int cur_x = q.front().first;
            int cur_y = q.front().second;
            board[cur_y][cur_x] = VISITED;
            q.pop();
        
            if(cur_x == itemX && cur_y == itemY) return answer/2;
            for(int dir = 0; dir<4; dir++) {
                if(canMove(cur_x, cur_y, dir)) {
                    int nx = cur_x + offset[dir][0];
                    int ny = cur_y + offset[dir][1];
                    q.push(make_pair(nx,ny));
                }
            }
        }
        answer++;
    }
    
    
    return answer;
}

Copy link
Collaborator

@kokeunho kokeunho left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

정성스러운 PR과 대면 미팅에서 설명 덕분에 잘 이해할 수 있었습니다.
특히 좌표들을 2배해서 올바른 경로를 구하는 것이 새롭고 재밌었습니다.
강래님 설명대로 코드를 짜기만 하는데도 실력이 부족해서 구현이 어렵네요...
앞으로 구현 연습을 우선으로 해야겠습니다.
수고하셨습니다!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants