KimMinJun
Coding Note
KimMinJun
전체 방문자
오늘
어제
  • 분류 전체보기 (513) N
    • Project (1)
      • blog (1)
    • CS (1)
    • Web (29)
      • Vanilla JS (13)
      • TS (2)
      • React (7)
      • Next.js (5)
      • ETC (1)
    • Docker (14)
    • Git (5)
    • ALGORITHM (11)
      • 정렬 (6)
      • 최단경로 (1)
      • 자료구조 (1)
      • 슬라이딩 윈도우 (1)
      • etc (2)
    • PS (437) N
      • 백준 (187)
      • Programmers (110) N
      • CodeUp (21)
      • STL (3)
      • 제코베 JS 100제 (50)
      • SWEA (0)
      • LeetCode (65)
    • IT (1)
    • React 공식문서 (번역, 공부) (11)
      • Quick Start (2)
      • Installation (0)
      • Describing the UI (9)
      • Adding Interactivity (0)
      • Managing State (0)
      • Escape Hatches (0)
    • Next.js 공식문서 (번역, 공부) (3)
      • Getting Started (2)
      • Building Your Application (1)

블로그 메뉴

  • 홈
  • 태그
  • 방명록
  • 관리

공지사항

인기 글

태그

  • js
  • recursion
  • LeetCode
  • 수학
  • Level 2
  • Level 0
  • 제코베 JS 100제
  • tree
  • programmers
  • 백준
  • C++
  • C
  • 다이나믹 프로그래밍
  • Level 1
  • Level1
  • 정렬
  • 그래프
  • codeup
  • string
  • 문자열

최근 댓글

최근 글

hELLO · Designed By 정상우.
KimMinJun

Coding Note

Programmers / Level 3 / 경주로 건설 / JS
PS/Programmers

Programmers / Level 3 / 경주로 건설 / JS

2025. 9. 4. 23:20

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

 

프로그래머스

SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프

programmers.co.kr

 

문제분석

직선도로에서는 100의 비용이 들고,

코너에서는 500의 비용이 든다.

처음에 헷갈려서 틀렸었는데, '코너를 만드는데 드는 비용'이 500이므로,

코너를 만들고 앞으로 한 칸 나아가려면 총 600의 비용이 든다.

 

또한, 어느 방향에서 왔는지에 따라 같은 칸이라도 비용이 다르게 들 수 있다.

따라서 일반적인 방문처리를 하는 bfs로는 풀이가 불가능하다.

 

풀이

const EMPTY = 0;
const WALL = 1;

const STRAIGHT_COST = 100;
// 코너를 건설하는 값 + 앞으로 한 칸 나아가는 값
const CORNER_COST = 500 + STRAIGHT_COST;

// 상, 우, 하, 좌
const dr = [-1, 0, 1, 0];
const dc = [0, 1, 0, -1];

let N;

function isValid(row, col) {
  return 0 <= row && row < N && 0 <= col && col < N;
}

function bfs(board) {
  // [행, 열, 방향, 금액]
  const queue = [];
  const visited = Array.from({ length: N }, () =>
    Array.from({ length: N }, () => Array(4).fill(Infinity))
  );

  if (board[1][0] === EMPTY) {
    queue.push([1, 0, 2, STRAIGHT_COST]);
    visited[1][0][2] = STRAIGHT_COST;
  }
  if (board[0][1] === EMPTY) {
    queue.push([0, 1, 1, STRAIGHT_COST]);
    visited[0][1][1] = STRAIGHT_COST;
  }

  while (queue.length > 0) {
    const [curRow, curCol, curDir, curCost] = queue.shift();

    for (let d = 0; d < 4; d++) {
      const nr = curRow + dr[d];
      const nc = curCol + dc[d];

      // 배열 범위를 벗어날 경우 스킵한다.
      if (!isValid(nr, nc)) {
        continue;
      }
      // 벽일 경우 스킵한다.
      if (board[nr][nc] === WALL) {
        continue;
      }

      // 이전과 다른 방향일 경우, 코너이기 때문에 비용이 증가한다.
      const newCost =
        curDir === d ? curCost + STRAIGHT_COST : curCost + CORNER_COST;

      // 이미 더 적은 비용으로 방문한적이 있다면 스킵한다.
      if (visited[nr][nc][d] <= newCost) {
        continue;
      }

      visited[nr][nc][d] = newCost;
      queue.push([nr, nc, d, newCost]);
    }
  }

  let minCost = Math.min(...visited[N - 1][N - 1]);
  return minCost;
}

function solution(board) {
  N = board.length;

  let minCost = bfs(board);
  return minCost;
}

 

`isValid(row, col)`

function isValid(row, col) {
  return 0 <= row && row < N && 0 <= col && col < N;
}

현재 있는 행 또는 열이 배열의 범위를 벗어나지 않았는지 판단하는 함수이다.

 

`bfs(board)`

function bfs(board) {
  // [행, 열, 방향, 금액]
  const queue = [];
  const visited = Array.from({ length: N }, () =>
    Array.from({ length: N }, () => Array(4).fill(Infinity))
  );

  if (board[1][0] === EMPTY) {
    queue.push([1, 0, 2, STRAIGHT_COST]);
    visited[1][0][2] = STRAIGHT_COST;
  }
  if (board[0][1] === EMPTY) {
    queue.push([0, 1, 1, STRAIGHT_COST]);
    visited[0][1][1] = STRAIGHT_COST;
  }

  while (queue.length > 0) {
    const [curRow, curCol, curDir, curCost] = queue.shift();

    for (let d = 0; d < 4; d++) {
      const nr = curRow + dr[d];
      const nc = curCol + dc[d];

      // 배열 범위를 벗어날 경우 스킵한다.
      if (!isValid(nr, nc)) {
        continue;
      }
      // 벽일 경우 스킵한다.
      if (board[nr][nc] === WALL) {
        continue;
      }

      // 이전과 다른 방향일 경우, 코너이기 때문에 비용이 증가한다.
      const newCost =
        curDir === d ? curCost + STRAIGHT_COST : curCost + CORNER_COST;

      // 이미 더 적은 비용으로 방문한적이 있다면 스킵한다.
      if (visited[nr][nc][d] <= newCost) {
        continue;
      }

      visited[nr][nc][d] = newCost;
      queue.push([nr, nc, d, newCost]);
    }
  }

  let minCost = Math.min(...visited[N - 1][N - 1]);
  return minCost;
}

bfs로 얻은 결과를 반환해주는 함수이다.

코드가 한 번에 보기 어려우므로 쪼개서 살펴보자.

 

// [행, 열, 방향, 금액]
const queue = [];

bfs의 기본 시작은 큐를 만들어주고, 방문 처리할 배열을 만들어주는 것이다.

큐에는 [행, 열, 방향, 금액]을 넣어준다.

 

여기서 방향은 간단히 방향 배열의 인덱스로 판단해준다. 

(상: 0, 우: 1, 하: 2, 좌: 3)

// 상, 우, 하, 좌
const dr = [-1, 0, 1, 0];
const dc = [0, 1, 0, -1];

 

const visited = Array.from({ length: N }, () =>
  Array.from({ length: N }, () => Array(4).fill(Infinity))
);

다만, 여기서 방문 처리할 배열은 기본 2차원이 아닌 3차원으로 만들어준다.

현재 칸에 도달하면, 몇 번의 코너를 거쳤는지에 따라 총 가격이 다르게 된다.

따라서 현재 칸의 어느 방향에서 왔는지를 각 4칸(상, 우, 하, 좌)에 저장해주는 것이다.

 

  if (board[1][0] === EMPTY) {
    queue.push([1, 0, 2, STRAIGHT_COST]);
    visited[1][0][2] = STRAIGHT_COST;
  }
  if (board[0][1] === EMPTY) {
    queue.push([0, 1, 1, STRAIGHT_COST]);
    visited[0][1][1] = STRAIGHT_COST;
  }

다음으로, 첫 시작을 넣어준다.

처음 시작은 무조건 [0][0]에서 한다고 문제에 나와있다.

그러면, 다음으로 갈 수 있는 칸은 바로 아래 칸이나 오른쪽 칸이다.

무조건 갈 수 있는건 아니고,

해당 칸이 벽으로 막혀있지 않은지, 즉 빈 칸인 경우에만 큐에 넣어주고 방문처리를 해준다.

 

  while (queue.length > 0) {
    const [curRow, curCol, curDir, curCost] = queue.shift();

    for (let d = 0; d < 4; d++) {
      const nr = curRow + dr[d];
      const nc = curCol + dc[d];

      // 배열 범위를 벗어날 경우 스킵한다.
      if (!isValid(nr, nc)) {
        continue;
      }
      // 벽일 경우 스킵한다.
      if (board[nr][nc] === WALL) {
        continue;
      }

      // 이전과 다른 방향일 경우, 코너이기 때문에 비용이 증가한다.
      const newCost =
        curDir === d ? curCost + STRAIGHT_COST : curCost + CORNER_COST;

      // 이미 더 적은 비용으로 방문한적이 있다면 스킵한다.
      if (visited[nr][nc][d] <= newCost) {
        continue;
      }

      visited[nr][nc][d] = newCost;
      queue.push([nr, nc, d, newCost]);
    }
  }

bfs의 핵심 로직이다.

다른건 다 같지만, 마지막 조건 검사 부분이 일반적인 bfs와 다르다.

만약 현재 진행중이던 방향과 다른 방향이라면,

코너를 돌았다는 뜻이므로, 해당 경우에만 코너 값을 더해준다.

그리고 이미 더 적은 비용으로 방문한적이 있다면 스킵한다.

 

let minCost = Math.min(...visited[N - 1][N - 1]);

마지막으로, 도착지점의 4칸중에서 최소값을 찾아서 반환해주면 된다.

저작자표시 (새창열림)

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

Programmers / Level 2 / 전력망을 둘로 나누기 / JS  (0) 2025.09.05
Programmers / Level 3 / 네트워크 / JS  (0) 2025.09.02
Programmers / Level 3 / 섬 연결하기 / JS  (0) 2025.09.01
Programmers / Level 3 / 표 편집 / JS  (3) 2025.08.19
    'PS/Programmers' 카테고리의 다른 글
    • Programmers / Level 2 / 전력망을 둘로 나누기 / JS
    • Programmers / Level 3 / 네트워크 / JS
    • Programmers / Level 3 / 섬 연결하기 / JS
    • Programmers / Level 3 / 표 편집 / JS
    KimMinJun
    KimMinJun

    티스토리툴바