문제
A non-empty array A consisting of N integers is given. Array A represents numbers on a tape.
Any integer P, such that 0 < P < N, splits this tape into two non-empty parts: A[0], A[1], ..., A[P − 1] and A[P], A[P + 1], ..., A[N − 1].
The difference between the two parts is the value of: |(A[0] + A[1] + ... + A[P − 1]) − (A[P] + A[P + 1] + ... + A[N − 1])|
In other words, it is the absolute difference between the sum of the first part and the sum of the second part.
For example, consider array A such that:
A[0] = 3
A[1] = 1
A[2] = 2
A[3] = 4
A[4] = 3
We can split this tape in four places:
P = 1, difference = |3 − 10| = 7
P = 2, difference = |4 − 9| = 5
P = 3, difference = |6 − 7| = 1
P = 4, difference = |10 − 3| = 7
Write a function:
class Solution { public int solution(int[] A); }
that, given a non-empty array A of N integers, returns the minimal difference that can be achieved.
For example, given:
A[0] = 3
A[1] = 1
A[2] = 2
A[3] = 4
A[4] = 3
the function should return 1, as explained above.
Write an efficient algorithm for the following assumptions:
N is an integer within the range [2..100,000];
each element of array A is an integer within the range [−1,000..1,000].
Copyright 2009–2022 by Codility Limited. All Rights Reserved. Unauthorized copying, publication or disclosure prohibited.
문제 해설
solution(int[] A)에 입력되는 배열 A는 비어있지 않으며 2 ~ 100,000개의 요소를 가질 수 있고,
배열 A의 요소에는 -1,000 ~ 1,000까지 들어갈 수 있다.
배열 A가 주어졌을 때 임의의 수 P를 기준으로 배열을 나눌 수 있는다. (P는 0보다 크고 배열의 길이보다 작다.)
만약 배열의 길이가 5이고 P가 2이면 A[0], A[1]를 하나의 파트로 구분하고
A[2], A[3], A[4]를 다른 파트로 구분할 수 있다.
이때 나누어진 두 파트 값의 차이를 구해준다.
예를 들어 배열 A를 다음과 같이 작성했을 때,
A[0] = 3
A[1] = 1
A[2] = 2
A[3] = 4
A[4] = 3
P = 1이면 (A[0]) - (A[1] + A[2] + A[3] + A[4]) = |3 − 10| = 7
P = 2이면 (A[0] + A[1]) - (A[2] + A[3] + A[4]) = |4 − 9| = 5
P = 3이면 (A[0] + A[1] + A[2]) - (A[3] + A[4]) = |6 − 7| = 1
P = 4이면 (A[0] + A[1] + A[2] + A[3] ) - (A[4]) = |10 − 3| = 7
위의 예시와 같이 임의의 숫자 P에 대해 나눈 배열은 각각 7, 5, 1, 7의 차이가 난다.
이때 가장 작은 값을 찾아 반환한다.
풀이 - 1
class Solution {
public int solution(int[] A) {
int min = Integer.MAX_VALUE;
for(int i=1; i<A.length; i++) {
int frontSum=0, backSum=0;
for(int j=0; j<i; j++) {
frontSum += A[j];
}
for(int k=i; k<A.length; k++) {
backSum += A[k];
}
min = Math.min(min, Math.abs(frontSum-backSum));
}
return min;
}
}
'풀이 - 1'에 대한 회고
역시 가장 먼저 생각해볼 수 있는 알고리즘은 반복문을 이용하는 것!
첫 번째 반복문을 통해 임의의 P 값을 가져온다.
그리고 임의의 P 값을 기준으로 index가 P보다 작은 배열의 요소들을 더하고,
index가 P와 같거나 큰 배열의 요소들을 더한다.
그리고 두 값의 차이를 절대값으로 계산하는 동시에 최소값인지 계산해준다.
이렇게 작성한 점수는 53%!
계산 결과는 정확하지만 퍼포먼스가 1도 안나온다!
당연히 반복문이 중첩되어있기 때문에 배열의 길이가 커지면 커질수록
계산하는데 오래 걸리기 때문이다.
그렇게 나온 시간복잡도는 O(n*n).
이중 for문이 아닌 다른 방법으로 다시 생각해보자!
풀이 - 2
class Solution {
public int solution(int[] A) {
int total = 0;
for(int subA : A) {
total += subA;
}
int min = Integer.MAX_VALUE;
int sum = 0;
for(int i=0; i<A.length-1; i++) {
sum += A[i];
min = Math.min(min, Math.abs(total - sum - sum));
}
return min;
}
}
'풀이 - 2'에 대한 회고
반복문을 두번 사용해도 상관없다.
다만, 이중 for문을 이용하면 '풀이 - 1'의 시간복잡도가 나오기 때문에 퍼포먼스에서 무조건 0점 나온다.
이번에는 배열 전체의 합계부터 구하고 반복문을 통해 차이를 구했다.
여기서 포인트는 'total - sum - sum'!
왜 전체 합계에서 sum을 두 번이나 빼줬을까?
문제에서는 임의의 수 P를 기준으로 A[0]부터 A[P-1]까지의 합계와 A[P]부터 A[N]까지의 합계를 구했다.
여기서 sum은 A[0]부터 A[P-1]까지의 합계를 의미한다.
그리고 나머지 합계는 전체 합계에서 sum을 빼면 자동으로 계산된다.
즉, 편의상 나머지 합계를 R이라고 했을 때 R = total - sum이 되는 것이다.
(당연히 A[P]부터 A[N]까지의 합계를 구하기 위해 반복문을 실행할 필요가 전혀 없다.)
그러니 P를 중심으로 구분된 두 배열 집단의 차이는 R - sum == (total - sum) - sum인 것!
그래서 total에서 sum을 두 번 빼준것이다.
코드 수정 후 결과는?!!
드디어 퍼포먼스도 100%가 나왔다.
그리고 시간 복잡도도 O(n)으로 나와 이중 for문보다 훨씬 효율적이라는 것을 확인할 수 있었다.
다른 사람의 풀이
class Solution {
public int solution(int[] A) {
int[] dp = new int[A.length+3];
final int len = A.length;
dp[0] = A[0];
for(int i=1 ; i<len ; i++) {
dp[i] = dp[i-1] + A[i];
}
int mini = Integer.MAX_VALUE;
for(int i=1; i<len ; i++) {
int left = dp[i-1];
int right = dp[len-1]-dp[i-1];
int temp = Math.abs(left - right);
mini = Math.min(temp,mini);
}
return mini;
}
}
'다른 사람의 풀이'를 통한 회고
다른 사람의 코드를 찾아보다가 코드가 조금 복잡해 보이지만 이런 알고리즘도 있다는 것을 학습하고 싶어 가져왔다.
이 사람은 DP(동적 계획법)을 통해 테스트를 해결했다고 한다.
먼저 배열 dp에 index에 따른 합계를 계산해준다.
그리고 임의의 숫자 P에 따라 구분된 배열의 값을 dp 배열에서 꺼내 최소값을 계산한다.
'풀이 - 2'에 비해 소스코드가 조금 더 복잡해 보인다.
하지만 결과에서 알 수 있듯이 DP 또한 효율적인 알고리즘임은 틀립없다.
코딩테스트 뿐만 아니라 알고리즘 공부를 위해서라도 DP에 대해 알아두면 좋을 것 같다.
'Practice > 코딩 테스트' 카테고리의 다른 글
[Codility] Lesson 4 : PermCheck - JAVA (0) | 2022.06.11 |
---|---|
[Codility] Lesson 4 : FrogRiverOne - JAVA (0) | 2022.06.08 |
[Codility] Lesson 3 : PermMissingElem - JAVA (0) | 2022.06.01 |
[Codility] Lesson 3 : FrogJmp - JAVA (0) | 2022.06.01 |
[Codility] Lesson 2 : OddOccurrencesInArray - JAVA (0) | 2022.05.31 |