[프로그래머스 JAVA] 수식 복원하기
2024. 9. 19. 21:32ㆍ프로그래머스
https://school.programmers.co.kr/learn/courses/30/lessons/340210
프로그래머스
코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.
programmers.co.kr
문제 접근 방법
특별히 떠오르는 알고리즘은 없었다.
문제에서 제시한 힌트와 비슷한 방식으로도 풀이해봤고, 조금 더 최적화해서 진행해보기도 했지만 실행결과는 비슷해서 아쉬웠다.
풀이 방법 (2가지)
1. 문제 설명대로 풀어본 코드
문제에서 제시한 힌트는 아래와 같다.
가능한 STRAT진수 ~ END진수 를 체크해보고, 그 결과들이 다르다면 ?를 출력하고 결과가 같다면 풀이 가능한 문제로 표현하는 것이다.
결과들이 다른 것은 Set의 길이로 구분했다.
코드
더보기
package programmers;
import java.util.*;
public class 수식복원하기 {
public static void main(String[] args) {
String[] expressions = {"10 - 2 = X", "30 + 31 = 101", "3 + 3 = X", "33 + 33 = X"};
String[] solution = solution(expressions);
for (String expr : solution) {
System.out.println(expr); // 결과 출력
}
}
public static String[] solution(String[] expressions) {
List<String> answerList = new ArrayList<>(); // 결과를 저장할 리스트
List<Integer> possibleBases = new ArrayList<>(); // 가능한 진법들을 저장할 리스트
// 1. 가능한 진법 찾기
for (int base = 2; base <= 9; base++) {
boolean validBase = true; // 현재 진법이 유효한지 여부를 나타내는 변수
for (String expression : expressions) {
String[] tokens = expression.split(" ");
String A = tokens[0]; // 첫 번째 피연산자
String operator = tokens[1]; // 연산자 (+ 또는 -)
String B = tokens[2]; // 두 번째 피연산자
String equalsSign = tokens[3]; // 등호 (=)
String C = tokens[4]; // 결과 값 또는 'X'
// 숫자들이 해당 진법에서 유효한지 확인
if (!isValidNumber(A, base) || !isValidNumber(B, base)) {
validBase = false;
break; // 유효하지 않으면 더 이상 검사할 필요 없음
}
// 결과 값이 'X'가 아닌 경우 계산 검증
if (!C.equals("X")) {
if (!isValidNumber(C, base)) {
validBase = false;
break;
}
// 해당 진법에서 숫자를 정수로 변환
int operand1 = Integer.parseInt(A, base);
int operand2 = Integer.parseInt(B, base);
int result = Integer.parseInt(C, base);
int computedResult;
if (operator.equals("+")) {
computedResult = operand1 + operand2; // 덧셈 계산
} else if (operator.equals("-")) {
computedResult = operand1 - operand2; // 뺄셈 계산
if (computedResult < 0) {
validBase = false; // 결과가 음수이면 유효하지 않음
break;
}
} else {
validBase = false; // 지원하지 않는 연산자
break;
}
// 계산 결과가 실제 결과와 일치하는지 확인
if (computedResult != result) {
validBase = false;
break;
}
}
}
// 유효한 진법이면 리스트에 추가
if (validBase) {
possibleBases.add(base);
}
}
// 2. 결과 값 채우기
for (int i = 0; i < expressions.length; i++) {
String expression = expressions[i];
String[] tokens = expression.split(" ");
String A = tokens[0]; // 첫 번째 피연산자
String operator = tokens[1]; // 연산자
String B = tokens[2]; // 두 번째 피연산자
String equalsSign = tokens[3]; // 등호
String C = tokens[4]; // 결과 값 또는 'X'
if (C.equals("X")) {
Set<String> possibleResults = new HashSet<>(); // 가능한 결과 값들을 저장할 집합
// 가능한 진법들에 대해 결과를 계산
for (int base : possibleBases) {
// 해당 진법에서 숫자들이 유효한지 확인
if (!isValidNumber(A, base) || !isValidNumber(B, base)) {
continue;
}
int operand1 = Integer.parseInt(A, base); // 첫 번째 피연산자 변환
int operand2 = Integer.parseInt(B, base); // 두 번째 피연산자 변환
int computedResult;
if (operator.equals("+")) {
computedResult = operand1 + operand2; // 덧셈 계산
} else if (operator.equals("-")) {
computedResult = operand1 - operand2; // 뺄셈 계산
if (computedResult < 0) {
continue; // 결과가 음수이면 스킵
}
} else {
continue; // 지원하지 않는 연산자
}
// 계산된 결과를 해당 진법의 문자열로 변환
String resultStr = Integer.toString(computedResult, base).toUpperCase();
// 결과가 해당 진법에서 유효한지 확인
if (!isValidNumber(resultStr, base)) {
continue;
}
possibleResults.add(resultStr); // 가능한 결과 값 추가
}
if (possibleResults.size() == 1) {
// 가능한 결과가 하나이면 결과를 채워 넣음
String resultValue = possibleResults.iterator().next();
String filledExpression = A + " " + operator + " " + B + " = " + resultValue;
answerList.add(filledExpression);
} else {
// 여러 결과가 가능하면 '?'로 표시
String filledExpression = A + " " + operator + " " + B + " = ?";
answerList.add(filledExpression);
}
}
}
return answerList.toArray(new String[0]); // 결과를 배열로 변환하여 반환
}
// 숫자가 해당 진법에서 유효한지 확인하는 함수
static boolean isValidNumber(String numStr, int base) {
for (int i = 0; i < numStr.length(); i++) {
char c = numStr.charAt(i);
if (c < '0' || c > '9') {
return false; // 숫자가 아닌 문자가 포함된 경우
}
int digit = c - '0';
if (digit >= base) {
return false; // 자릿수가 진법보다 크거나 같은 경우
}
}
return true; // 모든 자릿수가 해당 진법에서 유효한 경우
}
}
2. 입력값에서 진수를 확정짓는 코드
올림이나 빌림에서 N진수를 확정짓는 방법을 추가하고, 추론 과정을 조금 더 직관적으로 변경해봤다.
코드
더보기
package programmers;
import java.util.Arrays;
public class 수식복원하기_2 {
public static void main(String[] args) {
String[] expressions = {
"1 + 5 = 10",
"23 + 12 = X",
"55 + 31 = X", // 올림 발생 (Carry occurs)
"52 - 11 = X",
"53 - 24 = X" // 빌림 발생 (Borrow occurs)
};
String[] solution = solution(expressions);
System.out.println(confirmedBase); // 확정된 진수 출력
for (String expr : solution) {
System.out.println(expr); // 결과 출력
}
}
static int estimatedMinimumBase = 2; // 추정한 최소 진수 (Initially guessed minimum base)
static int confirmedBase = -1; // 확정된 진수 (-1이면 아직 확정되지 않음)
static boolean[] isVisited; // 이미 처리된 표현식 표시 (Indicates if an expression has been processed)
static String[] resultExpressions; // 결과를 저장할 배열 (Array to store the results)
static int resultIndex = 0; // 결과 배열의 인덱스 (Index for the result array)
public static String[] solution(String[] expressions) {
resultExpressions = new String[expressions.length];
isVisited = new boolean[expressions.length];
// 각 표현식을 검사하여 진수를 추정하고 확정된 진수를 찾는다.
for (int i = 0; i < expressions.length; i++) {
String expression = expressions[i];
String[] tokens = expression.split(" ");
int operandA = Integer.parseInt(tokens[0]); // 첫 번째 피연산자
String operator = tokens[1]; // 연산자 (+ 또는 -)
int operandB = Integer.parseInt(tokens[2]); // 두 번째 피연산자
String resultStr = tokens[4]; // 결과 (또는 'X')
// 각 숫자의 일의 자리와 십의 자리 추출
int aUnitsDigit = operandA % 10; // operandA의 일의 자리
int aTensDigit = operandA / 10; // operandA의 십의 자리
int bUnitsDigit = operandB % 10; // operandB의 일의 자리
int bTensDigit = operandB / 10; // operandB의 십의 자리
// 현재 표현식에서의 최대 자릿수 구하기
int currentMaxDigit = Math.max(Math.max(aUnitsDigit, aTensDigit), Math.max(bUnitsDigit, bTensDigit));
// 추정한 최소 진수를 갱신 (현재 최대 자릿수 + 1과 기존 추정 값 중 큰 값)
estimatedMinimumBase = Math.max(currentMaxDigit + 1, estimatedMinimumBase);
if (resultStr.equals("X")) continue; // 결과가 'X'이면 다음으로
isVisited[i] = true; // 결과가 있는 표현식은 방문 표시
int resultValue = Integer.parseInt(resultStr); // 결과를 정수로 변환
int resUnitsDigit = resultValue % 10; // 결과의 일의 자리
int resTensDigit = (resultValue % 100) / 10; // 결과의 십의 자리
int resHundredsDigit = resultValue / 100; // 결과의 백의 자리
// 결과의 최대 자릿수에 따라 추정한 최소 진수를 갱신
estimatedMinimumBase = Math.max(estimatedMinimumBase, Math.max(resUnitsDigit, resTensDigit) + 1);
if (operator.equals("+")) {
// 덧셈에서 올림을 고려하여 확정된 진수를 찾는다.
int expectedSum = aUnitsDigit + bUnitsDigit; // 일의 자리 예상 합
int difference = expectedSum - resUnitsDigit; // 예상 합과 실제 결과의 차이
int carry = difference == 0 ? 0 : 1; // 올림 발생 여부 (0: 없음, 1: 발생)
if (carry == 1) {
confirmedBase = difference; // 차이가 진수와 관련되므로 확정된 진수 설정
break;
}
expectedSum = aTensDigit + bTensDigit;
difference = expectedSum - resTensDigit;
if (difference == 0) continue;
confirmedBase = difference; // 차이가 진수와 관련되므로 확정된 진수 설정
} else {
// 뺄셈에서 빌림을 고려하여 확정된 진수를 찾는다.
if (aUnitsDigit >= bUnitsDigit) {
continue; // 빌림이 없으면 다음으로
}
int expectedDifference = 10 + aUnitsDigit - bUnitsDigit; // 빌림이 발생한 일의 자리 계산
int difference = expectedDifference - resUnitsDigit;
confirmedBase = 10 - difference; // 차이를 통해 진수를 계산하여 확정된 진수 설정
}
}
// 추정한 최소 진수가 9인 경우 확정된 진수를 9로 설정 (진수는 최대 9까지)
if (estimatedMinimumBase == 9) {
confirmedBase = 9;
}
// 결과가 'X'인 표현식에 대해 결과를 계산한다.
for (int i = 0; i < expressions.length; i++) {
if (isVisited[i]) continue; // 이미 처리된 표현식은 넘어감
String expression = expressions[i];
String[] tokens = expression.split(" ");
int operandA = Integer.parseInt(tokens[0]);
String operator = tokens[1];
int operandB = Integer.parseInt(tokens[2]);
String resultStr = tokens[4];
if (!resultStr.equals("X")) continue; // 결과가 있는 표현식은 넘어감
int aUnitsDigit = operandA % 10; // operandA의 일의 자리
int aTensDigit = operandA / 10; // operandA의 십의 자리
int bUnitsDigit = operandB % 10; // operandB의 일의 자리
int bTensDigit = operandB / 10; // operandB의 십의 자리
// 확정된 진수가 없는 경우 추정한 최소 진수로 결과를 계산
if (confirmedBase == -1) {
if (operator.equals("+")) {
// 덧셈에서 추정한 진수로 결과를 계산하고, 올림이 발생하면 '?'로 표시
if (aUnitsDigit + bUnitsDigit >= estimatedMinimumBase || aTensDigit + bTensDigit >= estimatedMinimumBase) {
resultExpressions[resultIndex++] = operandA + " " + operator + " " + operandB + " = ?";
continue;
}
// 결과를 계산하여 저장
resultExpressions[resultIndex++] = operandA + " " + operator + " " + operandB + " = " + (operandA + operandB);
} else {
// 뺄셈에서 추정한 진수로 결과를 계산하고, 빌림이 발생하면 '?'로 표시
if (aUnitsDigit < bUnitsDigit) {
resultExpressions[resultIndex++] = operandA + " " + operator + " " + operandB + " = ?";
continue;
}
// 결과를 계산하여 저장
resultExpressions[resultIndex++] = operandA + " " + operator + " " + operandB + " = " + (operandA - operandB);
}
}
// 확정된 진수가 있는 경우 그 진수로 결과를 계산
else {
calculateResult(operandA, operandB, operator); // 계산 메서드 호출
}
}
return Arrays.copyOf(resultExpressions, resultIndex); // 결과 배열 반환
}
// 확정된 진수를 사용하여 결과를 계산하는 메서드
private static void calculateResult(int operandA, int operandB, String operator) {
int aUnitsDigit = operandA % 10; // operandA의 일의 자리
int aTensDigit = operandA / 10; // operandA의 십의 자리
int bUnitsDigit = operandB % 10; // operandB의 일의 자리
int bTensDigit = operandB / 10; // operandB의 십의 자리
int carryOrBorrow = 0; // 올림 또는 빌림을 저장할 변수
int resUnitsDigit = 0; // 결과의 일의 자리
int resTensDigit = 0; // 결과의 십의 자리
int resHundredsDigit = 0; // 결과의 백의 자리 (덧셈에서 발생할 수 있음)
if (operator.equals("+")) {
// 덧셈에서 일의 자리 계산
if (aUnitsDigit + bUnitsDigit >= confirmedBase) {
carryOrBorrow = 1; // 올림 발생
resUnitsDigit = aUnitsDigit + bUnitsDigit - confirmedBase;
} else {
resUnitsDigit = aUnitsDigit + bUnitsDigit;
}
// 덧셈에서 십의 자리 계산
if (aTensDigit + bTensDigit + carryOrBorrow >= confirmedBase) {
resHundredsDigit = 1; // 백의 자리 발생
resTensDigit = aTensDigit + bTensDigit + carryOrBorrow - confirmedBase;
} else {
resTensDigit = aTensDigit + bTensDigit + carryOrBorrow;
}
// 최종 결과를 계산하여 저장
int finalResult = resUnitsDigit + resTensDigit * 10 + resHundredsDigit * 100;
resultExpressions[resultIndex++] = operandA + " " + operator + " " + operandB + " = " + finalResult;
} else {
// 뺄셈에서 일의 자리 계산
if (aUnitsDigit < bUnitsDigit) {
aTensDigit--; // 빌림 발생으로 십의 자리에서 1 차감
aUnitsDigit += confirmedBase; // 일의 자리에 진수를 더함
resUnitsDigit = aUnitsDigit - bUnitsDigit;
} else {
resUnitsDigit = aUnitsDigit - bUnitsDigit;
}
// 뺄셈에서 십의 자리 계산
resTensDigit = aTensDigit - bTensDigit;
// 최종 결과를 계산하여 저장
int finalResult = resUnitsDigit + resTensDigit * 10;
resultExpressions[resultIndex++] = operandA + " " + operator + " " + operandB + " = " + finalResult;
}
}
}
결론
두 풀이를 잘 조합하면 더 효율적인 풀이가 가능할 것 같은데, 결과적으로 두 풀이의 실행시간 차이가 크게 나지않아서 그만뒀다.
문제를 풀이하면서 (Integer.ParseInt(str, base)), Integer.toString(computedResult, base) 을 처음 사용해봤다.
'프로그래머스' 카테고리의 다른 글
[데브코스 백엔드 1기] 회고 (0) | 2024.12.17 |
---|---|
[프로그래머스 JAVA] 순위 (3) | 2024.10.13 |
[프로그래머스 JAVA] 주사위 고르기 (1) | 2024.09.14 |
[프로그래머스 JAVA] 이중우선순위큐 (0) | 2024.05.13 |
[프로그래머스 JAVA] 모음사전 (0) | 2024.05.12 |