Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
Tags
- 자바의 정석 기초편 ch9
- 자바의 정석 기초편 ch6
- 자바의 정석 기초편 ch4
- 스프링 고급 - 스프링 aop
- 스프링 mvc1 - 스프링 mvc
- 2024 정보처리기사 수제비 실기
- 자바 고급2편 - io
- 자바의 정석 기초편 ch2
- 자바의 정석 기초편 ch13
- 자바의 정석 기초편 ch5
- 스프링 mvc2 - 검증
- 데이터 접근 기술
- 자바로 계산기 만들기
- 스프링 mvc2 - 로그인 처리
- 자바 기초
- 자바 중급1편 - 날짜와 시간
- 자바의 정석 기초편 ch14
- 자바의 정석 기초편 ch12
- 자바 중급2편 - 컬렉션 프레임워크
- 스프링 트랜잭션
- 자바의 정석 기초편 ch1
- 스프링 mvc2 - 타임리프
- 자바의 정석 기초편 ch11
- @Aspect
- 람다
- 스프링 입문(무료)
- 2024 정보처리기사 시나공 필기
- 자바로 키오스크 만들기
- 자바의 정석 기초편 ch7
- 자바 고급2편 - 네트워크 프로그램
Archives
- Today
- Total
개발공부기록
Java - 푸드 파이트 대회, 콜라 문제 / SQL(MySQL) - 식품분류별 가장 비싼 식품의 정보 조회하기, 5월 식품들의 총매출 조회하기 본문
기타 개발 공부/온라인 코딩 테스트 회고
Java - 푸드 파이트 대회, 콜라 문제 / SQL(MySQL) - 식품분류별 가장 비싼 식품의 정보 조회하기, 5월 식품들의 총매출 조회하기
소소한나구리 2025. 3. 20. 10:20728x90
java
푸드 파이트 대회
문제
- 프로그래머스 - https://school.programmers.co.kr/learn/courses/30/lessons/134240
- 대회에서 제공할 음식의 개수인 food가 int[]로 낮은 칼로리 순서대로 주어졌을 때 대회를 위한 음식의 배치를 나타내는 문자열을 return하는 함수를 원성
- 대회에서 선수는 1대 1로 대결하는데 준비된 음식을 일렬로 배치한 뒤 한 선수는 제일 왼쪽에 음식부터 오른쪽으로 다른 선수는 제일 오른쪽에 음식부터 왼쪽으로 순서대로 먹는 방식으로 진행되며 중앙에는 물을 배치함
- 두 선수가 먹는 음식의 종류와 양이 같아야 하며 음식을 먹는 순서도 같아야 하고 칼로리가 낮은 음식부터 먼저 먹을 수 있게 배치해야 함
- 대회에 사용되지 않는 음식은 버려짐
- 예를 들어 3가지의 음식이 주어지고 칼로리가 적은 순서대로 1번 음식3개, 2번 음식 4개, 3번 음식을 6개 준비하고 물이 편의상 0이라고 한다면 food는 [1, 3, 4, 6]이 되며 2 선수는 1번음식 1개, 2번음식 2개, 3번 음식 3개씩을 먹게 되므로 음식의 배치는 1223330333221 이 되야하고 1번 음식 1개는 사용하지 못함
제한조건
- food의 길이는 2 이상 9 이하이며 food의 각 원소의 길이는 1 이상 1,000 이하임
- food에는 칼로리가 적은 순서대로 음식의 양이 담겨있으며 food[i]는 i번의 음식의 수임
- food[0]은 준비한 물의 양이며 항상 1임
- 정담의 길이가 3 이상인 경우만 입력으로 주어짐
입출력 예시
나의 풀이
import java.util.*;
class Solution {
public String solution(int[] food) {
List<Integer> buffer = new ArrayList<>();
List<Integer> buffer2 = new ArrayList<>();
int[] foodSeq = new int[food.length];
for (int i = 0; i < food.length; i++) {
foodSeq[i] = food[i] / 2;
}
for (int i = 0; i < foodSeq.length; i++) {
for (int j = 0; j < foodSeq[i]; j++) {
buffer.add(i);
buffer2.add(i);
}
}
buffer2.sort(Comparator.reverseOrder());
buffer.add(foodSeq[0]);
buffer.addAll(buffer2);
StringBuilder answer = new StringBuilder();
for (int i : buffer) {
answer.append(i);
}
return answer.toString();
}
}
- 문제를 풀면서 상당히 코드 구조가 비효율 적이라고 생각했지만 매우 단순하고 원초적으로 동일한 자료구조를 2개를 만들어서 하나를 역순 정렬하려 합치는 방법으로 접근했다.
- 먼저 주어진 음식의 개수인 food를 모두 동일하게 배치해야하기 때문에 짝수로 만들기 위해 각 요소를 나누기 2연산을하여 각 음식을 배치할 개수를 짝수 값으로 구해서 int[]에 저장했다
- 이때 물은 무조건 1개가 들어오기 때문에 0으로 저장된다
- 그 다음 2중 반복문을 통해서 foodSeq의 길이만큼 반복하는데 이게 준비한 음식의 번호(칼로리가 낮은 음식의 순서)이며 그 다음 반복으로 foodSeq[i]만큼 반복하여 음식의 개수만큼 음식의 번호가 준비한 2개의 버퍼에 담기도록 작성했다.
- 모든 음식의 순서가 음식의 개수만큼 버퍼에 담기면 1개의 버퍼에는 buffer.add(foodSeq[0])로 음식 중간에 물을 배치한다.
- 그다음 또다른 버퍼를 역순 정렬한 buffer.addAll(buffer2)로 물 다음에 음식 순서를 붙이면 List로 대회에 필요한 음식과 물의 배치가 끝난다.
- 그다음 List에 담긴 순서를 문자열로 반환하기 위해 StringBuilder를 생성하여 List의 요소를 하나씩 꺼내서 StringBuilder에 추가하고 이후 String으로 변환하여 문제를 해결했다.
문제점 개선 풀이
import java.util.*;
class Solution {
public String solution(int[] food) {
StringBuilder sb = new StringBuilder();
for (int i = 1; i < food.length; i++) {
int count = food[i] / 2;
sb.append(String.valueOf(i).repeat(count));
}
String result = sb.toString();
sb.append("0");
sb.append(new StringBuilder(result).reverse());
return sb.toString();
}
}
- 여기서 아무리 제약조건이 food[0]이 항상 1이라고 해도 foodSeq[0]을 계산하는 과정과 foodSeq배열 자체가 불필요하며 buffer를 두개를 사용하는 것은 메모리 낭비라는 것을 알았다.
- 우선 food[0]을 따로 처리하지 않아도 어차피 물은 0이기 때문에 구해진 음식 뒤에 0을 추가하기만 하면 된다.
- 여러 반복을 할 필요 없이 StringBuilder를 생성한 다음 1번에 반복으로 food[i] / 2로 배치할 음식을 짝수개로 구한다.
- 그 다음 sb.append()로 음식의 번호를 저장하는데, repeat(count)로 count의 개수만큼 음식의 번호를 문자열로 저장한다
- repeat(): Java 11에서 제공되는 String 클래스의 메서드로 문자열을 주어진 횟수만큼 반복해서 새로운 문자열을 생성한다
- StringBuilder에 담은 문자열을 새로운 String으로 변환해둔 다음 StringBuilder에 0을 추가하여 물을 추가해준다
- 그 다음 String으로 변환해둔 문자열을 물까지 추가한 StringBuilder의 뒤에 new StringBuilder(result)로 다시 추가해주는데 이때 .reverse()를 사용해서 뒤집어서 추가해주고 String으로 변환하여 완성된 문자열을 반환한다.
다른 풀이
class Solution {
public String solution(int[] food) {
String answer = "0";
for (int i = food.length - 1; i > 0; i--) {
for (int j = 0; j < food[i] / 2; j++) {
answer = i + answer + i;
}
}
return answer;
}
}
- 매우 간단한 문법으로 깔끔하게 문제를 해결할 수 있는 방법을 생각했다는 것이 놀라워서 가져왔다.
- 먼저 문자열 answer 변수에 물을 뜻하는 "0"을 저장하고 2중 반복문을 돌린다.
- 이때 처음 반복문의 i는 food.length -1 로 food의 마지막 인덱스부터 1씩 감소한다
- 두 번째 반복문의 j는 0부터 시작하고 food[i] / 2 만큼 반복하는데 배치할 음식의 개수만큼 반복할 수 있도록 구성한다.
- 그다음 수행할 로직으로 answer의 양쪽으로 i(음식의 번호)를 j(음식의 개수)만큼 추가시켜주면 매우 깔끔하게 문제를 해결할 수 있다.
콜라 문제
문제
- 프로그래머스 - https://school.programmers.co.kr/learn/courses/30/lessons/132267
- 빈 병 a개를 가져다 주면 콜라 b병을 주는 마트가 있을 때 빈 병 n개를 가져다 주면 몇 병을 받을 수 있는지 계산하는 문제
- 보유 중인 빈 병이 a개 미만이면 추가적인 빈 병을 받을 수 없음
제한조건
- 1 <= b < a <= n <= 1,000,000
- 정답은 항상 int 범위를 넘지 않게 주어짐
입출력 예시
나의 풀이
class Solution {
public int solution(int a, int b, int n) {
int answer = 0;
while (n >= a) {
int newCola = (n / a) * b;
answer += newCola;
n = newCola + (n % a);
}
return answer;
}
}
- 처음에는 n % 2 == 0의 조건으로 분기를 나누어 코드를 작성했지만 오히려 이 분기가 필요없이 단순한 연산으로 문제를 해결할 수 있다는 것을 알았다.
- 빈 병 n을 a로 나누게 되면 새로 콜라를 받을 수 있는 횟수가 나오게 된다
- 빈 병 a개를 가져다 주면 b개를 주기 때문에 b / a * b 는 새로 콜라를 받는 개수가 되기 때문에 이 개수를 반복문 안에서 누적연산하면 된다.
- 빈 병 n이 a를 나누었을 때 나머지는 새로 받은 콜라를 다 마시고 합쳐서 다시 반복한다.
- 이 때 빈 병의 개수가 a보다 작게되면 콜라를 받을 수 없기 때문에 반복을 중지한다
다른 풀이
class Solution {
public int solution(int a, int b, int n) {
return (n > b ? n - b : 0) / (a - b) * b;
}
}
- 반복문이 없이 단순히 수학적으로 문제를 푼 방식이 너무 신기하고 수학을 잘 모르는 나로서는 생각하기 어려운 방식이지만 한번쯤 풀이해보면 좋을 것 같다고 생각하여 가져왔다.
- 다만 직관성이나 가독면 성에서는 반복문 풀이가 더 이해하기 쉬울 수 있어 실제 프로그래밍에서의 사용은 고민이 필요할 것 같다
- (n > b ? n - b : 0)
- n이 b보다 큰 경우에는 n - b를, 그렇지 않으면 0을 사용했는데 교환이 한 번이라도 이뤄지려면 n이 최소 b + 1이상이여야 한다는 것을 나타낸다.
- 문제의 기본 제한으로 b < a <= n이기 때문에 n < b인 상황은 교화 자체가 불가능한 것을 처리한 삼항 연산자이다.
- / (a - b)
- 한 번 교환할 때마다 실제로 소모되는 빈 병의 개수이다
- 이 개수를 삼항 연산자의 조건이 true일 때 연산되는 n - b 값과 나누어서 실제 교환이 가능한 총 횟수를 구한다
- 처음에 n개의 빈 병중에서 마지막 단계 직전까지는 최소한 b개는 남아 있어야 한 번의 교환을 진행할 수 있다고 보고 n - b만큼을 뺀뒤 한 번 교환 시 줄어드는 양(a - b)으로 나눈 것이다
- ... * b
- 실제 교환이 가능한 총 횟수에 교환 한번에 받을 수 있는 새 콜라 수를 곱해서 최종적으로 얻을 수 있는 병의 총 수를 계산한다
SQL(MySQL)
식품분류별 가장 비싼 식품의 정보 조회하기
문제 및 테이블 예시
- 프로그래머스 - https://school.programmers.co.kr/learn/courses/30/lessons/131116
- FOOD_PRODUCT 테이블에서 식품분류별로 가격이 제일 비싼 식품의 분류, 가격, 이름을 조회하는 SQL 문을 작성
- 식품분류는 '과자', '국', '김치', '식용유'인 경우만 출력
- 결과는 식품 가격을 기준으로 내림차순 정렬
입출력 예시
나의 풀이
WITH, INNER JOIN, GROUP BY
WITH MAX_PRICE AS (
SELECT
CATEGORY, MAX(PRICE) AS MAX_PRICE
FROM FOOD_PRODUCT
GROUP BY CATEGORY
)
SELECT
FD.CATEGORY,
FD.PRICE AS MAX_PRICE,
FD.PRODUCT_NAME
FROM FOOD_PRODUCT AS FD
JOIN MAX_PRICE AS MP
ON FD.CATEGORY = MP.CATEGORY AND FD.PRICE = MP.MAX_PRICE
WHERE FD.CATEGORY IN ('과자','국','식용유','김치')
ORDER BY MAX_PRICE DESC
- 저번 SQL 풀이에서 기억을 되찾아서 알게된 WITH를 사용하여 서브 쿼리를 설정하고 쿼리를 작성했다.
- 먼저 WITH를 통해 GROUP BY를 걸어서 CATEGORY별 최대 가격을 조회하는 쿼리를 만들었다.
- 그 다음 메인 쿼리에서 이 서브 쿼리의 결과와 INNER 조인을 하는데 이때 카테고리와, 가격을 서로 매칭시켜서 카테고리별 최대 금액이 매칭되도록 한다음 조회할 컬럼들만 SELECT 절에 입력한다.
- 그 다음 조건 요구사항을 위해 WHERE 절에 IN을 사용하여 과자, 국, 식용유, 김치만 조회하도록 하고 최대 가격을 내림차순으로 정렬하여 최종 결과를 반환했다
- 메인 쿼리의 WHERE 절을 서브쿼리에서 GROUP BY를 할 때 HAVING을 사용하여 조건을 지정해도 된다
다른 풀이
ROW_NUMBER()
SELECT CATEGORY,
PRODUCT_NAME,
PRICE
FROM (
SELECT CATEGORY,
PRODUCT_NAME,
PRICE,
ROW_NUMBER() OVER(PARTITION BY CATEGORY ORDER BY PRICE DESC) AS RN
FROM FOOD_PRODUCT
WHERE CATEGORY IN ('과자', '국', '식용유', '김치')
) T
WHERE RN = 1
ORDER BY PRICE DESC;
- 비슷한 풀이를 할 때 윈도우 함수인 ROW_NUMBER()를 활용할 수 있다는 것도 항상 함께 생각해보면 상황에 따라 쿼리를 최적화 할 수 있지 않을까 생각된다.
WITH ORDERS AS (
SELECT PRODUCT_ID,
SUM(AMOUNT) AS AMOUNT
FROM FOOD_ORDER
WHERE PRODUCE_DATE LIKE '2022-05%'
GROUP BY PRODUCT_ID
)
SELECT
FP.PRODUCT_ID,
FP.PRODUCT_NAME,
FP.PRICE * O.AMOUNT AS TOTAL_SALES
FROM FOOD_PRODUCT AS FP
JOIN ORDERS AS O
ON FP.PRODUCT_ID = O.PRODUCT_ID
ORDER BY TOTAL_SALES DESC, FP.PRODUCT_ID
5월 식품들의 총매출 조회하기
문제 및 테이블 예시
- 프로그래머스 - https://school.programmers.co.kr/learn/courses/30/lessons/131117
- FOOD_PRODUCT와 FOOD_ORDER 테이블에서 생산일자가 2022년 5월인 식품들의 식품 ID, 식품 이름, 총매출을 조회하는 SQL문 작성
- 결과는 총매출을 기준으로 내림차순 정렬하고 총매출이 같다면 식품 ID를 기준으로 오름차순 정렬
입출력 예시
나의 풀이
WITH, INNER JOIN, GROUP BY
WITH ORDERS AS (
SELECT PRODUCT_ID,
SUM(AMOUNT) AS AMOUNT
FROM FOOD_ORDER
WHERE PRODUCE_DATE LIKE '2022-05%'
GROUP BY PRODUCT_ID
)
SELECT
FP.PRODUCT_ID,
FP.PRODUCT_NAME,
FP.PRICE * O.AMOUNT AS TOTAL_SALES
FROM FOOD_PRODUCT AS FP
JOIN ORDERS AS O
ON FP.PRODUCT_ID = O.PRODUCT_ID
ORDER BY TOTAL_SALES DESC, FP.PRODUCT_ID
- 처음에는 FOOD_PRODUCT테이블과 FOOD_ORDER테이블을 조인해서 문제를 해결해보려고 시도했는데 쿼리문을 작성하다가 문제를 잘못 파악하여 문제를 다시 읽고 위와 동일한 방법으로 문제를 해결했다
- FOOD_ORDER의 PRODUCT_ID 별로 그룹화를 진행할 때 생산일자가 2022년 5월인 상품들로 그룹화를 한 다음 주문 수량을 모두 묶는 쿼리를 WITH 문을 활용해 ORDERS 쿼리를 정의했다.
- 그 다음 메인 쿼리에서 FOOD_PRODUCT 테이블과 두 테이블의 PRODUCT_ID가 같은 값끼리 INNER JOIN을 진행하여 테이블을 묶었는데 여기서 FOOD_PRODUCT 테이블의 PRICE와 ORDERS 테이블의 수량을 곱해서 매출액을 구했다
- 이후 문제 요구사항을 맞추기 위해 ORDEY BY를 활용하여 매출을 기준으로 내림차순하고 매출액이 같으면 PRODUCT_ID를 오름차순으로 결과를 정렬했다.
다른 풀이
SELECT
o.PRODUCT_ID,
p.PRODUCT_NAME,
SUM(o.AMOUNT * p.PRICE) AS TOTAL_SALES
FROM
FOOD_PRODUCT p INNER JOIN FOOD_ORDER o
ON p.PRODUCT_ID = o.PRODUCT_ID
WHERE
o.PRODUCE_DATE REGEXP '2022-05'
GROUP BY
o.PRODUCT_ID
ORDER BY
TOTAL_SALES DESC,
PRODUCT_ID ASC;
- 이 방법이 내가 원래 접근해보려고 했던 방식이었는데 WITH문 없이도 GROUP BY를 FOOD_ORDER의 PRODUCT_ID를 묶어서 바로 메인쿼리에서 FOOD_ORDER와 FOOD_PRODUCT의 가격을 곱해서 구한 매출액을 SUM()으로 합계를 구하면 PRODUCT_ID 별 매출액을 구할 수 있었다
- 여기서 WHERE절에서 조회할 날짜를 지정하고 정렬 로직도 맞춰 주면 2022년 5월의 상품별 매출액을 요구사항의 정렬을 기준으로 깔끔하고 명확하게 구현할 수 있다.
- 이 방법을 통해서 GROUP BY를 조금 더 잘 쓸 수 있을 것 같다.
- 다만 여기서 REGEXP 대신 LIKE나 BETWEEN을 사용하는 것이 더 성능과 가독성 면에서 더 적절하고 MySQL에서는 문제가 없을 수 있지만 SQL 표준에서는 SELECT 절에있는 모든 비집계 컬럼이 GROUP BY에 포함되어야 하기 때문에 PRODUCT_NAME도 포함시켜주어야 한다.
728x90
'기타 개발 공부 > 온라인 코딩 테스트 회고' 카테고리의 다른 글
Java - 모의고사, 소수 만들기, 덧칠하기 (0) | 2025.03.31 |
---|---|
Java - 명예의 전당(1), 카드 뭉치, 과일 장수 (0) | 2025.03.28 |
Java - 두 개 뽑아서 더하기, 가장 가까운 같은 글자 / SQL(MySQL) - 조건에 맞는 사용자와 총 거래금액 조회, 가격대 별 상품 개수 구하기, 즐겨찾기가 가장 많은 식당 정보 출력하기 (0) | 2025.03.18 |
Java - 시저 암호, 숫자 문자열과 영단어, 문자열 내 마음대로 정렬하기, K번째 수 (0) | 2025.03.16 |
Java - 삼총사, 크기가 작은 문자열, 최소직사각형 / SQL(MySQL) - 오랜 기간 보호한 동물(2), 보호소에서 중성화한 동물, 조건별로 분류하여 주문상태 출력하기 (0) | 2025.03.13 |