Facebook Pixel

3020. Find the Maximum Number of Elements in Subset

MediumArrayHash TableEnumeration
Leetcode Link

Problem Description

You are given an array of positive integers nums. Your task is to find a subset of nums that can be arranged in a specific palindromic pattern based on powers.

The subset must be arrangeable in a 0-indexed array following this pattern: [x, x², x⁴, ..., x^(k/2), x^k, x^(k/2), ..., x⁴, x², x]

Where:

  • x is the base number
  • k must be a non-negative power of 2 (i.e., k = 1, 2, 4, 8, 16, ...)
  • The pattern forms a palindrome with powers increasing to the middle and then decreasing symmetrically

For example:

  • [2, 4, 16, 4, 2] follows the pattern (which is [2¹, 2², 2⁴, 2², 2¹])
  • [3, 9, 3] follows the pattern (which is [3¹, 3², 3¹])
  • [2, 4, 8, 4, 2] does NOT follow the pattern because 8 = 2³, and the sequence [2¹, 2², 2³, 2², 2¹] has 3 in the middle, which is not a power of 2

The goal is to return the maximum number of elements that can be selected from nums to form such a valid subset.

Key points to understand:

  • You need to select elements from the given array (with their available frequencies)
  • The selected elements must be able to form the described palindromic power pattern
  • The pattern always has an odd length (2n + 1 elements for some n ≥ 0)
  • You want to maximize the size of such a subset
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

To understand how to solve this problem, let's first think about what makes a valid pattern. The pattern [x, x², x⁴, ..., x^(k/2), x^k, x^(k/2), ..., x⁴, x², x] tells us several important things:

  1. We start with some base number x and keep squaring it to build the first half of the sequence
  2. The sequence is symmetric (palindromic) around the middle element
  3. Each element except the middle one appears exactly twice in the pattern
  4. The middle element appears exactly once

This means for any base number x, we need:

  • At least 2 occurrences of x in our array
  • At least 2 occurrences of in our array
  • At least 2 occurrences of x⁴ in our array
  • And so on...
  • Until we reach some power where we either have only 1 occurrence (which becomes our middle element) or 0 occurrences (which means we stop before this)

The key insight is that we can greedily try each possible base number and see how long of a sequence we can build. For each base x:

  • We keep squaring: x → x² → x⁴ → x⁸ → ...
  • As long as we have at least 2 occurrences of the current power in our array, we can add 2 more elements to our pattern
  • When we find a power with only 1 occurrence, we can use it as the middle element and stop
  • When we find a power with 0 occurrences, we must stop and use the previous power as the middle element

There's a special case for x = 1: Since 1² = 1⁴ = ... = 1, all elements in the pattern would be 1. The valid patterns would be [1], [1, 1, 1], [1, 1, 1, 1, 1], etc. So for 1, we can use any odd number of 1's (up to the count available).

By counting the occurrences of each number in a hash table and trying each unique number as a potential base, we can find the maximum possible length of such a subset.

Solution Approach

The solution uses a hash table to count occurrences and then enumerates each possible base number to find the maximum subset length.

Step 1: Count Occurrences First, we create a Counter (hash table) to store the frequency of each element in nums:

cnt = Counter(nums)

Step 2: Handle the Special Case of 1 Since 1² = 1⁴ = ... = 1, the number 1 requires special handling. For 1, we can use any odd number of 1's:

ans = cnt[1] - (cnt[1] % 2 ^ 1)

This expression gives us the largest odd number ≤ cnt[1]. The XOR operation cnt[1] % 2 ^ 1 flips the last bit, ensuring we get an odd count if cnt[1] is even, or an even count minus 1 (which is odd) if cnt[1] is odd.

After handling 1, we remove it from further consideration:

del cnt[1]

Step 3: Enumerate Each Possible Base For each remaining number x in the hash table, we try to build the longest possible pattern:

for x in cnt:
    t = 0  # Length of pattern starting with base x
    while cnt[x] > 1:
        x = x * x  # Square x to get the next element
        t += 2     # Add 2 to length (one for each side of palindrome)

The loop continues as long as the current power of x appears at least twice in the array. Each iteration adds 2 to our pattern length (one for the left side, one for the right side of the palindrome).

Step 4: Handle the Middle Element After the loop, we check if the final x (the highest power we reached) exists in the array:

t += 1 if cnt[x] else -1
  • If cnt[x] == 1, we can use it as the middle element, so we add 1
  • If cnt[x] == 0, we went one power too far, so we subtract 1 (removing one element from each side that we counted in the last iteration)

Step 5: Update the Maximum We keep track of the maximum length found:

ans = max(ans, t)

The algorithm efficiently finds the maximum subset by trying each unique element as a potential base and greedily building the longest valid pattern possible with that base. The time complexity is O(n log max_value) where n is the number of unique elements, as we may square a number multiple times until it exceeds the maximum value or isn't found in the array.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through the solution with nums = [2, 4, 4, 2, 16, 8, 16].

Step 1: Count occurrences

cnt = {2: 2, 4: 2, 8: 1, 16: 2}

Step 2: Handle base 1 Since there are no 1's in the array, ans = 0.

Step 3: Try each number as a base

Base x = 2:

  • Start with t = 0
  • Check cnt[2] = 2 > 1
    • Square: x = 2 * 2 = 4
    • Add to length: t = 0 + 2 = 2
  • Check cnt[4] = 2 > 1
    • Square: x = 4 * 4 = 16
    • Add to length: t = 2 + 2 = 4
  • Check cnt[16] = 2 > 1
    • Square: x = 16 * 16 = 256
    • Add to length: t = 4 + 2 = 6
  • Check cnt[256] = 0, loop ends
  • Final check: cnt[256] = 0, so t = 6 + (-1) = 5
  • Pattern would be: [2, 4, 16, 4, 2] (length 5)

Base x = 4:

  • Start with t = 0
  • Check cnt[4] = 2 > 1
    • Square: x = 4 * 4 = 16
    • Add to length: t = 0 + 2 = 2
  • Check cnt[16] = 2 > 1
    • Square: x = 16 * 16 = 256
    • Add to length: t = 2 + 2 = 4
  • Check cnt[256] = 0, loop ends
  • Final check: cnt[256] = 0, so t = 4 + (-1) = 3
  • Pattern would be: [4, 16, 4] (length 3)

Base x = 8:

  • Start with t = 0
  • Check cnt[8] = 1 > 1 ✗, loop doesn't start
  • Final check: cnt[8] = 1, so t = 0 + 1 = 1
  • Pattern would be: [8] (length 1)

Base x = 16:

  • Start with t = 0
  • Check cnt[16] = 2 > 1
    • Square: x = 16 * 16 = 256
    • Add to length: t = 0 + 2 = 2
  • Check cnt[256] = 0, loop ends
  • Final check: cnt[256] = 0, so t = 2 + (-1) = 1
  • Pattern would be: [16] (length 1)

Step 4: Return maximum The maximum length is 5, achieved with base 2, forming the pattern [2, 4, 16, 4, 2].

Solution Implementation

1class Solution:
2    def maximumLength(self, nums: List[int]) -> int:
3        # Count frequency of each number in the array
4        frequency_map = Counter(nums)
5      
6        # Special case for 1: since 1^2 = 1, we can use all 1s
7        # But we need odd count for a valid subsequence (center element)
8        # If count of 1s is even, we subtract 1 to make it odd
9        max_length = frequency_map[1] - (frequency_map[1] % 2 ^ 1)
10      
11        # Remove 1 from consideration as we've already handled it
12        del frequency_map[1]
13      
14        # Check each unique number as potential base of geometric sequence
15        for base_num in frequency_map:
16            current_length = 0
17            current_num = base_num
18          
19            # Build sequence: [x, x, x^2, x^2, x^4, x^4, ...]
20            # We need at least 2 occurrences to continue the pattern
21            while frequency_map[current_num] > 1:
22                current_num = current_num * current_num  # Square the number
23                current_length += 2  # Add pair to sequence
24          
25            # If the final squared number exists, add it as center element
26            # Otherwise, we went too far, so subtract 1 from length
27            if frequency_map[current_num]:
28                current_length += 1  # Add center element
29            else:
30                current_length -= 1  # Remove last invalid pair
31          
32            # Update maximum length found so far
33            max_length = max(max_length, current_length)
34      
35        return max_length
36
1class Solution {
2    public int maximumLength(int[] nums) {
3        // Count frequency of each number in the array
4        Map<Long, Integer> frequencyMap = new HashMap<>();
5        for (int num : nums) {
6            frequencyMap.merge((long) num, 1, Integer::sum);
7        }
8      
9        // Handle special case for number 1 (since 1 * 1 = 1)
10        // For 1, we can use all occurrences but need an odd count for a valid pattern
11        Integer onesCount = frequencyMap.remove(1L);
12        int maxLength = (onesCount == null) ? 0 : onesCount - (onesCount % 2 ^ 1);
13      
14        // Process each unique number to find the longest valid pattern
15        for (long baseNum : frequencyMap.keySet()) {
16            int currentLength = 0;
17            long currentNum = baseNum;
18          
19            // Build pattern: [x, x, x², x², x⁴, x⁴, ...]
20            // Keep squaring while we have at least 2 occurrences
21            while (frequencyMap.getOrDefault(currentNum, 0) > 1) {
22                currentNum = currentNum * currentNum;
23                currentLength += 2;  // Add pair of elements
24            }
25          
26            // Add the final element if it exists (the peak of the pattern)
27            // If it doesn't exist, subtract 1 to maintain valid pattern
28            currentLength += frequencyMap.getOrDefault(currentNum, -1);
29          
30            // Update maximum length found so far
31            maxLength = Math.max(maxLength, currentLength);
32        }
33      
34        return maxLength;
35    }
36}
37
1class Solution {
2public:
3    int maximumLength(vector<int>& nums) {
4        // Count frequency of each number in the array
5        unordered_map<long long, int> frequencyMap;
6        for (int number : nums) {
7            ++frequencyMap[number];
8        }
9      
10        // Special handling for 1: can form a sequence of all 1s
11        // If count is odd, use all; if even, use count-1 to maintain odd length
12        int maxLength = frequencyMap[1] - (frequencyMap[1] % 2 ^ 1);
13      
14        // Remove 1 from consideration for the main algorithm
15        frequencyMap.erase(1);
16      
17        // Check each unique number as a potential base of geometric sequence
18        for (auto [baseValue, frequency] : frequencyMap) {
19            int currentLength = 0;
20            long long currentValue = baseValue;
21          
22            // Build sequence: base, base^2, base^4, base^8, ...
23            // Continue while current value exists with frequency >= 2
24            while (frequencyMap.count(currentValue) && frequencyMap[currentValue] > 1) {
25                currentValue = currentValue * currentValue;  // Square the value
26                currentLength += 2;  // Add 2 for symmetric pattern (e.g., x, x^2, x)
27            }
28          
29            // Add final element if it exists (peak of sequence), otherwise subtract 1
30            // This handles the middle element of palindrome-like sequence
31            currentLength += frequencyMap.count(currentValue) ? 1 : -1;
32          
33            // Update maximum length found so far
34            maxLength = max(maxLength, currentLength);
35        }
36      
37        return maxLength;
38    }
39};
40
1/**
2 * Finds the maximum length of a valid subset where elements form a geometric sequence
3 * with common ratio being the square of the previous element
4 * @param nums - Array of positive integers
5 * @returns Maximum length of valid subset
6 */
7function maximumLength(nums: number[]): number {
8    // Count frequency of each number in the array
9    const frequencyMap: Map<number, number> = new Map();
10    for (const num of nums) {
11        frequencyMap.set(num, (frequencyMap.get(num) ?? 0) + 1);
12    }
13  
14    // Handle special case for 1s (1^2 = 1, so they form their own sequence)
15    // For odd count of 1s, use all; for even count, use count-1 to ensure odd length
16    let maxLength = frequencyMap.has(1) 
17        ? frequencyMap.get(1)! - (frequencyMap.get(1)! % 2 ^ 1) 
18        : 0;
19    frequencyMap.delete(1);
20  
21    // Check each unique number as potential start of a geometric sequence
22    for (const [startNum, _] of frequencyMap) {
23        let currentNum = startNum;
24        let sequenceLength = 0;
25      
26        // Build sequence by squaring: x, x^2, x^4, x^8, ...
27        // Each intermediate element needs to appear at least twice (to continue and return)
28        while (frequencyMap.has(currentNum) && frequencyMap.get(currentNum)! > 1) {
29            currentNum = currentNum * currentNum;
30            sequenceLength += 2; // Add 2 for the pair (going up and coming back)
31        }
32      
33        // Add 1 if the final element exists (peak of sequence), otherwise subtract 1
34        sequenceLength += frequencyMap.has(currentNum) ? 1 : -1;
35      
36        // Update maximum length found so far
37        maxLength = Math.max(maxLength, sequenceLength);
38    }
39  
40    return maxLength;
41}
42

Time and Space Complexity

Time Complexity: O(n × log log M)

The algorithm first counts the frequency of all elements in O(n) time using Counter(nums). Then it iterates through each unique element in the counter. For each unique element x, the while loop continues as long as cnt[x] > 1, where in each iteration x is squared (x = x * x).

The key insight is that x grows exponentially fast through repeated squaring: x → x² → x⁴ → x⁸ → x¹⁶ ..., which means x becomes x^(2^k) after k iterations. The loop terminates when x^(2^k) ≥ M (where M is the maximum value in the array), which gives us 2^k ≥ log M, so k ≥ log log M. Therefore, for each unique element, the while loop runs at most O(log log M) times. Since there are at most n unique elements, the total time complexity is O(n × log log M).

Space Complexity: O(n)

The space is dominated by the Counter object cnt, which stores the frequency of each unique element in nums. In the worst case where all elements are unique, the counter would store n key-value pairs, resulting in O(n) space complexity. The remaining variables (ans, x, t) use constant space.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Infinite Loop with Base = 1

The most critical pitfall is attempting to build a power sequence starting with base 1. Since 1² = 1, squaring 1 repeatedly will always yield 1, causing an infinite loop.

Problem Code:

for base_num in frequency_map:
    current_num = base_num
    while frequency_map[current_num] > 1:
        current_num = current_num * current_num  # If base_num = 1, infinite loop!

Solution: Handle 1 as a special case before the main loop and remove it from the frequency map:

max_length = frequency_map[1] - (frequency_map[1] % 2 ^ 1)
del frequency_map[1]  # Critical: Remove 1 to prevent infinite loop

2. Integer Overflow When Squaring Large Numbers

Repeatedly squaring numbers can quickly lead to very large values that may cause overflow or performance issues.

Problem Scenario: If base_num = 10^5 and we square it multiple times, we get astronomical numbers that waste computation time checking if they exist in the frequency map.

Solution: Add an upper bound check to exit early when the squared value exceeds the maximum possible value in the input:

max_value = max(frequency_map.keys())
for base_num in frequency_map:
    current_num = base_num
    while current_num <= max_value and frequency_map[current_num] > 1:
        current_num = current_num * current_num

3. Misunderstanding the Pattern Requirements

A common mistake is thinking any palindromic sequence of powers works, but the exponents must follow the pattern [1, 2, 4, 8, ..., 2^k, ..., 8, 4, 2, 1].

Incorrect Understanding: Thinking [x, x², x³, x², x] is valid (it's not, because 3 is not a power of 2).

Solution: The code naturally enforces this by repeatedly squaring (which creates powers of 2 exponents). Each squaring doubles the exponent: x → x² → x⁴ → x⁸, ensuring all exponents are powers of 2.

4. Not Handling the Case When Building Goes Too Far

When building the sequence, if we find that the next squared value doesn't exist, we've counted one pair too many in our last iteration.

Problem Code:

while frequency_map[current_num] > 1:
    current_num = current_num * current_num
    current_length += 2
# Forgetting to handle the case where current_num doesn't exist

Solution: Always check if the final current_num exists and adjust accordingly:

if frequency_map[current_num]:
    current_length += 1  # Valid center element
else:
    current_length -= 1  # We went too far, remove last pair

5. Modifying Dictionary During Iteration

While not present in this specific solution, a common mistake would be trying to modify the frequency map while iterating over it.

Problem Code:

for base_num in frequency_map:
    # ... build sequence ...
    # DON'T do this:
    frequency_map[base_num] = 0  # Modifying during iteration can cause issues

Solution: The current approach correctly avoids this by only reading from the frequency map during iteration, not modifying it (except for the special case of 1, which is handled before iteration).

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

You are given an array of intervals where intervals[i] = [start_i, end_i] represent the start and end of the ith interval. You need to merge all overlapping intervals and return an array of the non-overlapping intervals that cover all the intervals in the input.


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More