Groo
키패드 누르기 본문
안녕하세요, 오늘은 오랜만에 코딩 테스트 연습 문제에 대한 글을 작성하려고 합니다.
이번 문제는 2020년 카카오 인턴쉽에서 출제되었으며 조금 수준이 있는 문제라고 할 수 있습니다.
📚 문제 설명
스마트폰 전화 키패드의 각 칸에 다음과 같이 숫자들이 적혀 있습니다.
이 전화 키패드에서 왼손과 오른손의 엄지손가락만을 이용해서 숫자만을 입력하려고 합니다.
맨 처음 왼손 엄지손가락은 * 키패드에 오른쪽 엄지손가락은 # 키패드에 위치하며 엄지손가락의 사용 규칙은 다음과 같습니다.
1. 엄지손가락은 상하좌우 4가지 방향으로만 이동할 수 있으며 키패드 이동 한 칸은 거리로 1에 해당합니다.
2. 왼쪽 열의 3개의 숫자 1, 4, 7을 입력할 때는 왼쪽 엄지손가락을 사용합니다.
3. 오른쪽 열의 3개의 숫자 3, 6, 9를 입력할 때는 오른쪽 엄지손가락을 사용합니다.
4. 가운데 열의 4개의 숫자 2, 5, 8, 0을 입력할 때는 두 엄지손가락의 현재 키패드의 위치에서 더 가까운 엄지손가락을 사용합니다.
4-1. 만약 두 엄지손가락의 거리가 같다면 오른손잡이는 오른손 엄지손가락, 왼손잡이는 왼손 엄지손가락을 사용합니다.
순서대로 누를 번호가 담긴 배열 numbers, 왼손잡이인지 오른손잡이인 지를 나타내는 문자열 hand가 매개변수로 주어질 때, 각 번호를 누른 엄지손가락이 왼손인 지 오른손인 지를 나타내는 연속된 문자열 형태로 return 할 수 있도록 solution 함수를 완성하세요.
📸 제한 사항
1) numbers 배열의 크기는 1 이상 1,000 이하입니다.
2) numbers 배열 원소의 값은 0 이상 9 이하인 정수입니다.
3) hand는 left (왼손잡이) 또는 right (오른손잡이)를 의미합니다.
4) 왼손 엄지손가락 사용 시 L, 오른쪽 엄지손가락 사용 시 R을 순서대로 붙여 문자열로 반환하세요.
🗣 입출력 예시
numbers | hand | result |
[1, 3, 4, 5, 8, 2, 1, 4, 5, 9, 5] | "right" | "LRLLLRLLRRL" |
[7, 0, 8, 2, 8, 3, 1, 5, 7, 6, 2] | "left" | "LRLLRRLLLRR" |
[1, 2, 3, 4, 5, 6, 7, 8, 9, 0] | "right" | "LLRLLRLLRL" |
첫 번째 예시에서 순서대로 눌러야 할 번호의 목록은 [1, 3, 4, 5, 8, 2, 1, 4, 5, 9, 5]이고, 오른손잡이입니다. 그럼 지금부터 표를 활용하여 numbers 배열에 존재하는 각 숫자들을 어떤 손가락을 통해 선택하는지 천천히 순차적으로 자세히 살펴보도록 하겠습니다.
# 첫 번째 예시
왼손 손가락 위치 | 오른손 손가락 위치 | 눌러야 하는 숫자 | 사용한 손가락 | 부가 설명 |
* | # | 1 | L | 왼손으로 누릅니다. |
1 | # | 3 | R | 오른손으로 누릅니다. |
1 | 3 | 4 | L | 왼손으로 누릅니다. |
4 | 3 | 5 | L | 왼손 손가락 거리 : 1 오른손 손가락 거리 : 2 왼손으로 누릅니다. |
5 | 3 | 8 | L | 왼손 손가락 거리 : 1 오른손 손가락 거리 : 3 왼손으로 누릅니다. |
8 | 3 | 2 | R | 왼손 손가락 거리 : 2 오른손 손가락 거리 : 1 오른손으로 누릅니다. |
8 | 2 | 1 | L | 왼손으로 누릅니다. |
1 | 2 | 4 | L | 왼손으로 누릅니다. |
4 | 2 | 5 | R | 왼손 손가락 거리 : 1 오른손 손가락 거리 : 1 오른손 잡이입니다. 오른손으로 누릅니다. |
4 | 5 | 9 | R | 오른손으로 누릅니다. |
4 | 9 | 5 | L | 왼손 손가락 거리 : 1 오른손 손가락 거리 : 2 왼손으로 누릅니다. |
5 | 9 | - | - |
표를 위에서부터 아래로 해석하다 보면 어떤 구조로 프로그램이 작동하는지 전체적인 흐름을 알 수 있을 것입니다. 이전부터 계속 설명하지만 알고리즘 문제를 풀이할 때는 손으로 직접 해보는 것이 도움이 됩니다. 추가적으로 두 번째, 세 번째 예시도 풀어보겠습니다.
# 두 번째 예시
왼손 손가락 위치 | 오른손 손가락 위치 | 눌러야 하는 숫자 | 사용한 손가락 | 부가 설명 |
* | # | 7 | L | 왼손으로 누릅니다. |
7 | # | 0 | R | 왼손 손가락 거리 : 2 오른손 손가락 거리 : 1 오른손으로 누릅니다. |
7 | 0 | 8 | L | 왼손 손가락 거리 : 1 오른손 손가락 거리 : 1 왼손 잡이입니다. 왼손으로 누릅니다. |
8 | 0 | 2 | L | 왼손 손가락 거리 : 2 오른손 손가락 거리 : 3 왼손으로 누릅니다. |
2 | 0 | 8 | R | 왼손 손가락 거리 : 2 오른손 손가락 거리 : 1 오른손으로 누릅니다. |
2 | 8 | 3 | R | 오른손으로 누릅니다. |
2 | 3 | 1 | L | 왼손으로 누릅니다. |
1 | 3 | 5 | L | 왼손 손가락 거리 : 1 오른손 손가락 거리 : 1 왼손 잡이입니다. 왼손으로 누릅니다. |
5 | 3 | 7 | L | 왼손으로 누릅니다. |
7 | 3 | 6 | R | 오른손으로 누릅니다. |
7 | 6 | 2 | R | 왼손 손가락 거리 : 3 오른손 손가락 거리 : 2 오른손으로 누릅니다. |
7 | 2 | - | - |
# 세 번째 예시
왼손 손가락 위치 | 오른손 손가락 위치 | 눌러야 하는 숫자 | 사용한 손가락 | 부가 설명 |
* | # | 1 | L | 왼손으로 누릅니다. |
1 | # | 2 | L | 왼손 손가락 거리 : 1 오른손 손가락 거리 : 4 왼손으로 누릅니다. |
2 | # | 3 | R | 오른손으로 누릅니다. |
2 | 3 | 4 | L | 왼손으로 누릅니다. |
4 | 3 | 5 | L | 왼손 손가락 거리 : 1 오른손 손가락 거리 : 2 왼손으로 누릅니다. |
5 | 3 | 6 | R | 오른손으로 누릅니다. |
5 | 6 | 7 | L | 왼손 손가락 거리 : 2 오른손 손가락 거리 : 3 왼손으로 누릅니다. |
7 | 6 | 8 | L | 왼손 손가락 거리 : 1 오른손 손가락 거리 : 2 왼손으로 누릅니다. |
8 | 6 | 9 | R | 오른손으로 누릅니다. |
8 | 9 | 0 | L | 왼손 손가락 거리 : 1 오른손 손가락 거리 : 2 왼손으로 누릅니다. |
0 | 9 | - | - |
📨 나의 해결책 (2시간 10분 소요)
# 멤버 변수
StringBuiler 클래스의 result라는 변수는 사용자가 추후 numbers 배열에 존재하는 숫자들을 선택할 때 사용한 손가락이 무엇인지를 연속된 문자열 형태로 저장하는 변수이며 조금 있다가 바로 알아볼 solution 함수의 최종적인 반환 객체라고 할 수 있습니다.
그리고 result 변수 아래에 위치한 keypad라는 2차원 배열은 이번 프로그램 속에서 키패드 역할을 담당하며 numbers 배열에 존재하는 숫자들의 키패드 속 실질적인 좌표 값을 알려주는 역할을 합니다. 2차원 배열의 크기는 세로 4와 가로 3으로 이루어집니다.
또한 currentLeft, currentRight이라는 일차원 배열은 현재의 왼쪽 엄지손가락과 오른쪽 엄지손가락의 좌표 값을 저장하는 역할을 하며 0번째 인덱스 값을 세로, 1번째 인덱스 값을 가로의 좌표로 의미하고 있습니다. 초기 값은 *, # 기호의 좌표로 각각 설정합니다.
StringBuilder result = new StringBuilder();
String[][] keypad = { {"1", "2", "3"}, {"4", "5", "6"}, {"7", "8", "9"}, {"*", "0", "#"} };
int[] currentLeft = { 3, 0 };
int[] currentRight = { 3, 2 };
# solution 메소드
solution 메소드는 매개변수로 키패드에 입력해야하는 숫자들이 담긴 numbers 배열과 hand라는 문자열이 전달됩니다. 그 후 numbers 배열의 각 인덱스 값들을 for each 문을 통해 불러오면서 이전에 구현한 keypad라는 2차원 배열 속 값들과 비교해 만약 조건에 모두 해당한다면 judgeNumber라는 메소드를 해당하는 인자들과 함께 호출하여 키패드의 숫자를 클릭할 수 있도록 합니다.
public String solution(int[] numbers, String hand) {
for (int number : numbers) {
boolean isFind = false;
for (int i = 0; i < keypad.length; i++) {
for (int j = 0; j < keypad[i].length; j++) {
if (i == 3 && (j == 0 || j == 2)) continue;
if (number == Integer.parseInt(keypad[i][j])) {
judgeNumber(i, j, number, hand);
isFind = true;
break;
}
}
if (isFind) break;
}
}
return result.toString();
}
# judgeNumber 메소드
solution 메소드 속 for문 안에서 호출되는 judgeNumber 메소드는 매개변수로 keypad라는 2차원 배열에 해당하는 숫자가 위치한 세로 및 가로의 좌표 값을 rowIdx, columnIdx로 전달받습니다. 또한 number라는 변수에는 해당하는 숫자가, hand 변수에는 사용자의 주 손을 나타내는 문자열이 전달됩니다. 그 후 switch 문에서는 number 값에 따른 다른 처리 방식을 보여주고 있습니다.
키패드에서 해당하는 number 값을 클릭할 때 유의해야 하는 점 3가지에 대해서 알아보겠습니다. 첫 번째 숫자 1, 4, 7을 클릭할 때는 왼손 엄지손가락으로 클릭해야하며 두 번째 숫자 3, 6, 9를 클릭할 때는 오른손 엄지손가락으로 이를 클릭해야한다고 말했습니다.
마지막으로 숫자 2, 5, 8, 0을 클릭할 때는 현재의 왼손 엄지손가락과 오른손 엄지손가락의 좌표 값을 비교해 어느 손가락이 해당하는 숫자의 좌표에 더욱 가까이 위치하는지를 판별해 더 가까이 위치하는 손가락으로 그 번호를 클릭할 수 있도록 해야하는 것입니다.
그렇기에 저는 왼손 엄지손가락과 오른손 엄지손가락 중 어떤 손가락이 해당하는 숫자의 좌표 값에 더 가까이 있는지를 판별하기 위해 moveLeftCount와 moveRightCount라는 변수를 만들어 각 손가락의 좌표 값 세로와 가로 값을 while 문의 조건에 맞게 계속 이동시키면서 최종적으로 이동 횟수가 상대적으로 적은 손가락을 찾아내어 해당하는 숫자를 클릭할 수 있도록 코드로 구현했습니다.
public void judgeNumber(int rowIdx, int columnIdx, int number, String hand) {
switch (number) {
case 1:
case 4:
case 7: {
selectLeft(rowIdx, columnIdx);
break;
}
case 2:
case 5:
case 8:
case 0: {
int[] thisLeft = { currentLeft[0], currentLeft[1] };
int[] thisRight = { currentRight[0], currentRight[1] };
int moveLeftCount = 0;
int moveRightCount = 0;
// 왼손 (Row) && 왼손 (Column)
while (thisLeft[0] != rowIdx) {
if (thisLeft[0] > rowIdx) thisLeft[0] -= 1;
else thisLeft[0] += 1;
moveLeftCount++;
}
while (thisLeft[1] != columnIdx) {
if (thisLeft[1] > columnIdx) thisLeft[1] -= 1;
else thisLeft[1] += 1;
moveLeftCount++;
}
// 오른손 (Row) && 오른손 (Column)
while (thisRight[0] != rowIdx) {
if (thisRight[0] > rowIdx) thisRight[0] -= 1;
else thisRight[0] += 1;
moveRightCount++;
}
while (thisRight[1] != columnIdx) {
if (thisRight[1] > columnIdx) thisRight[1] -= 1;
else thisRight[1] += 1;
moveRightCount++;
}
if (moveLeftCount < moveRightCount) selectLeft(rowIdx, columnIdx);
else if (moveRightCount < moveLeftCount) selectRight(rowIdx, columnIdx);
else {
if (hand.equals("left")) selectLeft(rowIdx, columnIdx);
else selectRight(rowIdx, columnIdx);
}
break;
}
case 3:
case 6:
case 9: {
selectRight(rowIdx, columnIdx);
break;
}
}
}
# selectLeft & selectRight 메소드
이 두 개의 메소드는 모두 바로 위에서 살펴본 judgeNumber 메소드에서 호출합니다. selectLeft 메소드는 해당하는 숫자를 왼손으로 클릭하기에 currentLeft 배열 값을 새로운 rowIdx와 columnIdx로 변경시켜주고 result 문자열 변수에는 L이라는 값을 추가해주고 있습니다. 또한 selectRight 메소드 역시 내부의 로직은 동일하지만 왼손이 아닌 오른손으로 이를 클릭한다는 점이 다릅니다.
public void selectLeft(int rowIdx, int columnIdx) {
currentLeft[0] = rowIdx;
currentLeft[1] = columnIdx;
result.append("L");
}
public void selectRight(int rowIdx, int columnIdx) {
currentRight[0] = rowIdx;
currentRight[1] = columnIdx;
result.append("R");
}
[ 전체 소스코드 ]
public class Solution {
StringBuilder result = new StringBuilder();
String[][] keypad = { {"1", "2", "3"}, {"4", "5", "6"}, {"7", "8", "9"}, {"*", "0", "#"} };
int[] currentLeft = { 3, 0 };
int[] currentRight = { 3, 2 };
public String solution(int[] numbers, String hand) {
for (int number : numbers) {
boolean isFind = false;
for (int i = 0; i < keypad.length; i++) {
for (int j = 0; j < keypad[i].length; j++) {
if (i == 3 && (j == 0 || j == 2)) continue;
if (number == Integer.parseInt(keypad[i][j])) {
judgeNumber(i, j, number, hand);
isFind = true;
break;
}
}
if (isFind) break;
}
}
return result.toString();
}
public void judgeNumber(int rowIdx, int columnIdx, int number, String hand) {
switch (number) {
case 1:
case 4:
case 7: {
selectLeft(rowIdx, columnIdx);
break;
}
case 2:
case 5:
case 8:
case 0: {
int[] thisLeft = { currentLeft[0], currentLeft[1] };
int[] thisRight = { currentRight[0], currentRight[1] };
int moveLeftCount = 0;
int moveRightCount = 0;
// 왼손 (Row) && 왼손 (Column)
while (thisLeft[0] != rowIdx) {
if (thisLeft[0] > rowIdx) thisLeft[0] -= 1;
else thisLeft[0] += 1;
moveLeftCount++;
}
while (thisLeft[1] != columnIdx) {
if (thisLeft[1] > columnIdx) thisLeft[1] -= 1;
else thisLeft[1] += 1;
moveLeftCount++;
}
// 오른손 (Row) && 오른손 (Column)
while (thisRight[0] != rowIdx) {
if (thisRight[0] > rowIdx) thisRight[0] -= 1;
else thisRight[0] += 1;
moveRightCount++;
}
while (thisRight[1] != columnIdx) {
if (thisRight[1] > columnIdx) thisRight[1] -= 1;
else thisRight[1] += 1;
moveRightCount++;
}
if (moveLeftCount < moveRightCount) selectLeft(rowIdx, columnIdx);
else if (moveRightCount < moveLeftCount) selectRight(rowIdx, columnIdx);
else {
if (hand.equals("left")) selectLeft(rowIdx, columnIdx);
else selectRight(rowIdx, columnIdx);
}
break;
}
case 3:
case 6:
case 9: {
selectRight(rowIdx, columnIdx);
break;
}
}
}
public void selectLeft(int rowIdx, int columnIdx) {
currentLeft[0] = rowIdx;
currentLeft[1] = columnIdx;
result.append("L");
}
public void selectRight(int rowIdx, int columnIdx) {
currentRight[0] = rowIdx;
currentRight[1] = columnIdx;
result.append("R");
}
}
👍 글을 마치며
이번에 여러분들과 함께 풀어본 문제는 2020 카카오 인턴쉽 코딩 테스트에서 출제되었던 키패드 누르기라는 문제이며 프로그래머스 사이트에서 무료로 풀 수 있었습니다. 아직까지 이 문제를 푼 사람이 오늘 기준으로 2627명 밖에 없어서 모범 답안이라고 할 수 있는 코드가 정확히 공유되지 않아 이번 글에는 모범 답안을 추가하지 않았습니다. 추후 이 문제를 푼 사람이 많아지고 모범 답안이라고 생각되는 코드가 공유된다면 그때 다시 글을 수정하도록 하겠습니다. 저 또한 이 문제를 처음 풀 때 문제를 푼 사람이 많이 없어서 정말 어려운 문제가 아닌가라고 생각을 했으며 시작도 전에 겁을 많이 먹었습니다. 그러나 천천히 계획적으로 공책을 활용하여 문제를 분석하고 코드를 작성하니 어렵지 않게 문제를 풀 수 있었습니다. 이번 문제를 통해 저는 침착성과 계획성의 중요함을 느꼈습니다.
'프로그래밍 기초 > Data structure & Algorithm' 카테고리의 다른 글
비밀지도 (0) | 2020.11.06 |
---|---|
문자열 검색에 대해서 (0) | 2020.10.23 |
K번째 수 (0) | 2020.08.22 |
체육복 (0) | 2020.08.13 |
집합에 대해서 (0) | 2020.08.06 |