중간에서 만나기 테크닉 - 저장하고 검사하는 것이 아니라 검사를 먼저하고 저장하기
정수 배열에서 현재 위치 i번째 수 앞에 있는 수들 중 세 수의 합이 현재 위치 i번째 수와 같게 되는 경우,
그러한 수의 개수를 구하는 문제
"세 수"는 중복해서 선택해도 좋다
예를 들어 [-1,2,0]이면 0은 -1 -1 + 2 = 0이므로 이 조건에 만족하는 수이다
n이 최대 5000인데, 단순하게 짜면 $O(N^{4})$이라 매우 느리다
대충 이런 느낌
for i in range(n):
a = A[i]
for j in range(i):
for k in range(i):
for w in range(i):
b = A[j] + A[k] + A[w]
if b == a:
count += 1
break
다음으로는... 이미 이 문제를 해결하기 위해 비슷한 테크닉을 익힌적이 있다
https://deepdata.tistory.com/772
[Java]HashMap 응용력 키우기2 -두 수의 합과 세 수의 합 그리고 네 수의 합-
1. 문제 n개의 정수가 입력으로 주어지고, 이 중 서로 다른 위치에 있는 두 개의 수를 뽑아 더했을 때 k가 되는 가짓수를 구하는 프로그램을 작성해보세요. 2. 풀이 가장 쉬운 방법은 $O(N^{2})$으로
deepdata.tistory.com
만약에, 현재 위치 i 이전의 (j,k)에 대하여 A[j] + A[k]를 미리 hash나 배열에 저장해둔다면..
A[i] = A[j] + A[k] + A[w]이므로, A[i] - A[w] = A[j] + A[k]니까, A[i] - A[w]가 저장된 배열에 존재하는지 확인하면 된다
그래서 다음과 같이 모든 i = 0,1,2,..,n에 대하여...
j = 0,1,2,..i-1, k = 0,1,2,..,i-1에 대해 A[j] + A[k]를 미리 저장해두고
w = 0,1,2,...,i-1에 대하여 A[i] - A[w]가 존재하는지 체크하면.. $O(N^{3})$에 가능하다.
for i in range(n):
a = A[i]
for j in range(i):
for k in range(i):
P[A[j] + A[k]] = 1
for w in range(i):
if P[a - A[w]] == 1:
count += 1
break
n이 최대 5000이라 $O(N^{3})$도 느리다..
만약 배열 P에 A[k] + A[w]의 값이 저장되어 있다면, 위치 i 이전의 j에 대하여 A[i] - A[j]가 존재하는지만 체크해도 충분하다
왜냐하면 A[i] = A[j] + A[k] + A[w], j,k,w < i이므로 A[i] - A[j] = A[k] + A[w]이기 때문이다.
이것을 $O(N^{2})$에 할 수 있는 엄청난? 테크닉이 있는데...
저장을 먼저하고 검사를 하는게 아니라, 검사를 먼저 하고 저장을 하는 것이다
모든 i = 0,1,2...,n-1에 대하여
j = 0,1,2,..,i-1에 대해 P[A[i] - A[j]] = 1이면, A[i]는 조건을 만족하는 수이므로 counting (검사 먼저)
이 반복문이 끝나면 k = 0,1,2,..i에 대하여 P[A[i] + A[k]] = 1로 표시 (검사하고 저장하기)
이렇게 하면
i = 0에서 i = 0보다 앞에 있는 수는 없으므로 검사할 수는 없고 P에 A[0] + A[0]가 저장
i = 1에서 i = 1보다 앞에 있는 수는 A[0]이니 가능한 경우는 A[0] + A[0] + A[0]이고...
현재 P에는 A[0] + A[0]만 저장되어 있으며, A[1] - A[0]가 A[0] + A[0]와 같은지 보면 된다.
그리고 P에 A[1] + A[0], A[1] + A[1]이 저장
i = 2에서 i = 2보다 앞에 있는 수는 A[1], A[0]이니 가능한 경우는
A[0] + A[0] + A[0]
A[0] + A[0] + A[1]
A[0] + A[1] + A[1]
A[1] + A[1] + A[1]
i = 2보다 작은 j = 0,1에 대하여.. A[2] - A[0], A[2] - A[1]이
A[0]+A[0], A[0] + A[1], A[1] + A[1]중 하나와 같은지만 보면 된다.(검사 먼저)
실제로 P에는 이 3개의 수만 저장되어 있고
검사가 끝나면 A[0] + A[2], A[1] + A[2], A[2] + A[2]가 저장(검사하고 저장하기)
.....
이런식으로 하면 정말 놀랍게도 $O(N^{2})$에 가능해진다...
저장을 먼저하고 검사하는게 아니라..
일단 검사를 먼저하고 저장하기!
for i in range(n):
a = A[i]
for j in range(i):
if P[a - A[j]] == 1:
count += 1
break
for j in range(i+1):
P[a + A[j]] = 1
여기서 실수한게 있는데
배열 P의 범위는 A가 -10만에서 10만까지이고 두 수의 합이 저장되어야 하니까 20만이면 충분?
그러면 안돼
두 수의 합이 저장되어야 하니까 -20만에서 20만까지 가능하고,
배열의 인덱스는 음수가 없으니까 40만까지 저장할 수 있어야한다...
#현재 위치 i 이전의 세 수의 합이 현재 위치 i번째 수와 같은 경우를 세는 방법
n = int(input())
A = list(map(int,input().split()))
count = 0
#A가 -10만~10만이고, 두 수의 합이 저장된다
#-20만~20만을 저장할려면 크기가 40만이어야 한다
P = [0]*(4*10**5+1)
#A[i] = A[j] + A[k] + A[w], j,k,w < i인 i의 개수를 센다
#A[i] - A[j] = A[k] + A[w]이므로,
#k,w < i인 A[k] + A[w]의 값이 저장된 배열이 있다면,
#i = 0,1,2..,n-1, j = 0,1,2,..,i-1에 대해 A[i] - A[j]가 배열에 존재하는지 체크하면 된다
#이것을 O(N^2)에 하는 놀라운 테크닉
#검사를 먼저하고 저장하기
for i in range(n):
a = A[i]
#먼저 P에 A[i] - A[j]가 존재하면, counting
for j in range(i):
if P[a - A[j]] == 1:
count += 1
break
#그리고 j = 0,1,2,..,i에 대하여 A[i] + A[j]를 표시
for j in range(i+1):
P[a+A[j]] = 1
#이렇게 하면 다음에는 i+1에서 검사하게 될거고
#배열 P에는 i+1 이전의 j = 0,1,2,.,i, k= 0,1,2..,i에 대하여 A[j] + A[k]의 값이 저장되어 있다
print(count)
여기서 뭐 A[i] + A[j] = -20만이 될 수 있어서 a+ A[j]가 음수가 될 수 있는데.... 그래서 +20만을 해서 옮겨야 하는거 아니냐
그러는데 굳이 안해도 된다
A[i] + A[j]가 0~20만은 0~20만에 표시 될거고
A[i] + A[j]가 -20만~0은 20만~40만에 표시될거니까
'알고리즘 > 중간에서 만나기' 카테고리의 다른 글
홀수 위치 자리수들과 짝수 위치 자리수들의 곱이 서로 같은 n자리 자연수 찾는 마법같은 방법(중간에서 만나기) (0) | 2024.07.24 |
---|---|
[Java]HashMap 응용력 키우기2 -두 수의 합과 세 수의 합 그리고 네 수의 합- (0) | 2023.03.27 |