▼ Why ?
이번 문제도 구현 - 시뮬레이션 유형의 문제로, 문제가 주어준 상황에 맞게 올바른 결과값을 출력하도록 구현해내면 된다. 하지만 지금까지 풀었던 구현 유형의 문제들은 하나의 개체만을 생각했지만, 이 문제 [키패드]는 두 개의 개체(왼손, 오른손)를 신경 써야 하기 때문에 소스 코드로 구현하는데 좀 더 까다로울 것 같아 해결해보려고 한다.
▼ 키패드
문제 정보
스마트폰 전화 키패드의 각 칸에 다음과 같이 숫자들이 적혀 있습니다.
이 전화 키패드에서 왼손과 오른손의 엄지손가락만을 이용해서 숫자만을 입력하려고 합니다.
맨 처음 왼손 엄지손가락은 * 키패드에 오른손 엄지손가락은 # 키패드 위치에서 시작하며, 엄지손가락을 사용하는 규칙은 다음과 같습니다.
- 엄지손가락은 상하좌우 4가지 방향으로만 이동할 수 있으며 키패드 이동 한 칸은 거리로 1에 해당합니다.
- 왼쪽 열의 3개의 숫자 1, 4, 7을 입력할 때는 왼손 엄지손가락을 사용합니다.
- 오른쪽 열의 3개의 숫자 3, 6, 9를 입력할 때는 오른손 엄지손가락을 사용합니다.
- 가운데 열의 4개의 숫자 2, 5, 8, 0을 입력할 때는 두 엄지손가락의 현재 키패드의 위치에서 더 가까운 엄지손가락을 사용합니다.
- 만약 두 엄지손가락의 거리가 같다면, 오른손잡이는 오른손 엄지손가락, 왼손잡이는 왼손 엄지손가락을 사용합니다.
순서대로 누를 번호가 담긴 배열 numbers, 왼손잡이인지 오른손잡이인 지를 나타내는 문자열 hand가 매개변수로 주어질 때, 각 번호를 누른 엄지손가락이 왼손인 지 오른손인 지를 나타내는 연속된 문자열 형태로 return 하도록 solution 함수를 완성해주세요.
코딩테스트 연습 - 키패드 누르기 | 프로그래머스 스쿨 (programmers.co.kr)
프로그래머스
코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.
programmers.co.kr
제한 사항
- numbers 배열의 크기는 1 이상 1,000 이하입니다.
- numbers 배열 원소의 값은 0 이상 9 이하인 정수입니다.
- hand는 "left" 또는 "right" 입니다.
- "left"는 왼손잡이, "right"는 오른손잡이를 의미합니다.
- 왼손 엄지손가락을 사용한 경우는 L, 오른손 엄지손가락을 사용한 경우는 R을 순서대로 이어붙여 문자열 형태로 return 해주세요.
입출력 예
어떻게 해결해야 할까?
- 사실 이동을 구현할 개체가 한 개에서 두 개가 된 것일 뿐이기 때문에 개체의 이동을 구현하는 다른 문제들을 해결하듯이 해결하면 될 것 같다
- 일단 키패드를 2차원 배열의 형태로 저장하자
- 그리고 왼손의 엄지 손가락과 오른손의 엄지 손가락의 처음 위치를 저장할 변수를 생성한다
- 키패드에서 '1', '4', 7'를 입력할 때는 무조건 왼손의 엄지를, '3', '6', '9'를 입력할 때는 오른손의 엄지를 사용해야 하기 때문에 고려할 필요가 없다
- 따라서, '2', '5', '8', '0'을 입력하는 경우만 각 엄지의 현재 위치와 해당 번호 사이의 거리를 계산해 더 가까운 엄지를 사용하도록 하기만 하면 된다
- 단, 왼손잡이인지 오른손잡이인지 미리 체크하여 거리가 같을 경우 사용할 엄지를 결정한다
해결 코드 0
import java.util.*;
class Solution {
public String solution(int[] numbers, String hand) {
String result = "";
// 키패드의 번호 위치 (0 ~ 9)
int[][] keyPos = {
{3, 1}, // 0
{0, 0}, {0, 1}, {0, 2},
{1, 0}, {1, 1}, {1, 2},
{2, 0}, {2, 1}, {2, 2},
};
// 엄지의 초기 위치
int[] leftPos = {3, 0}; int[] rightPos = {3, 2};
String whatHanded = (hand.equals("right")) ? "R" : "L";
for(int num : numbers) {
if(num == 1 || num == 4 || num == 7) {
leftPos = keyPos[num];
result += "L";
} else if(num == 3 || num == 6 || num == 9) {
rightPos = keyPos[num];
result += "R";
} else {
int leftDist = Math.abs(leftPos[0] - keyPos[num][0]) + Math.abs(leftPos[1] - keyPos[num][1]);
int rightDist = Math.abs(rightPos[0] - keyPos[num][0]) + Math.abs(rightPos[1] - keyPos[num][1]);
if(leftDist < rightDist) { // 왼손의 엄지가 더 가까울 경우
leftPos = keyPos[num];
result += "L";
} else if(leftDist == rightDist) { // 같은 거리에 있을 경우
result += whatHanded;
if(result.charAt(result.length()-1) == 'L')
leftPos = keyPos[num];
else
rightPos = keyPos[num];
} else { // 오른손의 엄지가 더 가까울 경우
rightPos = keyPos[num];
result += "R";
}
}
}
return result;
}
}
해결 코드 1
- 결과값으로 반환할 문자열(' result )을 ' + ' 연산자를 사용하여 문자열을 합쳤더니 메모리 낭비도 심하고 연산 속도가 매우 떨어지는 것을 확인했다
➜ StringBuilder class를 이용하여 문자열의 결합 연산을 수행하자
class Solution {
public String solution(int[] numbers, String hand) {
String result = "";
// 키패드의 번호 위치 (0 ~ 9)
int[][] keyPos = {
{3, 1}, // 0
{0, 0}, {0, 1}, {0, 2},
{1, 0}, {1, 1}, {1, 2},
{2, 0}, {2, 1}, {2, 2},
};
// 엄지의 초기 위치
int[] leftPos = {3, 0}; int[] rightPos = {3, 2};
String whatHanded = (hand.equals("right")) ? "R" : "L";
StringBuilder sb = new StringBuilder();
for(int num : numbers) {
if(num == 1 || num == 4 || num == 7) {
leftPos = keyPos[num];
sb.append("L");
} else if(num == 3 || num == 6 || num == 9) {
rightPos = keyPos[num];
sb.append("R");
} else {
int leftDist = Math.abs(leftPos[0] - keyPos[num][0]) + Math.abs(leftPos[1] - keyPos[num][1]);
int rightDist = Math.abs(rightPos[0] - keyPos[num][0]) + Math.abs(rightPos[1] - keyPos[num][1]);
if(leftDist < rightDist) { // 왼손의 엄지가 더 가까울 경우
leftPos = keyPos[num];
sb.append("L");
} else if(leftDist == rightDist) { // 같은 거리에 있을 경우
sb.append(whatHanded);
if(sb.charAt(sb.length()-1) == 'L')
leftPos = keyPos[num];
else
rightPos = keyPos[num];
} else { // 오른손의 엄지가 더 가까울 경우
rightPos = keyPos[num];
sb.append("R");
}
}
}
result = sb.toString();
return result;
}
}
해결 코드 2
- 누를 번호를 받을 때마다 해당 번호와 엄지와의 거리를 계산하는 방식으로 했는데, 누를 수 있는 번호가 많지 않기 때문에 번호 사이의 거리를 미리 계산해둔다면 반복 연산 작업을 줄일 수 있을 것 같아 코드를 다시 작성해보았다
- '1', '4', 7'를 입력할 때는 무조건 왼손의 엄지를, '3', '6', '9'를 입력할 때는 오른손의 엄지를 사용해야 하기 때문에 해당 번호들까지의 거리는 고려하지 않아도 되기 때문에 ' 0 ' 으로 저장해두자
class Solution {
public String solution(int[] numbers, String hand) {
String result = "";
int left = 10; int right = 11;
int[][] distance = {{0, 0, 3, 0, 0, 2, 0, 0, 1, 0}, // 0에서 다른 번호(0~9)와의 거리
{4, 0, 1, 0, 0, 2, 0, 0, 3, 0}, // 1
{3, 0, 0, 0, 0, 1, 0, 0, 2, 0}, // 2
{4, 0, 1, 0, 0, 2, 0, 0, 3, 0}, // 3
{3, 0, 2, 0, 0, 1, 0, 0, 2, 0}, // 4
{2, 0, 1, 0, 0, 0, 0, 0, 1, 0}, // 5
{3, 0, 2, 0, 0, 1, 0, 0, 2, 0}, // 6
{2, 0, 3, 0, 0, 2, 0, 0, 1, 0}, // 7
{1, 0, 2, 0, 0, 1, 0, 0, 0, 0}, // 8
{2, 0, 3, 0, 0, 2, 0, 0, 1, 0}, // 9
{1, 0, 4, 0, 0, 3, 0, 0, 2, 0}, // * (10)
{1, 0, 4, 0, 0, 3, 0, 0, 2, 0}}; // # (11)
StringBuilder sb = new StringBuilder();
for(int num : numbers) {
if(num == 1 || num == 4 || num == 7) {
left = num;
sb.append("L");
} else if (num == 3 || num == 6 || num == 9) {
right = num;
sb.append("R");
}
else {
if(distance[left][num] < distance[right][num]) { // 왼손의 엄지가 더 가까운 경우
left = num;
sb.append("L");
} else if(distance[left][num] > distance[right][num]) { // 오른손의 엄지가 더 가까운 경우
right = num;
sb.append("R");
} else { // 거리가 같은 경우
if(hand.equals("left")) {
left = num;
sb.append("L");
} else {
right = num;
sb.append("R");
}
}
}
}
result = sb.toString();
return result;
}
}
▼ 정리
- 문자열(String)을 ' + ' 연산자를 사용하여 결합하는 것은 값이 합쳐진 새로운 String instance를 생성하는 것이기 때문에, 메모리적인 측면에서도 비효율적이고 경우에 따라 time limit가 발생할 수 있다
➜ StringBuilder(StringBuffer도 기능은 같지만 동기화를 지원한다) class의 append 메서드를 사용하면 성능이 확연하게 향상된다는 사실을 기억해두자 - 어떤 배열에 다른 배열의 값을 넣어주고 싶을 때 ' leftPos = keyPos[num]; ' 와 같이 하는 방법은 값을 넣어주는 것이 아닌 배열 leftPos의 주소값이 2차원 배열 keyPos[num]을 가리키도록 바꿔주는 것이기 때문에, 두 참조변수가 같은 배열을 가리키게 되는 것이다
➜ 이러한 점은 위 문제를 해결하는 데에는 상관이 없겠지만, 만약 하나의 배열(keyPos[num])의 값을 수정해야 하는 경우엔 다른 배열(leftPos)의 값도 같이 수정된다고 볼 수 있어 의도치 않은 결과가 나올 수 있다
➜ 따라서, 어떤 배열에 다른 배열의 값을 넣어주고 싶을 때엔 for문을 사용하여 배열의 요소에 차례대로 접근해서 값을 넣어주는 방법도 고려해야 한다 - 이번 문제에선 거리를 계산하는 반복 연산 작업을 없앨 수 있애기 위해서, 번호와의 거리를 미리 계산해두고 해당 번호까지의 거리를 비교해야 할 때마다 값을 꺼내오는 방식으로 바꿔봤더니, 몇몇 테스트 결과에서 연산시간이 줄어든 것을 확인할 수 있었다
➜ 이처럼 계산해야 하는 값이 많지 않다면 미리 계산한 값을 저장하여 필요할 때마다 해당 값을 불러오는 방식으로 반복 연산 작업을 줄여 속도를 좀 더 높일 수 있다는 것을 기억해두자