[백준] java 2798

Sundae·2023년 7월 22일
0

백준

목록 보기
10/63
post-custom-banner

https://www.acmicpc.net/problem/2798


문제

ㅋㅏ지노에서 제일 인기 있는 게임 블랙잭의 규칙은 상당히 쉽다. 카드의 합이 21을 넘지 않는 한도 내에서, 카드의 합을 최대한 크게 만드는 게임이다. 블랙잭은 ㅋㅏ지노마다 다양한 규정이 있다.

한국 최고의 블랙잭 고수 김정인은 새로운 블랙잭 규칙을 만들어 상근, 창영이와 게임하려고 한다.

김정인 버전의 블랙잭에서 각 카드에는 양의 정수가 쓰여 있다. 그 다음, 딜러는 N장의 카드를 모두 숫자가 보이도록 바닥에 놓는다. 그런 후에 딜러는 숫자 M을 크게 외친다.

이제 플레이어는 제한된 시간 안에 N장의 카드 중에서 3장의 카드를 골라야 한다. 블랙잭 변형 게임이기 때문에, 플레이어가 고른 카드의 합은 M을 넘지 않으면서 M과 최대한 가깝게 만들어야 한다.

N장의 카드에 써져 있는 숫자가 주어졌을 때, M을 넘지 않으면서 M에 최대한 가까운 카드 3장의 합을 구해 출력하시오.

입력

첫째 줄에 카드의 개수 N(3 ≤ N ≤ 100)과 M(10 ≤ M ≤ 300,000)이 주어진다. 둘째 줄에는 카드에 쓰여 있는 수가 주어지며, 이 값은 100,000을 넘지 않는 양의 정수이다.

합이 M을 넘지 않는 카드 3장을 찾을 수 있는 경우만 입력으로 주어진다.

접근 방법 / 풀이 과정

카테고리 설명에는 이렇게 적혀있다.
가장 간단한 알고리즘인, 모든 경우의 수를 검사하는 브루트 포스 알고리즘을 배워 봅시다.
설명과 같이 모든 경우의 수를 검사하는 문제라고 생각했다.

그런데 모든 경우의 수를 어떤 식으로 검사하지?
물론 컴퓨터는 모든 경우의 수를 계산할 수 있겠지만, 어떻게 하면 코드로 옮겨낼 수 있을까.
나의 변변찮은 뇌가 고민하기 시작했다.

경우의 수를 반복 검색하려면 반복문이 필요하다. 그리고 for문을 사용하려면 규칙이 필요하다.
다시 규칙의 시간이다.

총 경우의 수 구하기

5 6 7 8 9

------------------
5 6 7
5 6 8
5 6 9

5 7 8
5 7 9

5 8 9
-------------------
6 5 7 x
6 5 8 x
6 5 9 x 

6 7 8 
6 7 9

6 8 9
-------------------
7 5 6 x
7 5 8 x
7 5 9 x

7 6 8 x
7 6 9 x

7 8 9 
-------------------
8 5 6 x
8 5 7 x
8 5 9 x

8 6 7 x
8 6 9 x

8 7 9 x
-------------------

결론 -> 5 6 7 / 5 6 8 / 5 6 9 / 5 7 8 / 5 7 9 / 5 8 9 / 6 7 8 / 6 7 9 / 6 8 9 / 7 8 9

---------------------

x는 이미 중복된 경우를 뜻한다.
중복된 경우를 제하고 난 결론은 이러하다.
5 6 7 / 5 6 8 / 5 6 9 / 5 7 8 / 5 7 9 / 5 8 9 / 6 7 8 / 6 7 9 / 6 8 9 / 7 8 9

여기서 나는 약간 감동하였다. 규칙을 발견하였고 이는 곧 코드로 옮길 수 있다는 뜻이다.
어서 옮겨보자
.
.
.

package java_test;

import java.io.*;
import java.util.StringTokenizer;
import java.util.ArrayList;

public class Main {
    public static void main(String[] args)throws IOException {
       BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
       StringTokenizer st;
       st = new StringTokenizer(bf.readLine() , " ");
       int N = Integer.parseInt(st.nextToken());
       int M = Integer.parseInt(st.nextToken());
       /* 
      	조건식
      	i는 0부터 N-2까지
      	j는 i+1부터 N-1까지
      	z는 j+1부터 N까지        
        */
       ArrayList<Integer> array = new ArrayList<>();
       st = new StringTokenizer(bf.readLine() , " ");
       for(int i = 0; i < N; i++)    	   
    	   array.add(Integer.parseInt(st.nextToken()));
       int log = 0;
       for(int i = 0; i < N-2; i++) {
    	   
    	   for(int j = i+1; j < N-1; j++) {
    		   
    		   for(int z = j+1; z < N; z++) {
    			   int sum = array.get(i) + array.get(j) + array.get(z);
    			   if( M >= sum && sum > log) 
    				   log = sum;
    			     			   
    		   }
    	   }
       }
       System.out.println(log);
    }  
}

5 6 7 / 5 6 8 / 5 6 9 / 5 7 8 / 5 7 9 / 5 8 9 / 6 7 8 / 6 7 9 / 6 8 9 / 7 8 9
여기서 규칙은 이러하다.

첫번째 숫자를 i라고 보았을 때 i는 1씩 상승한다.
N-2까지이다. 마지막 세개의 숫자에서 멈춰야 하기 때문이다.

두번째 숫자를 j라고 보았을 때 j는 i+1에서 1씩 상승한다.
N-1까지. 위와 비슷한 이유로, 마지막 두개의 숫자에서 멈춰야 한다.

세번째 숫자를 z라고 보았을 때 z는 j+1에서 1씩 상승.
N까지이다.

조건은 세개의 숫자의 합이 M보다 같거나 작고, M에 근접한 숫자(log)의 합보다 클 때이다.

느낀점 / 배운점

아래와 비슷한 정답코드들을 보고나서, 나의 코드에도 실행횟수를 조금이나마 줄일 수 있는 코드들을 추가하면 좋겠다고 생각했다. 아래 코드는 구체적인 조건을 작성하면서 실행횟수를 줄였다.

		int answer = 0;
		
		for(int i=0; i<N-2; i++) { // 3개를 고르기 때문에 첫번째 카드는 N-2 까지
			
			if(arr[i] > M) continue; // 첫 번째 카드가 M보다 크면 건너뛴다.
			
			for(int j=i+1; j<N-1; j++) { // 두번째 카드는 N-1 까지
				
				if(arr[i] + arr[j] > M) continue; // 두 카드의 합이 M보다 크면 건너뛴다.
				
				for(int k=j+1; k<N; k++) { // 세번째 카드는 N까지 
					int temp = arr[i] + arr[j] + arr[k];
					
					if(M == temp) { // M과 세 카드의 합이 같은지 확인
						return temp;
					}
					
					if(answer < temp && temp < M) { // 세 카드의 합이 이전 합보다 크면서 M보다 작을 경우 갱신
						answer = temp;
					}
				}
			}
		

모든 경우의 수를 검색하는 방법. 브루트포스의 장점은 100%의 정확도를 보장한다고한다.
단점으로는 오래 걸리고 시간과 자원이 엄청나게 든다.
이러한 단점은 알고리즘에 중요한 시간복잡도에도 엄청난 영향을 줄 것이 분명하다.
하지만 어떻게 작성하느냐에 따라 크게 바뀔 수도 있기 때문에 개발자의 역량이 중요하다고 느꼈다.

profile
성장 기록 / 글에 오류가 있다면 댓글 부탁드립니다.
post-custom-banner

0개의 댓글