[프로그래머스 1레벨] 문자열 나누기

이민선(Jasmine)·2023년 1월 25일
0
post-thumbnail

문제 자체를 잘못 이해해서 헛발질을 계속 했다.
나는 첫 글자 x가 다음 글자를 읽을 때마다 계속 바뀐다고 잘못 이해했다.
예를 들어서 banana라면
b까지는 나와 같은 글자 b 1회 출몰, b가 아닌 글자 0회 출몰
a까지는 나와 같은 글자 a 1회 출몰, a가 아닌 글자 1회 출몰 => 여기까지 쪼개자!
라고 이해했는데 이 논리를 3번째 예시인
입출력 예 #3
s="aaabbaccccabba"인 경우 aaabbacc - ccab - ba와 같이 분해됩니다.
에 적용해보니 들어맞지 않는 것이었다.

첫 글자 x는 문자열이 쪼개질 때까지 고정이었던 것이다.
banana로 다시 설명하면,
첫 글자 x는 b로 고정.
ba까지 b와 같은 글자 1회 b와 다른 글자 1회 => 쪼개자!
다시 첫 글자 x는 그 다음 글자인 n으로 고정.
na까지 n과 같은 글자 1회 n과 다른 글자 1회 => 쪼개자!
....이런 식.
이 논리대로 하면 s="aaabbaccccabba" 예시에도 적용된다.
첫 글자 x="a"가 되고
aaabbacc까지
첫 글자 a는 4번 출몰 a와 다른 글자 4번 출몰 => 쪼개자!
...이런 식.

결국 수많은 헛발질 끝에 코드 작성 완료!

나의 코드

function solution(s) {
    let arr = [];
    let [firstLetter,firstIndex,same,dif] = [s[0],0,0,0];
    s.split("").forEach((v,i,a)=>{
        v === firstLetter ? same++ : dif++;
        if(same === dif){
            arr.push(s.substring(firstIndex,i+1));
            [firstLetter,firstIndex,same,dif] = [a[i+1],i + 1,0,0];
        }
    })
    return arr.join('').length === s.length ? arr.length : arr.length + 1;
}

s.split("")에 forEach문을 돌리기 전에 firstLetter, firstIndex, same, dif를 각각 구조분해 할당을 이용하여 초기화.
forEach문을 돌리면서 firstLetter와 현재 글자가 같으면 same을 1만큼 증가, 다르면 dif를 1만큼 증가. 두 개가 서로 같아지는 순간 arr에 현재 글자까지 싹둑 잘라 push.
그리고 firstLetter, firstIndex, same, dif를 각각 구조분해 할당을 이용하여 다시 초기화. 이 때 firstLetter는 현재 글자의 다음 글자가 되고, firstIndex는 현재 index의 다음 index가 되도록 재선언.
이렇게 하면 경우에 따라 마지막 부분이 arr에 들어가는 경우가 있고 못들어 가는 경우가 있음.
예를 들어 abracadabra는 마지막 부분이 끝글자 a인데, 이 때 firstLetter, firstIndex는 각각 a, 10이되어 forEach문이 마지막 순회를 하게 된다. 이때 a === firstLetter이므로 same++하여 same > dif가 되고 마지막 부분을 push 하지 않은 채로 forEach문을 종료한다.
그러므로, 원래 글자 보다 배열 내부로 들어간 단어의 총 알파벳 수가 더 적다면 (덜 들어갔다면) arr.length에 1을 추가하여 답 반환.

그런데 이 문제를 재귀함수로도 풀 수가 있었다.

function solution(s, count=0) {
    if(!s) return count
    let [first, ...rest] = s.split("")
    let countSame = 1
    let countDif = 0
    let i=0
    for(; i<rest.length; i++){
        if(rest[i] === first) countSame++
        else countDif++
        if(countSame === countDif) break
    }
    return solution(rest.slice(i+1).join(""), count+1)
}

count라는 인자를 추가하고 default 값으로 0을 주었다.
first와 나머지 글자들을 구조분해 할당으로 표현하였다.
그리고 내 코드와 다른 점은 first 글자를 제외하고 반복문을 돌린다는 점.
그래서 countSame할 때는 countDif할 때와 달리 1에서부터 시작한다. first 글자는 이미 countSame되었다고 치고 rest부분만 반복문을 돌리기 때문이다.
for문 안에서 rest 부분을 돌리며 countSame과 countDif가 같아지면 반복문을 빠져나온다.( break 사용)
이 때 count에 +1을 해준 채로 함수를 다시 돌리는데, 이 때는 s가 아니라 반복문 빠져나온 순간의 다음글자부터의 부분문자열을 매개변수로 전달한다.
이렇게 하면 반복문을 빠져나온 횟수와 count의 수가 일치하게 되고, 더 이상 남은 글자가 없으면 s = [""].slice(1).join("")는 빈 문자열이 되어 !s는 true가 되고 첫 문장에 의해 count를 반환하고 함수를 종료한다.

이 코드에서 가장 신기했던 점은 let i=0을 for문 바깥에 정의했다는 점이다. solution함수를 다음 번에 돌릴 때 for문에서 증가시켰던 i를 slice에서 사용하기 위해 for문 바깥에서도 i에 접근할 수 있어야하기 때문이다. 만약 let i=0을 for문 내부에 둔다면 i에 접근할 수 없고, 아래와 같이 레퍼런스 오류가 뜬다.

ReferenceError: i is not defined
profile
기록에 진심인 개발자 🌿

0개의 댓글