문제를 나름대로 간소화하기 위해서 고민을 해 보았다.
이전에 두 수의 합이 최소가 되도록 하는 문제를 풀었던 기억을 기초로 하여
A,B,C,D의 배열에 수들이 들어가 있다면 A+B (Ai + Bj), C+D (Ci + Dj)으로 배열을 두개로 만들어,
A+B 배열과 C+D 배열의 인자들 중 합이 0이 되는 인자 i,j 쌍의 갯수를 구하는 문제로 생각하면 어떨까
시간이 12초인 만큼, 4000x4000 배열 두개를 활용하여 2x4000x4000만큼의 연산은 감당가능하기 떄문에 좋은 접근이라고 생각했다.
그 때와 일차적으로 다른 점은, 중복되는 수가 여러개가 있을 수 있다는 점이였는데,
삽입시에 자동으로 정렬이 되면서 중복된 키를 허용하는 multiset으로 컨테이너를 쓰기로 계획했다.
A+B, C+D 배열을 만드는 것은 너무 간단하고, 이제 이 정렬된 두 컨테이너에서 쌍을 찾는 알고리즘을 고민해보기로 했다.
A+B의 iterator을 aa, C+D의 iterator을 bb라 하면
1. *aa == *(aa+1)이거나, *bb == *(bb-1)이면 aa+1 혹은 bb-1 후 다시 비교를 시작한다
이런 식으로 시도해봤으나, 중복 경우 로직이 좀 쓸대없이 복잡하기도 하고,
set의 경우에는 iterator을 쓰더라도 불편하게 접근되는 감이 있어서 map을 쓰기로 했다
데이터가 큰 만큼 hash화 된 index로 속도가 빠르게 접근 가능한 map이 좋을거라고 생각했는데, 공간 초과가 발생했다.
unordered_map을 사용해도 마찬가지였고, (공간면에서는 차이가 크게 없겠지만 시간초과도 발생했었기에)
기존에 사용하지 않았던 std::unique를 통한 중복 아이템 제거 후 해당 index에 몇개의 중복되는 아이템이 있는지 count 벡터를 별도로 구성하여 구현하였다
void erase_unique(vector<int>& vec) {
auto it = std::unique(vec.begin(), vec.end());
vec.erase(it, vec.end());
}
void count_item(const vector<int>& arr, vector<int>& countarr) {
int idx = 0, cnt = 0, val = 0;
auto iter = arr.cbegin();
val = *iter;
cnt = 1;
iter ++;
for (;iter != arr.cend();iter++) {
if (val != *iter) {
countarr[idx++] = cnt;
val = *iter;
cnt = 1;
} else
cnt += 1;
}
countarr[idx] = cnt;
}
~ 첫 번째, 두 번째 아이템의 합을 AA 배열에 저장, BB도 마찬가지로 저장 ~
sort(AA.begin(), AA.end());
sort(BB.begin(), BB.end());
AAcount.resize(AA.size());
BBcount.resize(BB.size());
count_item(AA, AAcount);
count_item(BB, BBcount);
erase_unique(AA);
erase_unique(BB);
~ 로직 ~
구현의 편의성을 위해 문제를 조금 더 다르게 해석해 보았다.
Ai+Bj = 0 -> -Ai = Bj 이기 떄문에,
A,B,C,D의 배열에 수들이 들어가 있다면 -(A+B) -(Ai + Bj), C+D (Ci + Dj)으로 배열을 두개로 만들어,
A+B 배열과 C+D 배열의 인자들 중 값이 같은 인자 i,j 쌍의 갯수를 구하는 문제로 생각하면 어떨까
라고 생각하면 문제가 좀더 간편해지고, 두 비교하는 루틴도 아주 간단해지는 효과가 발생했다.
AA 배열의 index를 쓰면 iterator 대신 변수 하나씩만 써도 되겠지만, 기존 코드에 간단히 변형을 하여 구현을 했다.
unsigned long long cnt = 0;
for (auto aa = AA.begin(), bb = BB.begin(), aacnt = AAcount.begin(), bbcnt = BBcount.begin();;) {
if ((*aa) == (*bb)) {
cnt += (unsigned long long)(*aacnt) * (*bbcnt);
}
if (aa + 1 != AA.end() && bb + 1 != BB.end()) {
if(*(aa+1) > *(bb+1)) {
bb++;
bbcnt++;
}
else {
aa++;
aacnt++;
}
} else if(aa + 1 != AA.end()) {
aa++;
aacnt++;
}
else if(bb + 1 != BB.end()) {
bb++;
bbcnt++;
}
else
break;
}
좀더 개선할 여지가 있을수도 있겠지만, 골드2 문제에 이미 심력을 많이 쏟았기에, 여기서 만족하기로 했다.
제출을 최소화 하는게 옳은 방향성이라고 생각해서 제출을 spamming하지 않으려고 하는 편이지만, 이번에는 그 원칙을 지키지 못했다.
쉬운 문제라고 생각했으나, 생각보다는 편리한 컨테이너들에 대해 공간복잡도에 대해 많이 생각해볼 계기가 되는 문제였다. 공간복잡도와 시간복잡도면에서 문제가 발생하다보니, 내가 선택한 알고리즘 방식에 대해 맞나 싶은 의문도 많이 들었고, 사실 문제 자체는 워낙 쉬운 문제다 보니 여기서 끝났지만, 알고리즘 자체의 난이도와 공간복잡도도 따지는 문제가 주어진다면 난이도가 곱절로 어려워질 것이라고 느꼈다.