간단한 문제 풀이 테크닉
들어가며
이번에는 간단한 문제 테크닉에 대해서 배워보고자 합니다.
문제 테크닉이라니 벌써부터 두근두근 거리지 않나요?
이제부터는 앞에서 표현한 것보다 좀 더 어려운 예제들이 나오게 됩니다.
다만, 알고리즘과 문제풀이법에서 배웠던 것처럼 차근차근 풀다보면 의외로 쉽게 풀릴거에요!
어떤 테크닉을 배울 것인가?
이번 장에서 다룰 테크닉은 3가지입니다.
- 빈도수 세기
- 슬라이딩 윈도우
- 투 포인터
슬라이딩 윈도우와 투 포인터는 어떻게 보면 비슷한 면도 있어요.
그리고 이름이 거창할 뿐이지 사실 다들 일상생활에서 잘 사용하고 있는 테크닉들이랍니다?
그럼 이제부터 하나씩 살펴볼까요?
빈도수 세기
빈도수 세기란 무엇일까요? 말 그대로 빈도수를 세는 문제들에 대한 접근법을 말합니다.
알고리즘이란 뭐라고 했죠? 문제를 푸는 방식입니다.
그렇기에 다소 추상적일 순 있지만, 그래도 문제를 푸는 방법 혹은 문제에 접근하는 방법이라고 생각하며 바라보시면 좋을 듯 합니다.
하나 문제를 내볼게요.
Q. 문제 "djdyaac"에서 각 알파벳이 몇 번 나왔는지 출력하시오. 이때, 출력 양식은 다음과 같습니다. (출력 시 알파벳은 정렬하지 않아도 됩니다.)
<출력> d:2 j:1 y:1 a:2 c:1
이와 같은 문제가 있다고 해봅시다.
어떻게 접근할 수 있을까요?
문제에 대한 분석
잠깐 앞에서 배웠던 것을 적용해서 해석해볼까요?
먼저 입력은 무엇이고 출력은 무엇일까요?
입력은 "djdyaac"와 같은 알파벳으로만 이루어진 문자열이겠죠?
출력의 경우는 알파벳:횟수 로 각 줄에 하나씩 내보내는 겁니다.
그러면 이 문제를 어떻게 단순화 할 수 있을까요?
잠깐! 아래 글을 읽기전에 한번 생각해보시겠어요?
저라면 문자열을 "d", "j", "d", "y", "a", "a", "c" 이렇게 나눠서 볼 것 같아요.
그러면 문자열을 다루는 문제에서 문자를 여러번 다루는 문제로 바뀔 수 있어요!
문자 하나를 셀려면 어떻게 하면 될까요?
우리는 머리로 바 로 알지만 컴퓨터는 하나하나 다 무엇을 해야하는지 풀어서 설명을 해줘야하죠?
그러면 그렇게 지정을 해 봅시다.
문자를 세는 문제로 쪼갰으니, 많이 생각하지 말고 문자 하나만 놓고 봐요!
그러면 위 문제를 다시 정리하면,
입력은 문자열 하나, 출력은 그 문자열에 대한 횟수를 반환한다고 생각해볼 수 있겠죠?
이를 컴퓨터에게 시키면 어떻게 할까요?
입출력을 분석했고, 쪼개서 단순화 시켰으니 이제 할일은 의사코드를 작성해보는 겁니다. 컴퓨터가 이해할 수 있도록 우리가 재구성한다고 생각하면 되요!
잠시 여기서 한번 생각해보시겠어요?
저는 아래와 같이 해볼 것 같아요.
- 함수를 선언한다.
- 선언한 함수에 횟수를 저장할 변수를 선언한다.
- 입력은 문자열을 받는다.
- 이때, 공백(' ')도 문자열이나, 본 문제에는 해당이 되지 않으므로 예외로 처리하며, 영문을 세는 것이므로 그 외의 입력은 예외로 처리한다.
- 문자열이 들어오면 횟수를 저장할 변수에 + 1을 한다.
- 횟수를 저장한 변수를 리턴한다.
이렇게 작성해볼 것 같아요.
그리고 코드로 옮기면 문자 하나에 대한 카운트 함수가 만들게 된 것이죠.
그러면 이를 확장해보는 겁니다.
여러개의 문자에 대해서 반복하면 되지 않을까?
그리고 단순히 +1만 할게 아니라, 전역 변수 등을 선언해서 각 문자에 맞춰서 해당 변수의 값을 변경하면 되지 않을까?
여러가지 해결법이 있겠지만, 이런식으로 확장해서 풀 수 있을 거에요.
여러가지 방법 중에서, 배열이나 전역변수 선언 같은 방법 말고 조금 더 테크닉적인 것을 다룰려고 해요.
이에 대해서 이제 알아볼까요?
값을 저장하는 방법
언어마다 다르긴 한데, 보통 이런 문제에 대해서는 key-value
쌍으로 이루어진 HashMap
이라는 것을 이용해서 구하곤 합니다.
하지만, 이는 나중에 배울 내용으로 당장 이에 대해서 다루고자 하는 것은 아니니까 걱정하지 않아도 되요.
그러면 어떻게 값을 저장하면 될까요?
방법은 되게 단순해요. 값과 1:1로 매칭되는 무언가를 만들어두고, 그것을 +해서 반환하면 되요.
전역변수여도 좋고, 배열이어도 좋아요.
절차지향이라면 C
를 기준으로 알파벳이 대문자 26개, 소문자 26개이니 대소문자를 구별한다고 하면 크기 52의 배열을 선언해서 전부 0을 넣고,
0~25번 원소를 A~Z라고 생각하고, 26~51번 원소를 a~z라고 생각해서 각각의 문자가 나올 때 해당 원소를 +1을 해주면 되죠.
객체지향 언어라고 한다면, 객체를 이용하면 됩니다!
외부에 별도로 값을 저장하는 변수를 선언하고 각 알파벳에 대응하는 속성을 생성한 후에, 객체의 속성의 값을 +해주는 것이죠.
참고로 속성은 객체에서 사용하는 변수를 가리킨다고 생각하시면 돼요!
{
a: 0,
b: 0,
c: 0,
...
x: 0,
y: 0,
z: 0
}
앞서서 문자열을 쪼갠 문자를 다룰 때, 함수안에 횟수를 저장할 변수를 선언했었잖아요?
이에 대한 내용을 위 객체의 프로퍼티를 +1 해주는 방향으로 구현하면 횟수를 셀 수 있겠죠?
방법의 차이는 있을 뿐 이처럼 값과 1:1로 대응하는 형태로 구현하는 게 빈도수 세기의 핵심입니다.
다만 여기까지 말하면, 단순 무식해보이잖아요?
이것의 매력은 어떻게 값을 저장할 것인가?하는 부분에서 발생해요.
가령, "djdyaac"를 다룬다고 하면 실제로 쓰이는 문자는 "d", "j", "y", "a", "c"로 5개 잖아요? 그런데, 여기에 쓰이지도 않는 "b", "d", "e"...와 같은 문자를 위해서 공간을 할당할 필요가 있을까요?
주어진 문자에 맞춰서 이를 셀 수 있게 만드는 게 빈도수 세기의 핵심입니다!
C
와 같은 언어라면, 동적할당과 같은 방법을 통해서 주어진 값에 따라서 저장하는 공간을 변화하게 할 수 있겠죠?
객체 지향의 경우라면, 프로퍼티 생성을 동적으로 해서 값을 넣어볼 것 같아요.
const count = {};
if (count["문자"] !== undefined) count["문자"] = 1;
else count["문자"] += 1;
자바스크립트 같은 경우는 배열의 원소를 동적으로 할당할 수 있어서, 위와 같이 짤 수 있겠죠?
자바나 파이썬 같은 언어라면 별도로 프로퍼티를 추가하는 방식으로 해서 구현하면 되요!
이에 대한 의사코드는 아래와 같답니다.
객체의 프로퍼티에 문자 세기와 관련된 요소가 없으면 문자 프로퍼티를 생성하고 1로 초기화한다.
객체의 프로퍼티에 문자 세기와 관련된 요소가 있으면 +1을 해준다.
한번 동적으로 할당해서 구현해보시겠어요?
그리고 이렇게 하는 방법에 익숙해지시면 된답니다!
생각보다 자주 쓰는 기본 테크닉이면서, 매우 중요한 테크닉이에요. 코딩 테스트를 보거나 프로그램을 작성하면 공간 복잡도, 즉 프로그램이 동작하면서 얼마나 메모리 용량을 차지하느냐도 중요하거든요!
근데 별거 없죠? 하지만 이 역시 빈도수나 횟수를 세는 문제 풀이 방법 중 하나이기에, 알고리즘이랍니다. ㅎㅎ.
각자 언어에 구현에 맡기지만, 참고를 위해 자바스크립트로 문자에 대한 횟수를 세는 부분에 대한 코드를 작성해둘게요! 참고하셔서 직접 위의 문제에 대한 답을 구현해보시면 될 것 같아요!
자바스크립트를 사용하는 분들을 위해서, 이렇게도 작성할 수 있다는 것을 보여주기 위해 제가 작성하는 방법대로 작성해둘게요!
const count = {};
function countWord(word) {
count[`${word}`] ? (count[`${word}`] += 1) : (count[`${word}`] = 1);
return count[`${word}`];
}
문자열 포매팅과, 삼항 연산자를 사용했으니, 참고하세요!
예제 풀이
원리에 대해 알았으니 직접 구현을 해봐야곘죠?
다음을 풀어보시겠어요?
Q. 문제
일종의 말장난으로 어떠한 단어의 문자를 재배열하여 다른 뜻을 가지는 다른 단어로 바꾸는 것을 애너그램(Anagram)이라고 한다.
예시로, chaos를 구성하는 문자의 순서를 바꾸어 애너그램하면 shaco가 되고, neo를 애너그램하면 one이 된다.
어떤 두 문자열이 주어졌을 때, 둘이 애너그램의 관계에 있는지를 보이는 코드를 작성해라.
- 예시1. 입력 : shaco, chaos / 출력 : true
예시2. 입력 : neo, coe / 출력 : false
추가로 이와 관련된 백준 문제도 있어서 첨부하니 백준으로 풀어도 괜찮아요!
백준 문제 링크
슬라이딩 윈도우
두번째로 배워볼 테크닉이자 알고리즘은 슬라이딩 윈도우(Sliding Window)입니다.
이에 대해서는 하나의 예제를 통해서 알아볼까요?
Q. 문제
3, 2, 1, 5, 4, 2, 6, 1 라는 연속된 숫자들이 주어질 때, 연속적인 3개의 숫자의 합이 최대가 될 때, 이때의 최대값은 무엇인가?
뒤의 내용을 읽기 전에 한번 풀어보시겠어요?
이에 대한 문제를 어떻게 풀 수 있을까요?