[Gold III] 두 배열의 합 - 2143

용씨·2023년 1월 31일
0

프로그래밍 문제

목록 보기
1/3

문제 링크와 소스코드

문제 파악

문제가 요구하는 것은 A의 부 배열의 합에 B의 부 배열의 합을 더해서 T가 되는 모든 부 배열 쌍의 개수를 구하기 이다.

시간복잡도 구하기

1) subA, subB 만들기 (누적합)

A 배열으로부터 subA 배열을 만든다.
B 배열으로부터 subB 배열을 만든다.

subAA의 부배열의 합을 요소로 가지는 배열이다. (subB도 마찬가지)
무슨 말이냐면
A = {1, 3, 1, 2} 이라면, 부배열은 {1}, {1,3}, {1,3,1}, {1,3,1,2}, {3}, {3,1}, {3,1,2}, {1}, {1,2}, {2}를 말한다. 즉 부분 집합을 말한다.

subA{1, 4, 5, 7, 3, 4, 6, 1, 3, 2}로 각 부 배열의 합을 요소로 가진다.

subAsubB누적합으로 만든다.
List<Long>으로 만드는 방법1Long[] 으로 만드는 방법2가 있다.

방법1

List<Long> subA = new ArrayList<>();
List<Long> subB = new ArrayList<>();

for (int i = 0; i < N; i++) {
	long sum = inputA[i];
	subA.add(sum);
	for (int j = i + 1; j < N; j++) {
		sum += inputA[j];
		subA.add(sum);
	}
}

for (int i = 0; i < M; i++) {
	long sum = inputB[i];
	subB.add(sum);
	for (int j = i + 1; j < M; j++) {
		sum += inputB[j];
		subB.add(sum);
	}
}

방법2

Long[] subA = new Long[(N * (N + 1)) / 2]; // N은 inputA의 길이
Long[] subB = new Long[(M * (M + 1)) / 2]; // M은 inputB의 길이

// P1 = subA
int count = 0;
for (int i = 0; i < N; i++) {
	long sum = inputA[i];
	for (int j = i; j < N; j++) {
		if (i == j) {
			subA[count++] = sum;
		} else {
			sum += inputA[j];
			subA[count] = sum;
			if (count < subA.length - 1) {
				count++;
			}
		}
	}
}

// P2 = subB
count = 0;
for (int i = 0; i < M; i++) {
	long sum = inputB[i];
	for (int j = i; j < M; j++) {
		if (i == j) {
			subB[count++] = sum;
		} else {
			sum += inputB[j];
			subB[count] = sum;
			if (count < subB.length - 1) {
				count++;
			}
		}
	}
}

subA의 개수는 (N * (N + 1)) / 2
subB의 개수는 (M * (M + 1)) / 2
이는 부분 집합의 개수 구하기와 동일하다.

여기까지 시간복잡도는 O(N^2)이다.


2) subA와 subB 정렬 + 슬라이딩 윈도우

A의 부 배열의 합 + B의 부 배열의 합을 더한 값(-> sum)을 T와 비교해야 한다.
여기서 슬라이딩 윈도우를 쓴다.

슬라이딩 윈도우는 사용 시 조건이 있다.
정렬이 되어 있거나 양수만 있을 때 슬라이딩 윈도우를 사용할 수 있다.

슬라이딩 윈도우를 위해서 다음이 필요하다.

  • subA, subB의 포인터
  • 정렬된 subA, subB

subA의 포인터 = 0 & subA는 오름차순
subB의 포인터 = 0 & subB는 내림차순

while(true)문을 돌면서 subA 포인터가 위치한 값 + subB 포인터가 위치한 값(-> sum)을 T와 비교해서 다음 if-else 문으로 포인터값을 증가시켜준다.

if (sum > T) {
	ptB++; // sum의 값을 줄여야 한다. 내림차순으로 정렬되어있는 subB의 포인터 값을 증가한다.
} else if (sum < T) {
	ptA++; // sum의 값을 늘려야 한다. 오름차순으로 정렬되어있는 subB의 포인터 값을 증가한다.
} else { // sum == T
	/* 3에서 설명 */
}

정렬 - 여기서 subA의 길이만큼 정렬하기 때문에 O(N^2 log{N^2})
포인터 - O(N^2)
여기까지 시간복잡도는 O(N^2) + O(N^2 log{N^2})

3) subA와 subB의 동률 찾기

아까 넘어갔던 else 문을 여기서 설명한다.

subAsubB에는 각각 중복된 값이 존재할 수 있다.
배열을 정렬했기 때문에, 중복된 값이 존재한다면 바로 다음 인덱스에서 찾을 수 있다.

// 포인터
int ptA = 0;
int ptB = 0;

long result = 0;
while (true) {
	long currentA = subA[ptA];
	long target = T - currentA;

	if (subB[ptB] > target) {
		ptB++;
	} else if (subB[ptB] < target) {
		ptA++;
	} else { // target == subB[ptB]
		long countA = 0;
		long countB = 0;

		while (ptA < subA.length && subA[ptA] == currentA) {
			ptA++;
			countA++;
		}

		while(ptB < subB.length && subB[ptB] == target) {
			ptB++;
			countB++;
		}

		result += countA * countB;
	}

	if (ptA == subA.length || ptB == subB.length) {
		break;
	}
}

while문을 돌면서 값이 똑같다면 포인터와 카운트 값을 증가시킨다. 포인터가 배열의 사이즈를 넘어서면 while문을 빠져나온다.

탐색 - O(N^2)
여기까지 시간복잡도는 O(N^2) + O(N^2 log{N^2})


문제 POINT

문제를 보면 알겠지만 주어진 숫자의 범위가 매우 크다(10억).

계산을 하다보면 int 범위인 21억을 넘어갈지도 모른다. long 타입으로 변수를 선언해주는 것이 매우매우매우 중요하다.

profile
아침형 인간이 목표

0개의 댓글

관련 채용 정보