leetcode에서 cpp 사용 기초 - map 잘써보기
#include <vector>
#include <unordered_map>
#include <cmath>
using namespace std;
#define ll long long // 세미콜론안붙인다
class Solution {
public:
vector<ll> distance(vector<int>& nums) {
int n = nums.size();
// 각 숫자의 모든 인덱스와 총합을 미리 저장
unordered_map<int, vector<int>> groups;
unordered_map<int, ll> total_sum_map;
for (int i = 0; i < n; ++i) {
groups[nums[i]].push_back(i);
total_sum_map[nums[i]] += i;
}
// 각 숫자가 몇 번째로 등장했는지 지금까지의 인덱스 합은 얼마인지 추적
unordered_map<int, int> count_map;
unordered_map<int, ll> prefix_sum_map;
vector<ll> arr;
for (int i = 0; i < n; ++i) {
int val = nums[i];
ll curIdx = i;
ll total_sum = total_sum_map[val]; // 현재 값에 대한 인덱스 총합
int total_count = groups[val].size(); // 현재 값이 몇개인지?
ll prefix_sum = prefix_sum_map[val]; // 현재 값의 누적합
int count_so_far = count_map[val]; // 현재 인덱스 이전의 그 값 개수
ll left = (ll)count_so_far * curIdx - prefix_sum;
ll right_sum = total_sum - (prefix_sum + curIdx);
int right_count = total_count - 1 - count_so_far;
ll right = right_sum - (ll)right_count * curIdx;
arr.push_back(left + right);
prefix_sum_map[val] += curIdx;
count_map[val]++;
}
return arr;
}
};
처음엔 deque써서 회전큐같이 풀었다가 TLE나서 prefix로 선회했다.
먼저 각 숫자 nums[i]가 등장하는 모든 인덱스들의 총합 total_sum_map과 인덱스 목록 groups을 미리 저장한다.
그 다음 배열을 한 번 더 순회하며, 현재 인덱스 curIdx를 기준으로 왼쪽에 있는 같은 값들의 거리 합이랑 오른쪽 거리합을 나눠서 계산한다.
- 왼쪽 구간:
(현재까지 그 숫자가 나온 횟수) * 현재 인덱스 - (현재까지의 인덱스 누적합) - 오른쪽 구간:
(나머지 인덱스의 합) - (나머지 등장 횟수) * 현재 인덱스
이거 나도 처음에 뇌가 돌아버릴뻔했는데, 알고보니 분배법칙이었다.
[2, 3, 7]이 있고, 현재 idx가 7이라고 해보겠다.
왼쪽에 있는 2,3과의 거리를 구하는 법?
7-2 + 7-3 이다. 보이는 지?
(왼쪽 개수 * 내 위치) - (왼쪽 인덱스들의 합) 이렇게 된다.
C++ PS 필수 문법: deque, #define, typedef
알고리즘 문제를 풀 때 코딩 속도를 높여주고, 코드를 간결하게 작성할 수 있게 돕는 유용한 문법들입니다.
1. 매크로 정의 #define
단축어를 만들 때 사용합니다. 코드 길이를 획기적으로 줄일 수 있어 C++로 PS(Problem Solving)를 할 때 즐겨 사용됩니다. 끝에 세미콜론(;)을 붙이지 않는 것에 주의하세요.
#define ll long long
#define pb push_back
#define all(v) (v).begin(), (v).end()
// 사용 예시
ll big_number = 10000000000;
vector<int> v;
v.pb(1); // v.push_back(1)과 동일
2. 별칭 지정 typedef & using
기존 자료형에 새로운 이름을 부여하는 기능입니다. 구조체나 긴 템플릿 타입을 타이핑하기 편하게 만듭니다. 요즘 Modern C++(C++11 이후)에서는 좀 더 직관적인 using을 자주 사용합니다.
// typedef 방식
typedef long long ll;
typedef pair<int, int> pii;
// using 방식 (Modern C++ 권장)
using ll = long long;
using pii = pair<int, int>;
// 사용 예시
pii my_pair = {1, 2};
3. 양방향 큐 std::deque (Double Ended Queue)
std::deque는 양쪽 끝에서 데이터 삽입과 삭제가 모두 의 시간 복잡도로 빠르게 이루어지는 자료구조입니다. 게다가 배열(Vector)처럼 중간 요소에 인덱스([])로 접근하는 Random Access도 가능해 다재다능하게 쓰입니다. (Vector는 뒤에서만 삽입/삭제 효율이 좋습니다.)
#include <deque>
#include <iostream>
using namespace std;
int main() {
deque<int> dq;
// 양 끝 삽입
dq.push_back(2); // 뒤에 2 삽입: [2]
dq.push_back(3); // 뒤에 3 삽입: [2, 3]
dq.push_front(1); // 앞에 1 삽입: [1, 2, 3]
// 인덱스 접근
cout << "인덱스 1 참조: " << dq[1] << endl; // 출력: 2
// 양 끝 삭제
dq.pop_back(); // 뒤에서 꺼냄: [1, 2]
dq.pop_front(); // 앞에서 꺼냄: [2]
return 0;
}