junior developer :)/알고리즘 & 코딩테스트

[프로그래머스]그리디(탐욕법)_조이스틱(Level 2/JS)

ㅁ윤슬ㅁ 2023. 7. 11. 16:50
728x90
반응형

오늘은 그리디 알고리즘에 대해 적어보려고 한다.

그리디 알고리즘은 탐욕법이라고도 하며 현재 선택이 나중에 미칠 영향에 대해서는 고려하지 않고 현재 상황에서 지금 당장 좋은 것을 고르는 방법을 의미한다.

프로그래머스에서 해당하는 문제로는 Level 2 조이스틱 문제를 풀이해봤다. 

조이스틱
문제 설명
조이스틱으로 알파벳 이름을 완성하세요.
맨 처음엔 A로만 이루어져 있습니다.
ex) 완성해야 하는 이름이 세 글자면 AAA, 네 글자면 AAAA조이스틱을 각 방향으로 움직이면 아래와 같습니다.
▲ - 다음 알파벳
▼ - 이전 알파벳 (A에서 아래쪽으로 이동하면 Z로)
◀ - 커서를 왼쪽으로 이동 (첫 번째 위치에서 왼쪽으로 이동하면 마지막 문자에 커서)
▶ - 커서를 오른쪽으로 이동 (마지막 위치에서 오른쪽으로 이동하면 첫 번째 문자에 커서)

예를 들어 아래의 방법으로 "JAZ"를 만들 수 있습니다.
- 첫 번째 위치에서 조이스틱을 위로 9번 조작하여 J를 완성합니다.
-조이스틱을 왼쪽으로 1번 조작하여 커서를 마지막 문자 위치로 이동시킵니다.
-마지막 위치에서 조이스틱을 아래로 1번 조작하여 Z를 완성합니다.
따라서 11번 이동시켜 "JAZ"를 만들 수 있고, 이때가 최소 이동입니다.
만들고자 하는 이름 name이 매개변수로 주어질 때, 이름에 대해 조이스틱 조작 횟수의 최솟값을 return 하도록 solution 함수를 만드세요.
제한 사항name은 알파벳 대문자로만 이루어져 있습니다.name의 길이는 1 이상 20 이하입니다.

정말 어려웠던 문제..

오랜 시간을 쏟다가 검색을 해봤는데, 다들 최근 추가된 테스트케이스에서 애를 먹는 듯 보였다.

처음 내가 시도했던 방식으로는 17-20번이 통과되지 않은 상태..  
매 과정마다 콘솔을 찍어보니 해당 풀이의 문제는 위치 이동에서 발생한 듯 보였다.

결국 완성된 코드는 아래와 같다.

function solution(name) {

    let updown = 0;
    let move = name.length-1;
    let Aindex = 0;
    let AtoZ = Array(name.length).fill("A").join('');

    for(let i = 0; i < name.length; i++){
        let asciiName = name.charCodeAt(i)
        let asciiChar = AtoZ.charCodeAt(i)

        if(asciiName <= 78){
            updown+= asciiName - asciiChar
        }
        else{ 
            updown+= 91 - asciiName;
        }

        // A 위치 기억하기
        Aindex = i;
        while(name.at(Aindex) == "A"){
            Aindex += 1;

        }
        
        if(i == 0){
           move = Math.min(name.length-Aindex, move);
        }
        else {
            move = Math.min(2*(i-1) + name.length-Aindex, move);

            move = Math.min(move, i-1 + (name.length-Aindex)*2);

        }
    }
    return updown + move
}

이 코드에서 알파벳을 바꾸는 조작을 하는 부분은 간단하다.
아스키 코드로 바꾼 알파벳과 처음 A로 지정해준 문자열과 비교해서 차이만큼을 더해주면 된다.
단지 이 부분에서 주의할 점은 알파벳 중간지점 ("M") 뒤로는 뒤에서 부터 가는 것이 더 적게 이동할 수 있는 방법이기 때문에 91에서 빼주는 방법으로 계산해줬다.

문제가 되는 부분은 좌우로 위치를 이동하는 부분이였다.
이 과정에서 A의 위치를 기억한 후 A가 연속되는 갯수를 기반으로 A기준 뒷부분의 길이와 A기준 앞부분의 길이를 판단할 수 있었다.

그 후에 순서대로 가는 것과 뒤로 돌아가는 것 중 이동 수가 적은 것을 선택. 만약 연속된 A의 앞쪽보다 뒷쪽이 짧은 경우, 뒷쪽부터 조작한다.
만약 BBBAAB 처럼 처음부터 뒷부분을 먼저 입력하는 것이 빠르다는 조건을 넣기 위해 마지막 코드를 추가했다.

그렇게 계산해서 나온 값과 알파벳을 바꿀 때의 횟수를 더하면 최소이동수가 나오게 된다.

 

기존 코드와 차이점으로는
1. 연속되는 A의 갯수를 확인하지 않아 앞쪽, 뒷쪽 중 어디가 더 빠른지에 대한 계산이 힘들었고,
2. 처음부터 뒷부분을 먼저 입력하는 것이 더 빠른 경우를 고려하지 않았다.

드디어 100점....


참고

https://velog.io/@jeeseob5761/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%A1%B0%EC%9D%B4%EC%8A%A4%ED%8B%B1

728x90
반응형