문제 상황
You are given an array of transactions transactions where transactions[i] = [fromi, toi, amounti] indicates that the person with ID = fromi gave amounti $ to the person with ID = toi.
Return the minimum number of transactions required to settle the debt.
transactions 배열이 주어진다 각 요소는 a부터, b까지 c만큼 라는 뜻, a, b 는 ID고 c는 달러다.
debt를 내지 않기 위해서 필요한 가장 작은 transaction의 수를 구하여라
Input: transactions = [[0,1,10],[2,0,5]]
Output: 2
Explanation:
Person #0 gave person #1 $10.
Person #2 gave person #0 $5.
Two transactions are needed. One way to settle the debt is person #1 pays person #0 and #2 $5 each.
다음의 경우를 보면 1이 0한테 10달러를 빚졌고 0이 2한테 5달러를 빚졌다. 그래서 결과적으로 1이 0과 2한테 5달러씩 주는것.
Input: transactions = [[0,1,10],[1,0,1],[1,2,5],[2,0,5]]
Output: 1
Explanation:
Person #0 gave person #1 $10.
Person #1 gave person #0 $1.
Person #1 gave person #2 $5.
Person #2 gave person #0 $5.
Therefore, person #1 only need to give person #0 $4, and all debt is settled.
0이 1한테, 1이 0한테, 1이 2한테, 2가 0한테 각각 얼마씩 주고있음 그래서 퉁치기 식으로 1이 0한테 4달러 주는걸로 해결한다면 문제가 해결된다.
입력
- 1 <= transactions.length <= 8
- transactions[i].length == 3
- 0 <= fromi, toi < 12
- fromi != toi
- 1 <= amounti <= 100
출력
가능한 가장 짧은 길이의 정수 최대는 8 최소는 0
과정
그니까 이게 대신 내주는 방법이 여러가지가 있을건데
상황 구현의 문제가 있다.
- 합치는건 한개의 간선에 대해서, 여러가지가 경우가 있을 수 있다.
- 그러니 합칠간선이 있으면 합친다.
- 근데 모든상황을 카테고리화하는건 어떻게 ? 모델화하는것을 말하는거지 정확하게는
초기상황으로 가능한 모든 경우의 수 만 있다면 충분할까?
dp의 한 칸을 [13][13]이라고 할 수는 있겠다. x가 y에게 얼마를 주어야하는지를 나타내는 표로써,
그런데 문제는, 이게 몇번이 일어나야 하냐는것. 최대 몇번?
아마도 한번 할때마다 선은 줄어들을거다. 그건 맞음 근데 그렇다고해서 그게 8개로 퉁쳐지는건 아니다.
1 → 2 , 2 → 3을 합친거랑
2 → 4, 4 → 3을 합친거랑 어떻게 구분을 해야하는지도 모르고 x에서 y로, y에서 z로 갔다라는거 자체를 어떻게 표현할지
12 * 12 * 12 * 8
144 * 96 * 144
최대 줄어들 수 있는 횟수 자체는 8이라고 봐야하고, 모든 이동할 수 있는 경우의 수가, x(0 ~ 11) 에서 y(0 ~11) 을 거쳐 z(0 ~ 11)로 라는 횟수가 만들어지기 때문에 이거라면 겹치지 않고. 그럼 12을 돌면서 얘가 겹치는 모든애를 찾아도, 큰 문제는 없겠지 근데 줄어들지 않는경우의 수도 있는데.
하지만 중간에 줄어들지 않는 경우도 있었다. 한번은 안줄어들고 계속 돌아야줄어든다는 거잖아. 이럴때를 어떻게 하냐는거야
계속 뱅뱅돌수 있나? 적어도 뱅뱅 돌수는 없음.
3개 + ā 의 노드가 있음, 이들이
누가 더 크냐에따라서 다른데 , 만약에 가는 화살표가 클경우엔, 아예
모델 만들기가 상당히 빡센문제다, 언제 끝나는지를 알 수가 없으니까.
내가 실패한 알고리즘
class Solution {
int dp[100][1728][12][12];
public:
int minTransfers(vector<vector<int>>& transactions) {
auto transform = [](int a, int b, int c){
return 144 * a + 12 * b + c;
};
for (vector<int> transaction : transactions)
{
dp[transaction[0]][transaction[1]][0][0] = transaction[2];
}
int ans = 8;
for (int tim = 0; tim < 56; tim++)
{
for (int cas = 0; cas < 1728; cas++)
{
int cnt = 0;
for (int i = 0; i < 12; i ++)
{
for (int j = 0; j < 12; j++)
{
if (dp[tim][cas][i][j])
{
cnt++;
int remains = dp[tim][cas][i][j];
for (int k = 0; k < 12; k++)
{
if (dp[tim][cas][j][k])
{
if (dp[tim][cas][i][j] > dp[tim][cas][j][k])
{
// dp[tim + 1][transform(i, j, k)][i][j] -= dp[tim][cas][j][k];
remains -= dp[tim][cas][j][k];
dp[tim + 1][transform(i, j, k)][i][k] += dp[tim][cas][j][k];
}
else
{
dp[tim + 1][transform(i, j, k)][i][k] += dp[tim][cas][i][j];
dp[tim + 1][transform(i, j, k)][j][k] -= dp[tim][cas][i][j];
// dp[tim + 1][transform(i, j, k)][i][j] = 0;
remains = 0;
}
}
dp[tim + 1][transform(i, j, k)][i][j] = remains;
}
}
}
}
if (cnt)
{
ans = min(ans, cnt);
}
}
}
return ans;
}
};
난 결국 이문제를 풀지 못했다. DP는 각 작은 상황별로 이 상황이 영향을 받는 상황, 이 상황이 영향을 줄 상황을 분리할 수 있어야하는데 그렇게 하지 못한 것이다.
나는 DP의 한 단위를 [i][j] 12 * 12로 하려했고, 모든 경우의 수를 구분하기 위해 [i][j][k] 12 * 12 * 12에 회차 [100] 을 곱해서 구하려했다. 이미 초기화된 수준이라고 봐야겠지만 그게 떠올릴 수 있는 유일한 방법이었다.
- [i][j] 가 하나의 판이기 때문에.
- [i][j][k] * 100이 서로의 판을 구분짓는 역할이라고 생각했기 때문에
- 하지만 문제는 풀 수 없었고 , 어디가 문제였는지를 찾아야한다 이제
- 알고리즘과 나 사이에 어떤 벽이 있는것 같다.
혼자서는 세상을 구할 수 없다.
class Solution {
public:
int minTransfers(vector<vector<int>>& transactions) {
unordered_map<int,int> mp;
for(auto transaction: transactions){
mp[transaction[0]]-=transaction[2];
mp[transaction[1]]+=transaction[2];
}
// 맵을 통해서 +와 -를 계산해놓음.
vector<int> netTransfer;
for(auto [_,amount]: mp)if(amount)netTransfer.push_back(amount);
// 구조분해 할당을 통해 amount가 있다면 뭔지모를 배열에 추가
int n = netTransfer.size();
// n의 크기를 설정, amount가 0인걸 mp를 제외한 node들을 설정
vector<int> memo(1<<n, -1);
// 그 n을 기반으로 가능한 모든 경우의 수 벡터 생성
memo[0] = 0;
// 0은 0
// 마스크라는건 아마 모든 경우의수 1부터 255까지의 모든 경우의 수.
// 한개의 마스크가 의미하는 건 뭐지?
for(int mask = 1;mask<(1<<n);mask++){
int totalSum =0;
int groups = 0;
// group은, 한개의 마스크에서 특정노드가 켜져있음을 상징할 때
for(int i=0;i<n;i++){
if((mask&1<<i)!=0){
// 그 마스크 값이라는게 1이라면. 뭔가 현존하는 값이라면
//그 번호의 netTransfer을 더한다
totalSum += netTransfer[i];
// 그 노드의 netTransfer을 더하고
groups=max(groups, memo[mask^1<<i]);
// 같은 마스크중 이 1의자리수가 뒤집혀져있는 마스크와 , 나 자신간의 값중 큰것을 groups로 선택
}
}
// 현재 마스크의 값을 가능한 큰값을 설정된 groups에 에러시 1을 더하는 방식으로 내 마스크에 설정
memo[mask] = groups+ (totalSum==0?1:0);
}
// 리턴하는 값은 아마도, 모든 수가 1인,
// memo의 값들은 그룹 + (netTransfer을 더한 값 + i번쨰 요소값이 뒤집혀져 있는 경우의 값의 최대값을 의미한다)
// 그리고 totalSum이 0이야, 즉 한번도 더해진적이 없어, 그러면 1개가 있겠다하고 1로 설정해놓는다. 왜?
// 그리고는 모두가 켜져있었을때의 값을 리턴함.
return n-memo[(1<<n)-1];
// 모든 노드가 1이었을때의 memo 값을 n에서 뺀다.
}
};
groups는 mask&1<<i 가 참이라고 전해지면, max(groups, memo[mask^1<<i]) 마스크를 뒤집은채로 max값을 구한다. 당연히 저건 없었을때로 고정이다. mask가 1일경우엔 0과 부딪힌다. totalsum이 뭘 의미할까.
Our task is now to find maxium num of groups in array such that net of the group is zero.
For each group of size g we will have g-1 transactions. Min transactions would be: n - number_of_groups.
이 문제의 풀이에 따르면 돈 교환의 총합이 0이되는 그룹을 찾아내기 위해서이다.
각 mask의 요소의 뜻은?
mask는 살아남은 각각의 노드를 비트로 표현한 것을 뜻한다. 비트가 노드를 상징하는 것이다.
Groups는 뭘 의미하는가
1부터 최대 4047 까지 갈 수 있는 마스크가 있고 이건 그냥 각각의 비트들이 묶여있을때 그냥 왔다갔다는 다 무시하고 총합이 0인지만 보는 것이다. 만약에 서로 합쳐진 그 요소들의 총합이 0이라면 이들은 한개의 그룹이라는 뜻이 된다.
mask의 group은 totalSum이 0일때 처음 양의 정수가 될 것이다. 한 마스크의 그룹은, 그 마스크와 한개의 비트가 0으로 다른 마스크와 기본적으로 같은 group일 것이다. (아직 계산을 하지 않았을 때) group의 개수가 정해져있고 group의 일부가 평형을 이루었는데 새로운 노드가 들어오기만 한 상황이라면 (다른 평형을 찾지 않는 상황) group의 개수는 동일하다. 그런데 이 모든 노드의 합이 또 평형을 이룬다면? group의 값에 1을 더한다
왜 Group의 값에 1을 더하냐
일단 n명의 인원이 평행을 이루고있다면 이들이 교환하면 되는 횟수는 n - 1로 정해진 것 같다 여기에 대해서는 아직 난 확신이 없다. 그렇다는 생각이 들기는한다. 어쨌든 그럼 평형을 이루는 group이 생길수록 총 교환횟수는 n에서 1씩 준다고 봐도 될 것이다. 그렇기 때문에 이전마스크에서 group의 최대값을 가져오고 새 마스크가 평형을 이룰경우 또 1을 더하는 것으로 보인다.
이 문제는 이것에 답해야한다 왜 DP였나?
결국 이 문제가 왜 DP였는지 모른다면 난 이문제를 다시 풀 수 없다. 그걸 위해서 왜 이문제가 DP인지를 연구할 필요가 있다.
이 문제의 가장큰 패착은 내가 선후관계를 노드 a와 b의 연결로봤다는 것이다. 근데 이문제는 전혀 그래프 문제도 아니었다. 얼핏 a가 b한테 선을 뻗는 그래프 문제로 보이지만 실제는 그래프와 관련없고 오히려 간단하게 한 노드별 총합으로 치환해야하는 문제였다.
그리고 진정한 직렬화는 그룹화하는 마스크에 있었다. 노드 k개의 상태를 고려한 마스크는 k - 1개를 고려하고있는 각각의 이전 k 개의 노드 그룹과 그룹이 1개 많거나 같다. 1개 더 많은 기준은 총합이 0인가에 대한 기준, 그러면 직렬화와 영향그래프를 그릴 수 있다. 정말 어려운 문제였다. 언제 다시 풀지 정해야 할 것
'알고리즘연구소👨💻 > 리트코드' 카테고리의 다른 글
[Leetcode 3254번 문제] 3254. Find the Power of K-Size Subarrays I. (0) | 2024.11.18 |
---|---|
[Leetcode 2064번 문제] 2064. Minimized Maximum of Products Distributed to Any Store (0) | 2024.11.14 |
[Leetcode 2601번 문제] 2601. Prime Subtraction Operation (0) | 2024.11.11 |
[Leetcode 3133번 문제] 3133. Minimum Array End (1) | 2024.11.09 |
[Leetcode 1829번 문제] 1829. Maximum XOR for Each Query (0) | 2024.11.08 |