-
728x90
DP - 1463, 11726, 11727, 9095, 10844, 11057, 2193, 9465, 2156, 11053, 11055, 11722, 11054, 1912, 2579, 1699, 2133, 9461, 2225, 2011, 1105
출처: https://plzrun.tistory.com/entry/알고리즘-문제풀이PS-시작하기 [plzrun's algorithm]문제를 접하고 바로 저번 학기에 수강했던 알고리즘 수업 내용이 떠올랐고, 대략적으로 풀이 내용이 기억났었는데 구현 방식이 구체적으로 떠오르지 않아 교재였던 Introduction to Algorithms(3rd Edition) - Cormen을 다시 살펴봤다.
교재에서는 DP파트에 들어있지 않고, 이 문제와 동일하지만 조금 더 요구사항이 있는(교재에서는 부분도 함께 출력하라고 함) 문제로 분할정복(Divide&Conquer) 파트에서 다뤄져 있다.
먼저 교재에서는 두 가지 풀이 방법이 제시되어있는데, 1) 주먹구구식 2)분할계획법 풀이이다.
(엄밀히 말하면 주먹구구식 풀이는 그냥 간단하다고 언급만 되어있고 구현은 슈도코드도 안나와있다..)어쨌든 지금은 공부하는 과정이니까... 그 두 가지 풀이를 모두 풀어보고 한 번 더 DP로도 풀어봤다.
결과적으로 주먹구구식 풀이는 역시나 메모리초과가 발생하고 나머지 풀이는 정답 결과를 받았다!<첫 번째 풀이(주먹구구식)> - 메모리 초과
12345678910111213141516171819202122232425262728293031323334353637383940package dp;public class N1912_RuleofThumbs {public static void main(String[] args) {Scanner sc = new Scanner(System.in);int n =sc.nextInt();int arr[] = new int[n];for(int i=0; i<n; i++) {arr[i] = sc.nextInt();}int max=-9999999;int sum[][] = new int[n][n];for(int i=0; i<n; i++) {for(int j=0; j<n; j++) {if(i>j) {sum[i][j] = -99999999;}else if(i == j) {sum[i][j] = arr[j];}else {sum[i][j] = sum[i][j-1]+arr[j];}}}for(int i=0; i<n; i++) {for(int j=i; j<n; j++) {}}System.out.println(max);}}Colored by Color Scripter첫 번째 풀이는 접근 방식이 아주 심플하다. 그냥 발생할 수 있는 모든 부분 배열을 구하고 전부 비교하는 것이다.
책에서는 간단하다고 적혀있는데 사실 구현까지 그렇게 간단하게 느껴지지는 않았다...ㅋㅋㅋㅋㅋㅋ아무리 모든 부분 배열을 구한다쳐도 이를 저장하기 위해 2차원 배열을 생성해서 값을 받으면서
그래도 너무 메모리 낭비는 피하기 위해 2차원 배열을 대각선으로 쪼개고,
대각선 왼편에는 모두 -9999999값을 저장한뒤, 후에 최댓값을 구할 때도 대각선 오른쪽 방향만 고려하며 값을 구했다.그래도 시간, 공간복잡도 모두 n^2이다.
결과적으로 메모리 초과!<두 번째 풀이(분할정복)>
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465package dp;public class N1912_DC{static int arr[];public static void main(String[] args) {Scanner sc = new Scanner(System.in);int n =sc.nextInt();arr = new int[n];for(int i=0; i<n; i++) {arr[i] = sc.nextInt();}int max = MaxPartialCal(0, n-1);System.out.println(max);}public static int CrossingCal(int low, int mid, int high) {int lmax=-999999;int lsum=0;for(int i=mid;i>=low;i--) {lsum = lsum + arr[i];if(lsum > lmax) {lmax = lsum;}}int rmax =-999999;int rsum =0;for(int i=mid+1;i<=high;i++) {rsum = rsum + arr[i];if(rsum > rmax) {rmax = rsum;}}return rmax + lmax;}public static int MaxPartialCal(int low, int high) {if(low ==high) {return arr[low]; //basecase : only one element}else {int mid = (low+high)/2;int lmax = MaxPartialCal(low, mid);int rmax = MaxPartialCal(mid+1,high);int cmax = CrossingCal(low,mid,high);if(lmax >= rmax && lmax>=cmax) {return lmax;}else if(rmax >= lmax && rmax >=cmax){return rmax;}else {return cmax;}}}}Colored by Color Scripter이 풀이는 교과서에 슈도 코드도 나와있었는데, 완전히 베끼지 않고 조금씩 참고하려다보니 구현하는게 많이 어려웠다.
분할정복법으로 접근할 때 키 포인트는 이 문제를 3가지 유형으로 나누는 것이다.
전체 배열의 중간점을 찍으면, 발생할 수 있는 최대 배열의 위치는 3가지가 존재한다.1) 중간점 왼쪽에 완벽히 포함된 경우
2) 배열이 중간점을 포함하고 걸쳐있는 경우
3) 중간점 오른쪽에 완벽히 포함된 경우따라서 세 가지 경우를 모두 비교해 최대값을 반환하는 것이 핵심 아이디어다.
2)경우에서는 최대부분배열이 중간점을 걸치고 있는 경우이기에,
먼저 중간점을 기준으로 왼쪽으로 쭉 읽어나가면서 값이 더 커지면 왼쪽 방향으로의 max값을 갱신하고,
이후 오른쪽으로 똑같이 쭉 읽어나가면서 max값을 갱신한 뒤,
마지막에 그 양쪽 max값을 더해주면서 최대 배열 길이를 반환하는 방식이다.
1)경우와 3)경우는 재귀적으로 중간점을 찍어 계속 나눠버리면
결국 언젠가는 배열 길이가 1이나 2)경우로 귀결된다.재귀적으로 계속해서 1), 2), 3) 경우들의 최대값을 반환하고 비교해서
전체 배열의 최대 부분 배열의 합을 출력한다.이 풀이 방법의 복잡도는 nlog(n)으로 1)풀이보다 우수하다.
low = 가장 왼쪽 index
high = 가장 오른쪽 index
mid = 중간 Index
lsum = 왼쪽 합
rsum = 오른쪽 합
lmax = 왼쪽 최대값
rmax = 오른쪽 최대값<세 번째 풀이(DP)>
1234567891011121314151617181920212223242526272829303132package dp;public class N1912{public static void main(String args[]) {Scanner sc = new Scanner(System.in);int n = sc.nextInt();int arr[] = new int[n];int dp[] = new int[n];for(int i=0; i<n; i++) {arr[i] = sc.nextInt();}dp[0] = arr[0];for(int i=1; i<n; i++) {}int max = -9999999;for(int i=0; i<n; i++) {max = Math.max(dp[i], max);}System.out.println(max);}}Colored by Color Scripter역시 알고리즘 분류에 다이나믹 프로그래밍으로 되어 있는 이유가 있다.
다른 풀이에 비해 너무 명쾌하다. 접근 방식을 떠올리기가 어려워서 그렇지..DP방식으로의 핵심적인 접근법은
최대값 = (이전까지의 최대값 + 지금 값) vs (지금 값)
두 가지 값을 비교하면서 그 위치의 최대 값에 입력해주고,
나중에 각 위치의 최대값을 비교해서 가장 큰 값을 출력하는 것이다.이전까지의 최대값에 현재 값을 더했을 때, 현재 값보다 더 낮다면,
1) 현재 값이 음수이거나, 2)이전까지의 최대값이 음수이면서, 현재 값보다 더 작다라는 뜻이기 때문이다.2)의 경우에는 더 고려할 필요없이 이전까지의 값을 버리고 현재 값부터 counting해주면 된다.
문제는 1)의 경우인데, 현재 값이 음수여도 뒤의 값에 따라 가져가는게 이득일 수도 있다.
그런데 이전까지의 최대값과 지금 값을 더하고, 지금 값과 비교하기 때문에
결국 지금 값이 음수여도 이전까지의 값이 양수라면 가져가게되고, 뒤에서 버릴지말지 판단하게된다.
그러므로 1)의 경우에도 문제가 없다.dp[] = 최대값을 담을 배열
dp[i] = Math.max(arr[i], dp[i-1]+arr[i]); -> 최대값 = (이전까지의 최대값 + 지금 값) vs (지금 값)Github: https://github.com/jaeuk9407/AlgorithmBOJ
'Algorithms' 카테고리의 다른 글
[백준 알고리즘] 1167번 Python 풀이 (0) 2021.01.01 [백준 알고리즘](DP) 2579번 Java 풀이 (0) 2020.04.22 [백준 알고리즘] (DP) 11054번 Java 풀이 (0) 2020.03.30 [백준 알고리즘] (DP) 11722번 Java 풀이 (0) 2020.03.30 [백준 알고리즘] (DP) 11055번 Java풀이 (0) 2020.03.27 댓글