알고리즘/백준

[백준] 10819번 : 차이를 최대로 - 자바(JAVA)

Junuuu 2022. 2. 1. 00:01
반응형

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

 

10819번: 차이를 최대로

첫째 줄에 N (3 ≤ N ≤ 8)이 주어진다. 둘째 줄에는 배열 A에 들어있는 정수가 주어진다. 배열에 들어있는 정수는 -100보다 크거나 같고, 100보다 작거나 같다.

www.acmicpc.net

문제 해석

N개의 정수로 이루어진 배열 A가 주어지면 정수의 순서를 적절히 바꿔 아래의 식의 최댓값을 구해야 한다.

| A[0] - A[1] | + | A[1] - A[2] | + ....... + | A[N-2] - A[N-1] |

입력

첫째 줄에는 N이 주어진다.

둘째 줄에는 배열 A에 들어가 있는 정수들이 주어진다.

 

제약조건

3 <= N <= 8

-100 <= 배열의 정수들 <= 100


출력

배열의 순서를 적절히 바꾸어 최댓값을 출력한다.


문제 풀이 전 설계

첫째 줄에 N을 입력받고 2번째 줄에 배열의 정수를 입력받는다.

이후에 공백을 기준으로 배열의 정수들을 나누어 저장한다.

 

최댓값을 구하는 방법 예상

1. 모든 숫자 조합을 다 구성하여 값을 구해본다 (비효율적이라 싫음)

 

2. 절댓값들이 모두 더해지기 때문에 숫자를 정렬하고 반으로 나누어 홀수 인덱스에는 큰 값들을 짝수 인덱스에는 작은 값들을 넣는다.

 

B배열에는 0번 인덱스부터 제일 작은 값부터 중간값까지 커지면서 정렬되어있습니다.

C배열에는 0번 인덱스부터  제일 큰 값부터 중간값까지 작아지면서 정렬되어 있습니다.

 

예시

정렬된 값 1 4 8 10 15 20

B배열 1 4 8

C배열 20 15 10

조합된 배열 1 20 4 15 8 10 = 55

20 1 15 4 10 8 = 50

 

위의 방법은 제일 작은 값이나 제일 큰 값이 좌측 구석에 박혀버림으로써 1번밖에 연산이 일어나지 않음으로써 손해가 발생합니다. 제일 작은 값이나 큰 값은 연산이 2번 일어나야지 무조건 좋습니다.

 

3. 위의 방법과 유사하게 숫자를 정렬하고 반으로 나누어 인덱스에 나누어 저장한다. 하지만 제일 큰 값이나 작은 값이 연산이 2번 일어나도록 조합을 조금 달리해봅니다.

 

B배열에는 0번 인덱스부터 제일 작은 값부터 중간값까지 커지면서 정렬되어있습니다.

C배열에는 0번 인덱스부터  제일 큰 값부터 중간값까지 작아지면서 정렬되어 있습니다.

 

B배열의 큰 값부터 작은 값까지를 A배열 0,2,4...로 배치하고

C배열의 큰 값부터 작은 값까지를 A배열 1,3,5...로 배치합니다.

 

예시

정렬된 값 1 4 8 10 15 20

B배열 1 4 8

C배열 20 15 10

조합된 배열 8 20 4 15 1 10 = 62

 

위의 방법은 제일 큰 값이나 작은 값에서 연산이 2번 발생합니다.

또한 중간값(8, 10)이 좌측 끝, 우측 끝으로 이동하여 연산에서 가장 영향력이 약해집니다.

 

만약에 배열에 음수가 존재할 때도 생각해보겠습니다

정렬된 값 -20 -10 10 20

B배열 -20 -10

C배열 10 20

조합된 배열 -10 20 -20 10 = 100 (잘 동작하는 듯 보입니다)

 

위에서는 배열의 크기가 짝수인데 홀수일 때도 생각해보겠습니다 + 음수 포함

정렬된 값 -20 7 10 20 35

 

Case 1

B배열 -20 7

C배열 10 20 35

조합된 배열 7 35 -20 20 10  (잘 동작하는 것 같습니다)

 

Case2

B배열 -20 7 10

C배열 20 35

조합된 배열 10 20 7 35 -20 (-20 가장 작은 값이 우측으로 가버리면서 연산에 손실이 발생할 것 같습니다)

 

 

어느 정도 생각정리를 하였으니 실제로 개발에 들어가 보겠습니다.

3번 시도해보고 안된다면 완전 탐색인 1번 시도 예정

 

 


풀이하면서 고민해본 점

입력은 어떻게 받는 것이 효율적인가( Scanner vs BufferedReader)

https://junuuu.tistory.com/7?category=968252 

 

[Java] Scanner 와 BufferedReader의 차이점

두 클래스는 모두 자바에서 입력을 받는 데 사용이 됩니다. BufferedReader 우선 BufferedReader는 InputStreamReader에 버퍼링 기능이 추가된 Class입니다. 사용자가 요청할 때마다 데이터를 읽어 오는 것이 아

junuuu.tistory.com

출력은 어떻게 하는 것이 효율적인가 ( System.out.println vs BufferedWriter) (String vs StringBuilder)

https://blog.naver.com/scyawer/222072629287

 

자바)System.out.println vs. BufferedReader

하드디스크는 원래 속도가 엄청 느립니다. 하드뿐만 아니라 키보드나 모니터와 같은 외부 장치와의 데이터 ...

blog.naver.com

 

위의 방법으로 시도해보았을 때 95% 에서 틀렸습니다가 나왔습니다.

반례 케이스를 찾다 보니 -1 0 0 같은 경우의 배열이 들어오면

최댓값을 0 -1 0으로 조합했을 때 2지만 위의 방법은 -1 0 0으로 조합하여 1이 나오는 경우가 발생했습니다.

따라서 배열의 길이가 3이고 0이 2개일 때를 카운트하여 특별 케이스로 처리하였습니다.

하지만 이 경우에도 95%에서 틀렸습니다가 나왔습니다.

 

또한 반례 중 -4 -3 -1 -1 0  배열이 존재합니다.

위의 방법으로 조합하게 되면 -3 0 -4 -1 -1으로 값이 10이 나오지만

-1 -4 0 -3 -1의 경우 값이 12가 나옵니다.

 

이처럼 홀수일 때  -3, -1을 양끝에 배치할 것인가 -1, -1을 양끝에 배치할 것인가에 따라 결과가 달라집니다.

코드 또한 복잡해질 것 같고 또 다른 반례가 나타날 것 같아 완전 탐색으로 계획을 변경했습니다ㅠㅠ 

 

완전 탐색의 핵심 로직만 살펴보겠습니다

static void perm(int depth) {
		if( depth == N) {
			sum();
			return ;
		}
		
		for(int i = depth; i< N; i++) {
			swap(i, depth);
			perm(depth + 1);
			swap(i, depth);
		}
		
	}

perm은 재귀함수입니다.

swap() 메서드는 인자의 인덱스를 기준으로 순서를 교체합니다.

depth는 0부터 시작하는데 {1, 2, 3} 배열의 예시를 들어보겠습니다.

 

depth = 0 일 때

반복문은 0부터 2까지 반복합니다.

 

i=0 일 때

swap(0, 0)

//현재 배열 상태 1, 2, 3

perm(1)

swap(0, 0)

//배열을 다시 되돌림

 

i=1 일 때

swap(1, 0)

//현재 배열 상태 2, 1, 3

perm(1)

swap(1, 0)

 

i=2 일 때

swap(2, 0)

//현재 배열 상태 3, 2, 1

perm(1)

swap(2, 0)

 

이를 통해 depth 0 은 첫 번째 자리를 1, 2, 3으로 고정하며 perm(depth+1)을 호출하여 perm(2)를 호출합니다.

 

여기까지 봤을 때는 아직 뭔지 감이 잘 오지 않으니

i=0 일 때 호출한 perm(1)으로 더 들어가 보겠습니다.

 

depth = 1 일 때

반복문은 1부터 2까지 반복합니다.

 

i=1 일 때

swap(1, 1)

//현재 배열 상태 1, 2, 3

perm(2)

swap(1, 1)

 

i=2 일 때

swap(2, 1)

//현재 배열 상태 1, 3, 2

perm(2)

swap(2, 1)

 

이를 통해 depth 1은 두 번째 자리를 2, 3으로 고정하고 perm(depth+1)을 호출하여 perm(3)을 호출합니다.

 

perm 3이 경우에는 depth = 3 이므로 depth = N이 성립하여 sum() 메서드가 호출되며 return 됩니다.

이를 통해 하나의 순열이 만들어지고 계산하는 과정이 이루어집니다.

 

이를 반복하게 된다면 모든 경우의 수를 탐색하는 순열이 만들어지게 됩니다.

 


어려웠던 점

순열을 구성하는 코드에 익숙하지 않았다.


삽질한 코드

package algorithm6;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Collections;

/*
 * BOJ : 10819번 차이를 최대로
 * N개의 정수로 이루어진 배열 A의 정수의 순서를 적절히 바꿔서 다음 식의 최댓값을 구하는 프로그램
 * | A[0] - A[1] | + | A[1] - A[2] |  + .... + | A[N-2] - A[N-1] |
 */

public class MaxGap {
	public static void main(String[] args) throws NumberFormatException, IOException {
		ArrayList<Integer> arrayListA = new ArrayList<Integer>();
		ArrayList<Integer> spcialList = new ArrayList<Integer>();
		ArrayList<Integer> combinatedArray = new ArrayList<Integer>();
        
		int generalResult = 0;
		arrayListA = makeArray();
		spcialList = (ArrayList<Integer>) arrayListA.clone();
		combinatedArray = combiningArray(arrayListA);
		generalResult = calculateArray(combinatedArray);

		int specialResult = 0;		
		specialResult = checkSpecialCase(spcialList);
		printResult(generalResult, specialResult);

	}

	static ArrayList<Integer> makeArray() throws NumberFormatException, IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		int N = Integer.parseInt(br.readLine());
		String str = br.readLine();
		String[] strArray = str.split(" ");
		ArrayList<Integer> arrayListA = new ArrayList<Integer>();

		for (String strElement : strArray) {
			arrayListA.add(Integer.parseInt(strElement));
		}

		return arrayListA;
	}

	static ArrayList<Integer> combiningArray(ArrayList<Integer> arrayListA) {
		// A배열 : 정렬된 배열
		// B배열 : A[0] ~ A[middle-1]
		// C배열 : A[middle] ~ A[length-1]
		// conbinatedArray : A[middle-1] A[length-1] A[middle-2] A[length-2] .......
		// A[0] A[middle]
		Collections.sort(arrayListA);
		int length = arrayListA.size();
		int middle = length / 2;

		ArrayList<Integer> combinatedArray = new ArrayList<Integer>();

		if (isEven(length)) {
			for (int i = 0; i < length; i++) {
				if (isEven(i)) {
					combinatedArray.add(arrayListA.get(middle - 1 - (i / 2)));
				} else {
					combinatedArray.add(arrayListA.get(length - 1 - (i / 2)));
				}
			}
		} else {
			for (int i = 0; i < length; i++) {
				if (isEven(i)) {
					// B배열을 먼저 하고싶은데 length가 홀수인경우에는 B배열 이 C배열보다 길이가 1이 작음
					// 하지만 index기준으로 나누어버리게 되면 B배열 index가 더 사용되는일이 발생함
					// 예를 들자면 length=5 ,A배열 = {1,3,5,7,9}라면
					// for문은 0~4까지 반복하고 여기에 짝수는 0,2,4 홀수는 1,3임
					// 반면에 B배열 = {1,3} , C배열 = {5, 7, 9}
					// index가 매칭되지 않음 그래서 index = 4일 때 outOfIndex 에러가 발생하게됨
					// middle = 2 이며 middle - 1 - (i / 2) = 2 -1 - (4/2) = -1
					// 따라서 만약 outOfIndex가 발생하게 된다면 try-catch를 사용하여 C배열이 이용되도록함
					try {
						combinatedArray.add(arrayListA.get(middle - 1 - (i / 2)));
					} catch (Exception e) {
						// TODO: handle exception
						combinatedArray.add(arrayListA.get(length - 1 - (i / 2)));
					}

				} else {
					// 1 3
					combinatedArray.add(arrayListA.get(length - 1 - (i / 2)));
				}				
			}
		}
		System.out.println(combinatedArray);
		return combinatedArray;
	}

	static boolean isEven(int num) {
		if (num % 2 == 0) {
			return true;
		} else {
			return false;
		}
	}

	static int calculateArray(ArrayList<Integer> combinatedArray) {
		int sum = 0;
		for (int i = 0; i < combinatedArray.size() - 1; i++) {
			sum += Math.abs(combinatedArray.get(i) - combinatedArray.get(i + 1));
		}
		return sum;
	}

	static int checkSpecialCase(ArrayList<Integer> arrayListA) {
		int result = 0;
		int length = arrayListA.size();
		// -1 0 0 0 과 같은 일반적인 케이스는 조합을 하면 0 -1 0 0 으로 되어 값이 2로 최대값으로 나오지만
		// -1 0 0 과 같은 홀수에 사이즈가3인 특별한 케이스는 조합하면 -1 0 0이 되어 1이 출력됨 사실은 0 -1 0 으로 조합하여 2가 나와야함
		
		if(length == 3) {
			int count = 0;
			int temp = 0;
			for(int i =0; i<length;i++ ) {
				if(arrayListA.get(i).equals(0)) {
					count++;
				}
				else {
					temp = arrayListA.get(i);
				}
			}
			if(count==2) {
				result = temp > 0 ? temp*2 : (-temp) * 2 ; 
			}
		}
		return result;
	}

	static void printResult(int generalResult, int spcialResult) throws IOException {
		BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
		if (generalResult > spcialResult) {
			bw.write(generalResult + "\n");
		} else {
			bw.write(spcialResult + "\n");
		}
		bw.flush();
		bw.close();
	}
}

 

완전 탐색 코드

package algorithm6;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;

/*
 * BOJ : 10819번 차이를 최대로
 * N개의 정수로 이루어진 배열 A의 정수의 순서를 적절히 바꿔서 다음 식의 최댓값을 구하는 프로그램
 * | A[0] - A[1] | + | A[1] - A[2] |  + .... + | A[N-2] - A[N-1] |
 */

public class MaxGap {
	
	static int arr[];
	static int N;
	static int max;
	
	public static void main(String[] args) throws NumberFormatException, IOException {
		makeArray();
		perm(0);
		printResult(max);
	}

	static void makeArray() throws NumberFormatException, IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		N = Integer.parseInt(br.readLine());
		String str = br.readLine();
		String[] strArray = str.split(" ");
		arr = new int[N];
		for(int i=0; i<N; i++) {
			arr[i] = Integer.parseInt(strArray[i]);
		}		
	}
		
	static void perm(int depth) {
		if( depth == N) {
			sum();
			return ;
		}
		
		for(int i = depth; i< N; i++) {
			swap(i, depth);
			perm(depth + 1);
			swap(i, depth);
		}
		
	}
	
	static void sum() {
		int sum = 0;
		for(int i=0; i<N-1; i++) {
			sum += Math.abs(arr[i] - arr[i+1]);
		}
		if (sum > max) {
			max = sum;
		}
	}
	
	static void swap(int num1, int num2) {
		int temp = arr[num2];
		arr[num2] = arr[num1];
		arr[num1] = temp;
	}
	
	static void printResult(int result) throws IOException {
		BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));		
		bw.write(result + "\n");						
		bw.flush();
		bw.close();
	}
}