Skip to main content

2963. Good Partitions

Count the Number of Good Partitions

Straight to the point: this task is to split the input array into one or more contiguous subarrays,

with the constraint that no two subarrays share the same number. This is called a good partition.

The expected returned value is total number of good partitions.

Can't Join Two Groups? So...

Put it this way: for any number, find its first and last occurrence at indices ii and jj.

Both ii and jj must belong to the same group.

Which means we need to track the last occurrence index of each number.

With that in hand, we launch two pointers: scanIdx and boundaryIdx.

boundaryIdx represents the right boundary index of the current unfinished group GkG_k.

scanIdx is used to incrementally scan indices within GkG_k that haven't been checked yet.

What are we checking? Whether the last occurrence index jj of the number xx at scanIdx exceeds boundaryIdx.

If it does, we must update boundaryIdx = jj,

so that xx stays loyal and remains within the current unfinished group GkG_k, ensuring xx doesn't join two groups 😏

Conversely, if during this incremental scan, scanIdx ever becomes >> boundaryIdx,

it naturally means group GkG_k has fully formed and stopped growing, so we update the total count of completed groups accordingly.

What Does Group Count Represent?

Say there are kk groups in total, implying that there are k1k - 1 gaps between them.

Each gap faces two paths: keep it to separate two adjacent groups, or remove it to merge them into one group.

The answer follows: total good partitions = 2k12^{k - 1} ✌️

Since total number of partitions is on the order of O(2k)O(2^k), we use a modulo of 109+710^9 + 7 to prevent overflow.

Even computing the exponent 2k12^{k - 1} should use modular exponentiation for safety.

Hash Two Pointers Efficiency Hash map stores each number's last occurrence index. Space complexity O(n)O(n).

Modular exponentiation is implemented iteratively. Space complexity O(1)O(1).

Each index is scanned once by scanIdx, and once when building the hash map. Time complexity O(n)O(n).

Modular exponentiation runs in O(log(k1))O(\log(k - 1)), where kk is the total number of groups.

Since kk is at most nn, this is effectively O(logn)O(\log n).

Overall time and space complexity: both O(n)O(n).

#include <unordered_map>
#include <vector>
using namespace std;

int countGoodPartitions(vector<int>& nums) // LeetCode Q.2963.
{
unordered_map<int, int> numsLastIndices; // Each num's last idx.

for (int idx = 0; idx < nums.size(); idx++)
numsLastIndices[nums[idx]] = idx;

int windowsCount = 0; // Sliding window by two pointers.
int scanIdx = 0, boundaryIdx = 0;

while (boundaryIdx < nums.size()) {
int num = nums[scanIdx];
scanIdx++; // Already visited.

if (boundaryIdx < numsLastIndices[num]) { // Num extends current window.
boundaryIdx = numsLastIndices[num];
continue;
}

if (scanIdx > boundaryIdx) {
windowsCount++; // Current window is finalized.

boundaryIdx++; // Search for another window, if possible.
}
}

int goodPartitions = 1;

int power = windowsCount - 1, modulo = pow(10, 9) + 7;

while (power > 0) {
goodPartitions *= 2;
goodPartitions %= modulo; // Must prevent overflow.
power--;
}

return goodPartitions;
}