ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [백준] 10819번 : 차이를 최대로 - 자바(JAVA)
    알고리즘/백준 2022. 2. 1. 00:01
    728x90

    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();
    	}
    }

    댓글

Designed by Tistory.