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
- 자바의 정석 기초편 ch4
- 자바 중급1편 - 날짜와 시간
- @Aspect
- 자바의 정석 기초편 ch2
- 자바의 정석 기초편 ch11
- 자바의 정석 기초편 ch14
- 스프링 입문(무료)
- 자바의 정석 기초편 ch6
- 자바 중급2편 - 컬렉션 프레임워크
- 자바 고급2편 - io
- 스프링 고급 - 스프링 aop
- 자바의 정석 기초편 ch1
- 자바의 정석 기초편 ch7
- 자바의 정석 기초편 ch13
- 데이터 접근 기술
- 스프링 mvc2 - 로그인 처리
- 2024 정보처리기사 시나공 필기
- 스프링 mvc1 - 스프링 mvc
- 자바 고급2편 - 네트워크 프로그램
- 자바의 정석 기초편 ch12
- 스프링 mvc2 - 검증
- 스프링 mvc2 - 타임리프
- 람다
- 스프링 트랜잭션
- 2024 정보처리기사 수제비 실기
- 자바의 정석 기초편 ch9
- 자바 기초
- 자바로 계산기 만들기
- 자바로 키오스크 만들기
- 자바의 정석 기초편 ch5
Archives
- Today
- Total
개발공부기록
Java - 직사각형 별찍기, 최대공약수와 최소 공배수, 3진법 뒤집기, 이상한 문자 만들기 / SQL(MySQL) - 오랜기간 보호한 동물(1), 카테고리 별 도서 판매량 집계하기 본문
기타 개발 공부/온라인 코딩 테스트 회고
Java - 직사각형 별찍기, 최대공약수와 최소 공배수, 3진법 뒤집기, 이상한 문자 만들기 / SQL(MySQL) - 오랜기간 보호한 동물(1), 카테고리 별 도서 판매량 집계하기
소소한나구리 2025. 3. 11. 14:20728x90
Java
양수의 개수와 덧셈
문제
- 프로그래머스 - https://school.programmers.co.kr/learn/courses/30/lessons/12969
- 두 개의 정수 n과 m이 주어질 때 별(*) 문자를 이용해 가로의 길이가 n, 세로의 길이가 m인 직사각형 형태를 출력
제한조건
- n, m은 각각 1000 이하인 자연수임
입출력 예시
나의 풀이
import java.util.Scanner;
class Solution {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int a = sc.nextInt();
int b = sc.nextInt();
for (int i = 0; i < b; i++) {
for (int j = 0; j < a; j++) {
System.out.print("*");
}
System.out.println();
}
}
}
- 2중 반복문을 활요하는 정석 풀이
다른 풀이
import java.util.Scanner;
import java.util.stream.IntStream;
public class Solution {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int a = sc.nextInt();
int b = sc.nextInt();
StringBuilder sb = new StringBuilder();
IntStream.range(0, a).forEach(s -> sb.append("*"));
IntStream.range(0, b).forEach(s -> System.out.println(sb.toString()));
}
}
- 이런 단순한 풀이도 Stream API를 활용한 방법이 신박해서 가져왔다
- IntStream.range(0, a)을 사용하면 0부터 a-1까지의 범위 숫자가 생성되어 0, 1, 2, 3, 4가 생성된다
- 그 이후 forEach(s -> sb.append("*");각 요소의 개수만큼 반복해주면 생성된 0, 1, 2, 3, 4의 개수 즉, 5번 만큼 반복하게 되고, 그 만큼 생성해준 Stringbuilder에 "*"을 추가해주면 "*"을 "*****"이 StringBuilder에 담기게 된다
- 그 다음 동일한 방법으로 StringBuilde를 toString()으로 문자열로 변환하여 range(0, b)에서 생성한 수의 개수만큼 반복해주면 된다
최대공약수(GCD), 최소공배수(LCM)
문제
- 프로그래머스 - https://school.programmers.co.kr/learn/courses/30/lessons/12940
- 입력받은 두 수의 최대공약수와 최소공배수를 반환하는 함수를 완성
- 배열의 맨 앞에 최대공약수, 그다음 최소공배수를 넣어 반환
- 3, 12의 최대공약수는 3, 최소공배수는 12이므로 [3, 12]를 반환하면 됨
제한조건
- 두 수는 1 ~ 1,000,000의 자연수임
입출력 예시
다른 풀이
사실상 못 풀었음, 공식을 몰라서 무작정 반복문과 조건문을 통해 구해보고자 하니 코드가 너무 비효율 적으로 된 것 같아서 중간에 포기하고 이런 문제는 공식을 보고 구현하는 것이 맞다고 생각하여 다양한 풀이를 해석해보고자 함
class Solution {
public int[] solution(int n, int m) {
int[] answer = new int[2];
// 최대 공약수 구하기
for (int i = 1; i <= Math.min(n, m); i++) {
if (n % i == 0 && m % i == 0) {
answer[0] = i;
}
}
// 최소 공배수 구하기
answer[1] = (n * m) / answer[0];
return answer;
}
}
최대공약수 구하기
- 입력 받은 두 수 중에 작은 값까지 반복하도록 Math.min(n, m)을 사용하여 반복 횟수를 제한한다
- 공약수는 두 수를 모두 나누어 떨어지게 하는 수인데 어떤 수가 두 수 중에 작은 값보다 크다면 애초에 공약수가 될 수 없으므로 작은 값까지 반복하면 된다
- 예를 들어 n이 3이고 m이 10인 경우 어떤 수인 a가 1, 2, 3 까지는 의미가 있지만 4로 나누는 것은 작은 수 3에 대해서는 공약수가 될 수 없기 때문에 의미가 없게 된다
- 즉, 최대공약수를 구할 때는 작은 값보다 큰 수 초과되는 수로는 나눌 필요가 없기 때문에 불필요한 반복문을 줄일 수 있다
- 반복문 내에서 1부터 작은 수까지 1씩 증가하는 수를 n과 m을 나누었을 때 나누어 떨어지면 answer[0]에 저장하도록 두 수의 공약수를 저장하게 되고 이때 가장 마지막에 저장되는 공약수가 최대공약수이다
최소공배수 구하기
- 최소공배수는 두 수의 배수 중에서 공통으로 나타나는 가장 작은 양의 정수이다.
- 예를 들어 4의 배수는 4, 8, 12, 16 24 ... 이고 6의 배수는 6, 12, 18 ,24 ...인데 두 수의 공통 배수는 12, 24 ... 중에서 가장 작은 12이가 최소공배수가 된다
- 임의의 두 수의 곱은 항상 공통 배수가 되는데 최소공배수는 아니기 때문에 곱한 값에 두 수의 최대공약수로 나누어 주게 되면 최소공배수를 구할 수 있다.
- 즉, 두 수의 곱을 최대공약수로 나누면 최소공배수가 된다는 공식을 활용하면되기 때문에 최대공약수만 알면 최소공배수는 구하기 쉽다.
class Solution {
public int[] solution(int n, int m) {
int gcd = getGCD(n, m); // 재귀 함수를 통해 최대공약수를 구함
int lcm = (n * m) / gcd; // 두 수의 곱을 최대공약수로 나누면 최소공배수가 됨
return new int[] {gcd, lcm};
}
// 유클리드 호제법을 이용한 재귀 함수
private int getGCD(int a, int b) {
if(b == 0) {
return a;
}
return getGCD(b, a % b);
}
}
- 재귀를 활용하여 유클리드 호제법이라는 것을 사용하면 간결하게 최대공약수를 구할 수 있다고 하는데 나는 현단계에선 이부분을 활용하기에는 한참 어려울 것 같아서 풀이 설명으로 이해를 하고자 했는데
- 유클리드 호제법을 활용하여 최대공약수를 구하는 getGCD(int a, int b) 메서드는 두 수를 나눈 나머지를 0이 될 때까지 계속 나누고 나머지 연산의 결과가 0이 되면 그 이전의 나눗셈의 대상이 되었던 수를 반환하여 최대공약수를 구한다.
- 최소공배수는 공식에 따라 동일하게 구한다
3진법 뒤집기
문제
- 프로그래머스 - https://school.programmers.co.kr/learn/courses/30/lessons/68935
- 자연수 n이 매개변수로 주어지고 n을 3진법 상에서 앞 뒤로 뒤집은 후 이를 다시 10진법으로 표현한 수를 return 하도록 함수를 완성
제한조건
- n은 1 ~ 100,000,000 인 자연수임
입출력 예시
나의 풀이
class Solution {
public int solution(int n) {
StringBuilder revBase3 = new StringBuilder();
while (n > 0) {
revBase3.append(n % 3);
n /= 3;
}
char[] charArr = revBase3.toString().toCharArray();
int answer = 0;
int digit = charArr.length-1;
for (char c : charArr) {
if (c == '0') {
digit--;
continue;
}
answer += (c - '0') * (int) Math.pow(3, digit);
digit--;
}
return answer;
}
}
- 우선 입력받은 수를 3진법으로 분할하여 저장하려면 문자열에 저장하는 것이 좋다고 생각하여 가변 문자열인 StringBuilder를 사용하여 반복문으로 입력받은 수가 3으로 나누었을 때 0이 될 때까지 3으로 나눈 나머지를 StringBuilder에 저장했다.
- 이렇게 저장하다보니 자연스럽게 3진수의 역순으로 문자열에 들어가게 되어 중간단계를 건너뛰게 되었다
- 이후 문자열 3진수를 10진수로 변환하는 방법이 도저히 떠오르지 않아서 Stream API를 사용해보려다가 원초적으로 문자 배열로 변화하여 계산하기로 결정했다.
- 3진법의 각 자리수를 digit이라고 선언하고 문자배열의 길이 -1로 선언하여 3진수의 자리수를 표현하였는데, -1을 한 이유는 3진수의 첫 번째 자리수가 3의 제곱이기 때문이다.
- 이 후에 반복문은 문자 배열의 0번째 인덱스 부터 순회하기 때문에 문자 배열의 요소가 '0'이라면 건너뛴 다음 digit-- 연산을 해주었다
- '0'이 아니라면 Math.pow(3, digit)을 사용하여 3진수의 제곱을 구하고, 문자 배열의 값을 '0'으로 빼서 3진수의 자리수를 구한다음 이 둘을 곱해서 answer 누적 저장하는 방식으로 10진수를 구했다.
다른 풀이
class Solution {
public int solution(int n) {
StringBuilder sb = new StringBuilder();
while(n > 0){
sb.append(n % 3);
n /= 3;
}
return Integer.parseInt(sb.toString(), 3);
}
}
- 다른건 볼 필요없이 Integer.parserInt()를 사용하면 편하게 3진수로 변환할 수 있다는걸 잊고 있었다...
- 첫 번째 인자에 문자열, 두 번째 인자에 변환하고자 하는 진수를 입력해주면 내부적으로 최적화된 로직을 통해 3진법으로 변환하기 때문에 오히려 더 안정적이다.
- 지금은 StringBuilder.append() 메서드로 바로 3진법을 뒤집어서 반환했지만 제대로된 3진법을 반환하고자 한다면 sb.insert(0, n % 3)으로 입력하면 올바른 3진법을 나타낼 수 있다.
이상한 문자 만들기
문제
- 프로그래머스 - https://school.programmers.co.kr/learn/courses/30/lessons/12930
- 한 개 이상의 단어로 구성되어 있는 문자열은 하나 이상의 공백문자로 구분되어 있음
- 각 단어의 짝수번째 알파벳은 대문자로, 홀수번째 알파벳은 소문자로 바꾼 문자열을 리턴하는 함수를 완성
제한조건
- 문자열 전체의 짝/홀수 인덱스가 아닌 단어(공백을 기준) 별로 짝/홀수 인덱스를 판단해야 함
- 첫 번째 글자는 0번째 인덱스로 보아 짝수번째 알파벳으로 처리해야 함
입출력 예시
나의 풀이
class Solution {
public String solution(String s) {
String[] strSplit = s.split(" ", -1);
StringBuilder answer = new StringBuilder();
for (int i = 0; i < strSplit.length; i++) {
StringBuilder sb = new StringBuilder(strSplit[i]);
StringBuilder sb2 = new StringBuilder();
for (int j = 0; j < sb.length(); j++) {
if (j % 2 == 0) {
sb2.append(sb.substring(j, j+1).toUpperCase());
} else {
sb2.append(sb.substring(j, j+1).toLowerCase());
}
}
if (i == strSplit.length -1) {
answer.append(sb2);
} else {
answer.append(sb2).append(" ");
}
}
return answer.toString();
}
}
- 문자열을 다루는 것이기 때문에 모두 StringBuilder를 사용했다
- 처음 주어진 문자열 s를 split(" ", -1)로 공백을 기준으로 단어와 빈 문자열(공백 위치)을 모두 보존한 문자열 배열로 나누었다
- 여기서 마지막에 계속 테스트가 통과 안되어 검색을 했었는데, 2번째 인자(limit)에 -1을 입력해 주어야 테스트가 제대로 통과된다는 것을 알았다
- 만약 -1 없이 split(" ")만 사용한다면 split 대상이 되는 문자열의 마지막에 공백이 있다면 이 마지막 공백을 포함하지 않고 문자열을 분리하기 때문에 로직을 수행한 이후 문자열을 복원할 때 원래 제공된 문자열과 길이가 달라질 수 있다
- 두 번째 인자(limit)에 -1을 입력해 주면 빈 문자열 토큰까지 모든 결과를 반환하기 때문에 다시 문자열을 합칠 때 제공된 문자열과 동일한 구조로 문자열을 합칠 수 있다
- 예를 들어 제공된 문자열이 a이후 공백, b 이후 공백 2번 c 이후 공백 일때 ("a_b__c_", 공백을 구분하기 쉽게 _로 표현) split(" ")로만 하면 "a", "b", "", "c" 가 되어 마지막 공백이 사라지지만 split(" ", -1)로 하면 마지막 공백이 제거되지 않고 유지 되어 "a", "b", "", "c", ""가 문자열 배열에 저장된다.
- 즉, split()을 사용할 때 트레일링 토큰(trailing token, 문자열 끝에 위치한 빈 문자열 토큰)을 제거하고 싶다면 split(" ")을 사용하면 되고 원본 상태를 유지하고자 트레일링 토큰을 유지하고자 할 때는 split(" ", -1)을 해주면 된다
- 그 이후에 요구사항에 따른 로직을 구현하기 위해 나누어진 각 문자열 배열을 별도의 StringBuilder로 변환하고 반복문을 통해서 substring(j, j+1)로 나뉘어진 각 문자열 토큰의 단어들을 조건문에 따라 대문자와 소문자로 변환시키고 새로운 StringBuilder에 저장시켰다
- 변환 작업이 진행되는 반복문이 종료되면 새로운 StringBuilder에 변환된 StringBuilder를 추가하는데, 이때 split(" ", -1)로 인해 날라갔던 공백을 붙이기 위해서 중간의 단어들의 변환이 끝나면 " " 을 붙이고 맨 마지막의 단어는 " "을 붙이지 않도록 로직을 구성했다
다른 풀이
class Solution {
public String solution(String s) {
String answer = "";
int cnt = 0;
String[] array = s.split("");
for(String ss : array) {
cnt = ss.contains(" ") ? 0 : cnt + 1;
answer += cnt%2 == 0 ? ss.toLowerCase() : ss.toUpperCase();
}
return answer;
}
}
- 문자열을 그대로 사용했지만 split("")을 사용하여 입력 문자열을 한 글자씩 배열로 분리하고 카운터인 int cnt = 0; 변수를 활용해 분리한 문자 배열에 " "(공백)이 있으면 cnt 변수의 값을 0으로 초기화 시켜서 다음 단어를 첫 번째 단어로 간주하도록 로직을 구성했다
- 3항 연산자를 통해 간단히 로직을 구성한 것이 인상 깊었고 나처럼 " "로 분리시키는게 아니라 문자열을 통째로 문자열 배열로 분리하여 count 변수와 " " 빈 문자열을 통해 각 단어를 구분한 것이 인상 깊었다.
- 여기서 문자열이 아닌 StringBuilder를 사용하는 것으로 변경한다면 성능도 올라가는 완벽한 코드가 될 것 같다
class Solution {
public String solution(String s) {
char[] arr = s.toCharArray();
int index = 0;
for (int i = 0; i < arr.length; i++) {
if (arr[i] == ' ') {
index = 0;
} else {
arr[i] = (index % 2 == 0) ? Character.toUpperCase(arr[i])
: Character.toLowerCase(arr[i]);
index++;
}
}
return new String(arr);
}
}
- 주어진 문자열을 애초에 문자 배열로 변환해서 문제를 해결하는 방식도 존재했는데 성능상 이게 제일 빠르고 코드도 매우 직관적이라고 가장 좋은 접근법이라고 생각되었다
- 마찬가지로 문자열을 통채로 문자 배열에 담았기 때문에 각 단어를 구분하기 위해 index라는 변수를 두어 배열의 요소가 ' '이면 0으로 초기화 시키는 로직을 두었다
- 여기서 Character 래퍼클래스에 toUpperCase, upLowerCase가 있는지 몰랐는데 이 문제를 통해 이 함수도 기억하게 될 것 같다
SQL(MySQL)
오랜 기간 보호한 동물(1)
문제 및 테이블 예시
- 동물 보호소에 들어온 동물의 정보를 담은 ANIMAL_INS 테이블과 동물 보호소에서 입략 보낸 동물의 정보를 담은 ANIMAL_OUTS 테이블이 존재할 때 아직 입양을 못 간 동물 중, 가장 오래 보호소에 있었던 동물 3마리의 이름과 보호 시작일을 조회하는 SQL문을 작성
- 결과는 보호 시작일 순으로 조회
입출력 예시
나의 풀이
SELECT
INS.NAME, INS.DATETIME
FROM ANIMAL_INS AS INS
LEFT JOIN ANIMAL_OUTS AS OUTS
ON INS.ANIMAL_ID = OUTS.ANIMAL_ID
WHERE
OUTS.ANIMAL_ID IS NULL
ORDER BY INS.DATETIME LIMIT 3
- 우선 입양을 간 데이터를 저장하고 있는 테이블(ANIMAL_OUTS)과, 모든 동물 보호소에 들어온 정보를 담고 있는 테이블(ANIMAL_INS)이 있으므로 우선 이 둘의 정보를 합쳐서 계산을 하는 것이 좋겠다고 생각하여 JOIN을 하려고 했다.
- 일반 JOIN(INNER)을 하게되면 두 테이블의 공통 정보(교집합)만 테이블에 저장되기 때문에 모든 동물 보호소에 들어온 데이터를 기준으로 입양을 간 데이터를 합치는 LEFT JOIN을 사용하였고, 외래키인 ANIMAL_ID를 기준으로 LEFT JOIN 하였다.
- LEFT JOIN으로 합치면 ANIMAL_INS 테이블 옆에 ANIMAL_OUTS의 데이터가 나란히 붙게 되는데, ANIMAL_ID를 기준으로 합쳤기 때문에 ANIMAL_OUTS에는 ANIMAL_INS의 ANIMAL_ID가 있는 데이터들의 정보가 없으므로 ANIMAL_OUTS의 컬럼들에 NULL인 데이터가 생겨난다
- 합쳐진 테이블에서 ANIMAL_OUTS의 컬럼들 중에 NULL인 데이터들이 동물 보호소에 있지만 입양을 가지 않은 동물들의 데이터이므로 이 테이블의 날짜를 기준으로 오름차순 정렬하여 3개만 자르게 되면 가장 오래 남아있는 동물의 데이터를 얻을 수 있다.
- 남아있는 동물의 이름과 보호 시작일을 조회해야 하므로 ANIMAL_INS의 NAME과 DATETIME을 조회하면 문제를 해결할 수 있다
다른 풀이
SELECT
NAME, DATETIME
FROM ANIMAL_INS
WHERE ANIMAL_ID
NOT IN (SELECT ANIMAL_ID FROM ANIMAL_OUTS)
ORDER BY DATETIME LIMIT 3
- 두 개의 테이블을 조회하기 때문에 LEFT JOIN이 아닌 서브 쿼리르 활용해도 된다.
- JOIN문이 없기 때문에 조금더 쿼리문이 간결해지며 WHERE 절에 NOT IN 키워드로 서브쿼리의 결과인 ANIMAL_OUTS의 ANIMAL_ID가 ANIMAL_INS의 ANIMAL_ID에 포함되어있지 않을 때의 데이터를 조회하도록 작성할 수 있다
카테고리 별 도서 판매량 집계하기
문제 및 테이블 예시
- 2022년 1월 카테고리 별 도서 판매량을 합산하고 카테고리, 총 판매량 리스트를 출력하는 SQL문 작성
- 결과는 카테고리명을 기준으로 오름차순 정렬
입출력 예시
나의 풀이
SELECT
CATEGORY,
SUM(SALES) AS TOTAL_SALES
FROM
BOOK AS B
JOIN
BOOK_SALES AS BS
ON
B.BOOK_ID = BS.BOOK_ID
WHERE
BS.SALES_DATE BETWEEN '2022-01-01' AND '2022-01-31'
GROUP BY CATEGORY
ORDER BY CATEGORY
- 우선 두 테이블의 공통 정보로 하나의 테이블로 합치기 위해 각 테이블의 BOOK_ID 컬럼으로 INNER JOIN을 사용했다
- 카테고리 별로 출력을 해야하므로 CATEGORY로 GROUP BY를 하고 요구사항에 따라 ORDER BY도 CATEGORY의 오름차순 순으로 출력하게 했다
- 이후 2022년 1월의 도서 판매량을 합산해야 하므로 BETWEEN 연산을 사용하여 2022년 1월의 데이터만 조회한 후 SUM(SALES)로 카테고리별 판매량을 합산하여 조회했다
- 날짜 조회는 DATE_FORMAT, 직접 비교 연산자를 해도되고 다양하게 접근할 수 있다
728x90