👨💻 개인 공부 기록용 블로그 입니다.
💡 틀린 내용이나 오타는 댓글, 메일로 제보해주시면 감사하겠습니다!! (__)
링크
https://leetcode.com/problems/k-inverse-pairs-array/?envType=daily-question&envId=2024-01-27
문제
문제 설명 및 요점
Inverse Pair는 인덱스는 증가하지만 값은 감소하는 형태의 숫자 쌍을 뜻한다.- 1~ 까지의 숫자가 있을 때, 의
Inverse Pair를 갖는 숫자 배치 경우의 수를 찾아야 한다. - 정답이 클 수 있으니 로 나눈 나머지를 반환한다.
풀이
-
조합론에서의 오일러 수 개념을 확장한 문제. 오일러 수는 연속되는 인덱스에서 감소하는 쌍에 해당하는 순열이고, 문제에서는 인덱스의 연속 조건이 빠져있다.
-
그래도 오일러 수 보다는 점화식 찾기가 쉬운 편이지만, 그럼에도 너무 어렵다…
-
각 에 대해, 는 까지 존재할 수 있다.
Inverse Pair가 가장 많이 존재할 수 있는 경우를 생각해보자.- 완벽하게 내림차순으로 정렬 되었을 때:
[n, n-1, ..., 2, 1] - 위 경우에서
Inverse Pair는 개의 숫자에서 2개의 숫자를 순서 고려 없이 뽑은 경우의 수와 같다.
-
또한, 각 에 대해 모든 를 합치면 이 나온다. ( 개의 숫자를 나열하는 경우의 수)
-
에서 출발해보자.
[1,2]( )와[2,1]( ) 두 경우가 존재한다. -
일 때, 이다. 일 때는 항상 1이므로, 나머지 경우에 대해 생각해보자.
- 인 경우,
[1,2]인 경우에서 3을 중간에 끼워 넣거나,[2,1]인 경우에서 3을 맨 뒤에 끼워 넣는 경우 밖에 없다. - 인 경우,
[1,2]인 경우에서 3을 맨 앞에 끼워 넣거나,[2,1]인 경우에서 3을 중간에 끼워 넣는 경우 밖에 없다. - 인 경우,
[2,1]인 경우에서 3을 맨 앞에 끼워 넣는 경우 밖에 없다.
- 인 경우,
-
-> 을 만드는 원리가 보이는가? 인 경우의 수에서 3을 어떻게 끼우느냐에 달려있다.
- 표기를 간소화하기 위해 형태로 표기하겠다.
- 은 에 관여한다.
- 은 에 관여한다.
- 시각화 하면 이와 같다.
-
-> 도 만들어보자. 인 경우의 수에서 4를 어떻게 끼울지 생각해보면 아래와 같은 결과를 얻을 수 있다.
- 표기를 간소화하기 위해 형태로 표기하겠다.
- 은 에 관여한다.
- 은 에 관여한다.
- 은 에 관여한다.
- 은 에 관여한다.
- 시각화 하면 이와 같다.
-
수열이 만들어지는 원리는 이게 다인데, 이걸 그대로 구현하면 시간초과가 발생한다. 개의 숫자에 대해 개의 숫자를 다음 수열에 번 누적시켜줘야 하므로, 라서 해결할 수 없다.
-
따라서 계산을 최적화해야 한다. 다시 위 시각화를 끌고 와서 생각해보자.
- 표기를 간소화하기 위해 형태로 표기하겠다.
- 가 어떻게 만들어지는지 자세히 살펴보자.
- 는 빨간 직선 하나, 초록 직선 하나, 파란 직선 하나로 구성되어 있다.
- 은 빨간 직선 하나, 초록 직선 하나로 구성되어 있다.
- 따라서 이다.
- 마치 조합의 성질 처럼, 를 통해 계산을 최적화 할 수 있다.
- 다만, 이는 인 경우에만 성립한다. 나머지는 대칭성을 이용해 계산해야 한다.
- 에 집중해보자.
- 은 빨강, 초록, 파랑, 노랑으로 구성되어 있다.
- 는 초록, 파랑, 노랑으로 구성되어 있다.
- 따라서 이다.
- 나머지 경우도 확장해보면, 인 경우에 대해 이 성립함을 알 수 있다.
-
정리하면, 최종 점화식은 다음과 같다.
- 표기를 간소화하기 위해 형태로 표기하겠다. 에서
- 인 경우에 대해,
- 인 경우에 대해,
- 점화식을 그대로 재귀로 구현해도 정답을 받을 수 있다.
- 생 재귀로는 당연히 불가능하고, 이전 값을 저장해두어야 한다. 파이썬의 경우
@cache데코레이터를 사용하면 쉽게 구현할 수 있다.
-
좀 더 최적화를 시켜보자.
- 일 때 부터 시작한다. 은 1이다.
- 칸 짜리 배열을 하나 준비하고, 빈 배열과 이전 값을 누적할 변수를 하나 둔다.
- 누적값을 빈 배열에 계속 추가해 나가다가, 이 되는 시점부터 누적값에서
dp[k - n - 1]을 뺀다. - 를 전부 계산할 필요 없이, 까지만 계산한 후
dp배열과 교체해준다. - 코드로 옮기면 아래와 같다. 과의 나머지 연산을 잊지 말자.
코드
class Solution:
def kInversePairs(self, n: int, k: int) -> int:
MOD = 10 ** 9 + 7
dp = [0] * (k + 1)
dp[0] = 1
for i in range(n):
temp, number = [], 0
for j in range(k + 1):
number += dp[j]
if j-i >= 1: number -= dp[j-i-1]
number %= MOD
temp.append(number)
dp = temp
return dp[k]