<p>You are given a <strong>0-indexed</strong> integer array <code>nums</code> consisting of <code>3 * n</code> elements.</p>

<p>You are allowed to remove any <strong>subsequence</strong> of elements of size <strong>exactly</strong> <code>n</code> from <code>nums</code>. The remaining <code>2 * n</code> elements will be divided into two <strong>equal</strong> parts:</p>

<ul>
	<li>The first <code>n</code> elements belonging to the first part and their sum is <code>sum<sub>first</sub></code>.</li>
	<li>The next <code>n</code> elements belonging to the second part and their sum is <code>sum<sub>second</sub></code>.</li>
</ul>

<p>The <strong>difference in sums</strong> of the two parts is denoted as <code>sum<sub>first</sub> - sum<sub>second</sub></code>.</p>

<ul>
	<li>For example, if <code>sum<sub>first</sub> = 3</code> and <code>sum<sub>second</sub> = 2</code>, their difference is <code>1</code>.</li>
	<li>Similarly, if <code>sum<sub>first</sub> = 2</code> and <code>sum<sub>second</sub> = 3</code>, their difference is <code>-1</code>.</li>
</ul>

<p>Return <em>the <strong>minimum difference</strong> possible between the sums of the two parts after the removal of </em><code>n</code><em> elements</em>.</p>

<p>&nbsp;</p>
<p><strong class="example">Example 1:</strong></p>

<pre>
<strong>Input:</strong> nums = [3,1,2]
<strong>Output:</strong> -1
<strong>Explanation:</strong> Here, nums has 3 elements, so n = 1. 
Thus we have to remove 1 element from nums and divide the array into two equal parts.
- If we remove nums[0] = 3, the array will be [1,2]. The difference in sums of the two parts will be 1 - 2 = -1.
- If we remove nums[1] = 1, the array will be [3,2]. The difference in sums of the two parts will be 3 - 2 = 1.
- If we remove nums[2] = 2, the array will be [3,1]. The difference in sums of the two parts will be 3 - 1 = 2.
The minimum difference between sums of the two parts is min(-1,1,2) = -1. 
</pre>

<p><strong class="example">Example 2:</strong></p>

<pre>
<strong>Input:</strong> nums = [7,9,5,8,1,3]
<strong>Output:</strong> 1
<strong>Explanation:</strong> Here n = 2. So we must remove 2 elements and divide the remaining array into two parts containing two elements each.
If we remove nums[2] = 5 and nums[3] = 8, the resultant array will be [7,9,1,3]. The difference in sums will be (7+9) - (1+3) = 12.
To obtain the minimum difference, we should remove nums[1] = 9 and nums[4] = 1. The resultant array becomes [7,5,8,3]. The difference in sums of the two parts is (7+5) - (8+3) = 1.
It can be shown that it is not possible to obtain a difference smaller than 1.
</pre>

<p>&nbsp;</p>
<p><strong>Constraints:</strong></p>

<ul>
	<li><code>nums.length == 3 * n</code></li>
	<li><code>1 &lt;= n &lt;= 10<sup>5</sup></code></li>
	<li><code>1 &lt;= nums[i] &lt;= 10<sup>5</sup></code></li>
</ul>

"""
Solution 1:
- identify differences of all possible subsequences
- return minimum
time: O(3nCn) <- 3n choose n -> (3n)!/(n!*(3n-n)!) -> (3n)!/(n!*(2n)!)
space: O(3nCn)

Solution 2:
- for each partition point i (must be between n-3n)
- find minimum sum before i and maximum sum after i
    - maxheap of size n
    - minheap of size n
    - pop out elements until heap is size n, get remaining sum of maxheap and minheap
    - difference is minheap sum - maxheap sum
- i++
time: O(3n*1.5nlog(1.5n)) = O(n^2logn)
space: O(3n)

look for optimizations in finding partition point and sum construction
Solution 3:
- maintain a prefix sum and suffix sum, but not actual prefix sum and suffix sum, but only of the greatest elements on left and right
- use heap to figure that out. keep a heap of only size n. for prefix sum, minimize. if the next element is <, remove the max in my heap and add next element instead
- for suffix sum, if next element is > heap elements, remove min heap element and push next large element
- for i in range(n, 3n), if prefix sum[i-1] - suffix sum[i], store that. else move on
time: O(3n + 2*3nlog(n)) = O(nlogn)
space: O(2n) = O(n)
"""

# Solution 3 implementation
import heapq
class Solution:
    def minimumDifference(self, nums: List[int]) -> int:
        n = len(nums) // 3

        # Populate prefix sum
        prefix_sum = [0 for _ in range(len(nums))]
        # Straight prefix sum, no removals considered
        prefix_sum[0] = nums[0]
        for i in range(1, n):
            prefix_sum[i] = prefix_sum[i-1] + nums[i]
        # The rest of prefix sum considers removal of elements so it needs a maxheap to store state
        max_heap = [-x for x in nums[:n]]
        heapq.heapify(max_heap)
        for i in range(n, len(nums)):
            # If the largest element on the heap is > current element
            # we want to use that current element instead to minimize
            # left prefix sum
            if -max_heap[0] > nums[i]:
                prev_largest = -heapq.heappushpop(max_heap, -nums[i])
                prefix_sum[i] = prefix_sum[i-1] - prev_largest + nums[i]
            # Remove the element, it will increase prefix sum
            else:
                prefix_sum[i] = prefix_sum[i-1]
        # print("prefix sum", prefix_sum)

        # Populate suffix sum
        suffix_sum = [0 for _ in range(3*n)]
        # Straight suffix sum, no removals considered
        suffix_sum[-1] = nums[-1]
        for i in range(3*n-2, 3*n-n-1, -1):
            suffix_sum[i] = suffix_sum[i+1] + nums[i]
        # The rest of suffix sum considers removal of elements
        min_heap = nums[2*n:]
        heapq.heapify(min_heap)
        for i in range(3*n-n-1, -1, -1):
            # If the smallest element on the min heap is < current element,
            # pop off the heap and push the new larger element. We're trying
            # to maximize the suffix sum
            if min_heap[0] < nums[i]:
                prev_smallest = heapq.heappushpop(min_heap, nums[i])
                suffix_sum[i] = suffix_sum[i+1] - prev_smallest + nums[i]
            # Remove the element, it will decrease suffix sum
            else:
                suffix_sum[i] = suffix_sum[i+1]
        # print("suffix sum", suffix_sum)

        # Find the min diff possible
        min_diff = float("inf")
        for i in range(n, 2*n+1):
            min_diff = min(min_diff, prefix_sum[i-1] - suffix_sum[i])
        return min_diff

48300