Minimum Component Subset — Problem Statement & Solution Guide
Quick Answer & Algorithm Key Takeaway
Master coding challenges related to Two Pointers and solve the Minimum Component Subset problem optimally.
O(n)O(k)Problem Description
Given an integer array nums and a set required of distinct integers, find the length of the shortest contiguous subarray of nums that contains every element of required at least once. If no such subarray exists, return -1. The input consists of the array nums and the set required; the output is a single integer representing the minimal length or -1 when impossible.
DSA Pattern Breakdown
DSA Pattern Breakdown
"Minimum Component Subset"
WHY DOES IT MATTER?
Sliding‑window is essential for any problem that asks for the smallest/largest contiguous segment meeting a condition, because it converts an exponential search into a linear scan by exploiting the monotonic nature of the condition.
OPTIMIZATION CHALLENGE
The key insight is that you only need to track counts of required elements, not the entire window content, allowing O(1) updates per pointer move and preventing repeated full‑window scans.
REAL-WORLD CONNECTION
Think of a streaming log processor that must detect the shortest time window containing all critical error codes; the two‑pointer technique mirrors how the processor slides a time cursor forward and backward to pinpoint the minimal interval.
During an interview, first write the frequency map for required elements, then implement the expand‑contract loop; keep a variable for how many distinct required values are satisfied to avoid scanning the whole map each time.
COMPLEXITY AT A GLANCE
O(n)O(k)Core Theory — Why This Approach?
The problem is a classic sliding‑window (two‑pointer) scenario where we need to maintain a dynamic interval of the array that satisfies a coverage constraint – containing every element from the required set at least once. A naive solution would enumerate all O(n²) subarrays and check each for the presence of required elements, leading to prohibitive runtime for large n. The optimal paradigm treats the left and right pointers as the current window boundaries and expands the right pointer until the window becomes valid, then contracts the left pointer to shrink it while preserving validity, updating the best length along the way. This approach leverages a frequency map of required elements, allowing O(1) updates per movement and guaranteeing each element is processed at most twice, yielding linear time.
The underlying theory rests on the monotonicity of the window: once a window satisfies the requirement, any extension to the right will also satisfy it, and any contraction from the left may break it. By exploiting this property we can avoid re‑examining previously processed sections, which is the essence of the two‑pointer technique. The algorithm thus transforms a combinatorial search into a deterministic scan, achieving O(n) time and O(k) auxiliary space, where k is the size of the required set.
Interview Questions on This Problem
Q1How would you modify the solution if the required set could contain duplicate values, i.e., you need each value a specific number of times?
Maintain a target count map for each required value and a current count map in the window. The window is valid only when every value's current count meets or exceeds its target count. The rest of the sliding‑window logic stays the same.
Q2Can you solve the problem in a single pass without using an explicit hash map for frequencies?
If the range of possible values is small and dense (e.g., 0…M), you can use a fixed‑size integer array as a frequency counter, which acts like a hash map but with O(1) access and no hashing overhead.
Q3What is the time‑space trade‑off if you pre‑process the array to store the next occurrence index of each required element?
Pre‑processing with a map of positions allows a binary‑search‑based solution that runs in O(n log k) time and O(n) space, which is slower than the linear two‑pointer method but can be useful when multiple queries on the same array are required.
Examples
Input
nums = [4,2,1,5,2,3,1,2], required = [1,2,3]
Output
3
Explanation: The subarray from index 4 to 6 (0‑based) is [2,3,1] and includes 1, 2, 3. No shorter window can contain all three values, so the answer is 3.
Input
nums = [7,5,9,1,2,8,6], required = [3,4]
Output
-1
Explanation: Neither 3 nor 4 appears in nums, therefore no contiguous segment can satisfy the requirement; the function returns -1.
Input
nums = [10,12,5,6,12,5,7,5,6], required = [5,6,12]
Output
3
Explanation: The segment from index 1 to 3 is [12,5,6] and already contains 5, 6, 12. Any window shorter than length 3 would miss at least one required value, so the minimal length is 3.
Constraints
- 1 <= nums.length <= 100000
- 1 <= required.size <= 100000
- -1000000000 <= nums[i] <= 1000000000
- All values in required are distinct
Optimal Approach & Strategy
Use two pointers with a hash map to maintain counts of required elements, expanding right until the window is valid then contracting left to minimize it – O(n) time.
Brute Force Approach
Check every possible subarray, count required elements inside each, and keep the smallest length that satisfies the condition – O(n²) time.
Code Solutions
function minComponentSubset(nums, required) {
const need = new Map();
for (const x of required) need.set(x, 0);
const requiredCount = need.size;
const window = new Map();
let have = 0, left = 0, best = Infinity;
for (let right = 0; right < nums.length; ++right) {
const val = nums[right];
if (need.has(val)) {
window.set(val, (window.get(val) || 0) + 1);
if (window.get(val) === 1) have++;
}
while (have === requiredCount && left <= right) {
best = Math.min(best, right - left + 1);
const lval = nums[left];
if (need.has(lval)) {
window.set(lval, window.get(lval) - 1);
if (window.get(lval) === 0) have--;
}
++left;
}
}
return best === Infinity ? -1 : best;
}
// Driver (same format as template)
const fs = require('fs');
const data = fs.readFileSync(0,'utf8').trim().split(/\s+/).map(Number);
let idx = 0;
const n = data[idx++];
const nums = data.slice(idx, idx+n); idx+=n;
const m = data[idx++];
const required = data.slice(idx, idx+m);
console.log(minComponentSubset(nums, required).toString());#include <bits/stdc++.h>
using namespace std;
int minComponentSubset(const vector<int>& nums, const vector<int>& required) {
unordered_map<int,int> need;
for(int x: required) need[x] = 0; // we only care about presence
int requiredCount = need.size();
unordered_map<int,int> window;
int have = 0, left = 0, best = INT_MAX;
for(int right = 0; right < (int)nums.size(); ++right) {
int val = nums[right];
if(need.find(val) != need.end()) {
window[val]++;
if(window[val] == 1) ++have; // first occurrence in window
}
while(have == requiredCount && left <= right) {
best = min(best, right - left + 1);
int lval = nums[left];
if(need.find(lval) != need.end()) {
window[lval]--;
if(window[lval] == 0) --have;
}
++left;
}
}
return best == INT_MAX ? -1 : best;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n; if(!(cin>>n)) return 0;
vector<int> nums(n);
for(int &x: nums) cin>>x;
int m; cin>>m;
vector<int> required(m);
for(int &x: required) cin>>x;
cout<<minComponentSubset(nums, required)<<"\n";
return 0;
}import java.io.*;
import java.util.*;
public class Main {
public static int minComponentSubset(int[] nums, int[] required) {
Set<Integer> need = new HashSet<>();
for (int x : required) need.add(x);
int requiredCount = need.size();
Map<Integer, Integer> window = new HashMap<>();
int have = 0, left = 0, best = Integer.MAX_VALUE;
for (int right = 0; right < nums.length; ++right) {
int val = nums[right];
if (need.contains(val)) {
window.put(val, window.getOrDefault(val, 0) + 1);
if (window.get(val) == 1) have++;
}
while (have == requiredCount && left <= right) {
best = Math.min(best, right - left + 1);
int lval = nums[left];
if (need.contains(lval)) {
window.put(lval, window.get(lval) - 1);
if (window.get(lval) == 0) have--;
}
left++;
}
}
return best == Integer.MAX_VALUE ? -1 : best;
}
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
int n = Integer.parseInt(st.nextToken());
int[] nums = new int[n];
st = new StringTokenizer(br.readLine());
for (int i = 0; i < n; i++) nums[i] = Integer.parseInt(st.nextToken());
st = new StringTokenizer(br.readLine());
int m = Integer.parseInt(st.nextToken());
int[] required = new int[m];
st = new StringTokenizer(br.readLine());
for (int i = 0; i < m; i++) required[i] = Integer.parseInt(st.nextToken());
System.out.println(minComponentSubset(nums, required));
}
}import sys
from collections import defaultdict
def min_component_subset(nums, required):
need = set(required)
required_count = len(need)
window_counts = defaultdict(int)
have = 0
left = 0
best = float('inf')
for right, val in enumerate(nums):
if val in need:
window_counts[val] += 1
if window_counts[val] == 1:
have += 1
while have == required_count and left <= right:
best = min(best, right - left + 1)
lval = nums[left]
if lval in need:
window_counts[lval] -= 1
if window_counts[lval] == 0:
have -= 1
left += 1
return -1 if best == float('inf') else best
if __name__ == "__main__":
data = list(map(int, sys.stdin.read().strip().split()))
if not data:
sys.exit(0)
it = iter(data)
n = next(it)
nums = [next(it) for _ in range(n)]
m = next(it)
required = [next(it) for _ in range(m)]
print(min_component_subset(nums, required))function minComponentSubset(nums, required) {
const need = new Map();
for (const x of required) need.set(x, 0);
const requiredCount = need.size;
const window = new Map();
let have = 0, left = 0, best = Infinity;
for (let right = 0; right < nums.length; ++right) {
const val = nums[right];
if (need.has(val)) {
window.set(val, (window.get(val) || 0) + 1);
if (window.get(val) === 1) have++;
}
while (have === requiredCount && left <= right) {
best = Math.min(best, right - left + 1);
const lval = nums[left];
if (need.has(lval)) {
window.set(lval, window.get(lval) - 1);
if (window.get(lval) === 0) have--;
}
++left;
}
}
return best === Infinity ? -1 : best;
}
// Driver (same format as template)
const fs = require('fs');
const data = fs.readFileSync(0,'utf8').trim().split(/\s+/).map(Number);
let idx = 0;
const n = data[idx++];
const nums = data.slice(idx, idx+n); idx+=n;
const m = data[idx++];
const required = data.slice(idx, idx+m);
console.log(minComponentSubset(nums, required).toString());Asked in Top Tech Interviews
Solve in Interative Editor
Ready to test your code? Open our built-in compiler, run custom test suites, and see detailed complexity analysis reports instantly.