[프로그래머스/탐욕법(Greedy)/2] 조이스틱 (JS)

진문장·2021년 9월 5일
0

출처: 프로그래머스 코딩테스트 조이스틱 (Greedy) 2번째 문제
(https://programmers.co.kr/learn/courses/30/lessons/42862)

문제 설명

조이스틱으로 알파벳 이름을 완성하세요. 맨 처음엔 A로만 이루어져 있습니다.
ex) 완성해야 하는 이름이 세 글자면 AAA, 네 글자면 AAAA

조이스틱을 각 방향으로 움직이면 아래와 같습니다.

▲ - 다음 알파벳
▼ - 이전 알파벳 (A에서 아래쪽으로 이동하면 Z로)
◀ - 커서를 왼쪽으로 이동 (첫 번째 위치에서 왼쪽으로 이동하면 마지막 문자에 커서)
▶ - 커서를 오른쪽으로 이동

예를 들어 아래의 방법으로 "JAZ"를 만들 수 있습니다.

  • 첫 번째 위치에서 조이스틱을 위로 9번 조작하여 J를 완성합니다.
  • 조이스틱을 왼쪽으로 1번 조작하여 커서를 마지막 문자 위치로 이동시킵니다.
  • 마지막 위치에서 조이스틱을 아래로 1번 조작하여 Z를 완성합니다.

따라서 11번 이동시켜 "JAZ"를 만들 수 있고, 이때가 최소 이동입니다.
만들고자 하는 이름 name이 매개변수로 주어질 때, 이름에 대해 조이스틱 조작 횟수의 최솟값을 return 하도록 solution 함수를 만드세요.

제한사항

  1. name은 알파벳 대문자로만 이루어져 있습니다.
  2. name의 길이는 1 이상 20 이하입니다.

풀이 방법

  1. name을 만들 때 해당 글자를 바꿨는 지 안바꿨는 지를 체크할 visited 배열을 만든다.
  2. move 라는 재귀함수를 만든다. move 함수를 실행하면 결과적으로 name이 완성된다.
  3. move 함수 에서는 한글자씩 이동하며 이동방향은 매개변수로 주어진다.
  4. 해당 글자를 만들기 위한 카운트를 answer에 더하고 다음 방향을 결정한다.
  5. 방향은 처음엔 오른쪽이고 방향을 결정할때는 그다음에 방향에 'A' 즉 초기 값이 지금 진행한 글자 수보다 많을 경우 방향을 왼쪽으로 바꾸게 된다.
  6. 모든 글자를 바꾸는게 성공하면 재귀함수를 종료한다.
  7. answer -1 의 값을 리턴한다.

소스 코드

function solution(name) {
    let answer = 0
    const INITIAL_CHAR = "A",
        LEFT = -1,
        RIGHT = 1
    const visited = new Array(name.length).fill(false)

    const move = (idx, direction, visited) => {
        if (isVisitedAll(visited)) return
        visited[idx] = true
        answer += getCharChangeCnt(INITIAL_CHAR, name[idx])

        if (direction === RIGHT) {
            if (name[idx + 1] === "A") {
                const aCnt = getACnt(name, idx + 1)
                if (idx + aCnt === name.length) return
                if (isGoToBack(name.length, idx, aCnt)) {
                    pass(visited, idx, aCnt)
                    answer += idx + 1
                    move(name.length - 1, LEFT, visited)
                    return
                }
            }
        }
        answer++
        move(idx + direction, direction, visited)
    }
    
    move(0, RIGHT, visited)
    return answer - 1
}
const isVisitedAll = (visited) => visited.every((el) => el === true)
const getCharChangeCnt = (a, b) => {
    const cnt = b.charCodeAt(0) - a.charCodeAt(0)
    if (cnt > 12) {
        return 26 - cnt
    }
    return cnt
}
const getACnt = (str, idx) => {
    let cnt = 0
    while (str[idx++] === "A") {
        cnt++
    }
    return cnt
}
const pass = (visited, idx, aCnt) => {
    for (let i = idx; i <= idx + aCnt; i++) {
        visited[i] = true
    }
}
const isGoToBack = (size, idx, aCnt) => {
    const processCnt = idx * 2
    const remainCnt = size - 1 - idx - aCnt
    return size > processCnt + remainCnt
}

후기

이 문제를 풀면서 이것이 그리디한 문제인지 조금 혼란이 왔다. 아직까지도 이게 그리디한 문제인지 잘은 모르겠다.

0개의 댓글