LeetCode 3020 - Find the Maximum Number of Elements in Subset
The problem gives us an array of positive integers and asks us to choose the largest possible subset that can be rearranged into a very specific symmetric structure. The required structure looks like this: where is a power of two.
Difficulty: 🟡 Medium
Topics: Array, Hash Table, Enumeration
Solution
Problem Understanding
The problem gives us an array of positive integers and asks us to choose the largest possible subset that can be rearranged into a very specific symmetric structure.
The required structure looks like this:
$$[x, x^2, x^4, \dots, x^{k/2}, x^k, x^{k/2}, \dots, x^4, x^2, x]$$
where $k$ is a power of two.
The sequence grows by repeatedly squaring the previous value until it reaches a peak, then mirrors itself back down. Every value except the middle element appears exactly twice because of the symmetry.
For example:
-
[2, 4, 16, 4, 2]is valid because: -
$2^2 = 4$
-
$4^2 = 16$
-
then the sequence mirrors back
-
[3, 9, 3]is valid because: -
$3^2 = 9$
-
[2, 4, 8, 4, 2]is invalid because $4^2 \neq 8$
The task is to determine the maximum possible size of such a subset.
A very important observation is that every element except the center must appear twice. Therefore, if we want to build a chain like:
$$x \rightarrow x^2 \rightarrow x^4 \rightarrow x^8$$
then:
xmust appear at least twicex²must appear at least twicex⁴must appear at least twice- the final peak value only needs to appear once
The constraints are large:
nums.length <= 10^5nums[i] <= 10^9
This immediately tells us that brute force subset generation is impossible because there are $2^n$ subsets.
There are also several tricky edge cases:
- The number
1is special because:
$$1^2 = 1$$
so the chain never changes.
- Large squaring operations can grow extremely quickly.
- Some numbers may exist only once, meaning they cannot appear in mirrored positions.
- Multiple possible chains may overlap, so we must carefully compute the maximum valid length.
Approaches
Brute Force Approach
A brute force solution would try every subset of the array and check whether it can be rearranged into the required pattern.
To verify a subset, we would:
- Sort or arrange the subset.
- Attempt to construct a valid squaring chain.
- Check symmetry and frequency requirements.
This approach is correct because it explores every possible subset, guaranteeing that the maximum valid one will eventually be found.
However, the number of subsets is:
$$2^n$$
With $n = 10^5$, this is completely infeasible.
Even generating all subsets for $n = 30$ would already be too expensive.
Optimal Approach
The key observation is that the structure is entirely determined by repeated squaring.
If we start from some value x, then the only possible next values are:
$$x^2, x^4, x^8, \dots$$
This means we do not need to explore arbitrary subsets. We only need to examine valid squaring chains.
We count the frequency of every number using a hash map.
For a chain:
$$x \rightarrow x^2 \rightarrow x^4 \rightarrow \dots$$
every non-peak value must appear at least twice because it appears on both sides of the symmetric array.
The final peak value may appear once.
Therefore:
- if a number has frequency at least
2, we can extend the chain - if it only appears once, it can only serve as the center
The number 1 requires special handling because squaring never changes it. A valid sequence of ones must always have odd length:
$$[1], [1,1,1], [1,1,1,1,1], \dots$$
So if there are c ones:
- if
cis odd, answer contribution isc - if
cis even, answer contribution isc - 1
This leads to an efficient frequency-based solution.
Approach Comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(2^n) | O(n) | Enumerates every subset |
| Optimal | O(n + m log log M) | O(n) | Uses frequency counting and squaring chains |
Here:
mis the number of distinct valuesMis the maximum value
The squaring chains grow extremely quickly, so each chain is very short.
Algorithm Walkthrough
- Build a frequency map of all numbers using a hash table.
This allows us to quickly determine how many times each value appears.
2. Handle the special case for the number 1.
Since:
$$1^2 = 1$$
any valid sequence consisting only of ones must have odd length.
If the count of ones is:
- odd, we can use all of them
- even, we must leave one unused
- Iterate through every distinct number greater than
1.
Treat each number as a possible starting value of a chain. 4. Repeatedly square the current value.
While the current value exists at least twice:
- we can place one copy on the left
- one copy on the right
This contributes 2 elements to the sequence.
5. Continue extending the chain.
Move from:
$$x \rightarrow x^2 \rightarrow x^4 \rightarrow \dots$$
until the next required number is unavailable. 6. Determine the peak element.
If the current number still exists at least once, it can become the center of the sequence, contributing 1.
7. Track the maximum sequence length across all starting values.
Why it works
The sequence structure uniquely determines which values are required at every level of the chain.
Every internal layer must appear twice because of symmetry, while the center only needs one occurrence.
By greedily extending the chain as long as frequency constraints allow, we construct the longest valid sequence for each starting value. Since every possible chain start is examined, the global maximum is guaranteed to be found.
Python Solution
from typing import List
from collections import Counter
class Solution:
def maximumLength(self, nums: List[int]) -> int:
frequency = Counter(nums)
answer = 1
# Special handling for 1
if 1 in frequency:
ones = frequency[1]
if ones % 2 == 0:
answer = max(answer, ones - 1)
else:
answer = max(answer, ones)
for value in frequency:
if value == 1:
continue
current = value
length = 0
while frequency.get(current, 0) >= 2:
length += 2
current = current * current
if frequency.get(current, 0) >= 1:
length += 1
else:
length -= 1
answer = max(answer, length)
return answer
The implementation begins by building a frequency map with Counter. This gives constant-time access to the number of occurrences of each value.
The variable answer stores the best sequence length found so far.
The number 1 is processed separately because its squaring chain never changes. The longest valid sequence of ones must have odd length, so we either use all ones or all but one.
For every other distinct value, we attempt to build the longest possible squaring chain.
The loop:
while frequency.get(current, 0) >= 2:
checks whether the current value can occupy mirrored positions in the sequence. If so, we add 2 to the length and square the current value.
After the loop finishes, we check whether the final value exists at least once. If it does, it becomes the center element.
If it does not exist, then the last added pair was invalid for forming a complete symmetric structure, so we subtract one.
Finally, we update the global maximum answer.
Go Solution
package main
func maximumLength(nums []int) int {
frequency := make(map[int]int)
for _, num := range nums {
frequency[num]++
}
answer := 1
// Special handling for 1
if count, exists := frequency[1]; exists {
if count%2 == 0 {
if count-1 > answer {
answer = count - 1
}
} else {
if count > answer {
answer = count
}
}
}
for value := range frequency {
if value == 1 {
continue
}
current := value
length := 0
for frequency[current] >= 2 {
length += 2
current = current * current
}
if frequency[current] >= 1 {
length++
} else {
length--
}
if length > answer {
answer = length
}
}
return answer
}
The Go implementation follows the same logic as the Python solution.
A map[int]int is used instead of Python's Counter.
One subtle difference is that Go map lookups return zero values for missing keys, so:
frequency[current]
naturally behaves like Python's get(current, 0).
Integer overflow is not a practical concern here because once values become very large, they stop existing in the frequency map and the loop terminates quickly.
Worked Examples
Example 1
Input:
nums = [5,4,1,2,2]
Frequency map:
| Value | Count |
|---|---|
| 1 | 1 |
| 2 | 2 |
| 4 | 1 |
| 5 | 1 |
Initial answer from ones:
answer = 1
Now examine value 2.
| Step | Current | Frequency | Length |
|---|---|---|---|
| Start | 2 | 2 | 0 |
| Use mirrored pair | 2 | 2 | 2 |
| Square | 4 | 1 | 2 |
| Use center | 4 | 1 | 3 |
Resulting sequence:
[2,4,2]
Length is 3.
Now examine value 4.
| Step | Current | Frequency | Length |
|---|---|---|---|
| Start | 4 | 1 | 0 |
| Cannot form pair | 4 | 1 | 0 |
| Use center | 4 | 1 | 1 |
Maximum answer remains:
3
Example 2
Input:
nums = [1,3,2,4]
Frequency map:
| Value | Count |
|---|---|
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 1 |
Ones contribution:
answer = 1
Every other value appears only once, so none can form mirrored pairs.
Each contributes only:
length = 1
Final answer:
1
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n + m log log M) | Frequency construction plus short squaring chains |
| Space | O(n) | Hash map storing frequencies |
The frequency map requires linear space in the number of distinct elements.
The squaring chains are extremely short because values grow exponentially:
$$x, x^2, x^4, x^8$$
Even starting from 2, the sequence exceeds $10^9$ after very few squaring operations.
Therefore, the total chain-processing work is very small.
Test Cases
from typing import List
class Solution:
def maximumLength(self, nums: List[int]) -> int:
from collections import Counter
frequency = Counter(nums)
answer = 1
if 1 in frequency:
ones = frequency[1]
if ones % 2 == 0:
answer = max(answer, ones - 1)
else:
answer = max(answer, ones)
for value in frequency:
if value == 1:
continue
current = value
length = 0
while frequency.get(current, 0) >= 2:
length += 2
current *= current
if frequency.get(current, 0) >= 1:
length += 1
else:
length -= 1
answer = max(answer, length)
return answer
solution = Solution()
assert solution.maximumLength([5,4,1,2,2]) == 3 # example 1
assert solution.maximumLength([1,3,2,4]) == 1 # example 2
assert solution.maximumLength([1,1,1]) == 3 # odd count of ones
assert solution.maximumLength([1,1,1,1]) == 3 # even count of ones
assert solution.maximumLength([2,2,4]) == 3 # simple valid chain
assert solution.maximumLength([2,2,4,4,16]) == 5 # deeper chain
assert solution.maximumLength([2]) == 1 # single element
assert solution.maximumLength([10,10]) == 1 # pair without valid center
assert solution.maximumLength([3,3,9,9,81]) == 5 # multiple squaring levels
assert solution.maximumLength([2,2,4,4,16,16,256]) == 7 # long chain
assert solution.maximumLength([2,2,4,16]) == 3 # missing duplicated middle
assert solution.maximumLength([1000000000]) == 1 # large value
Test Summary
| Test | Why |
|---|---|
[5,4,1,2,2] |
Validates sample case with chain length 3 |
[1,3,2,4] |
Validates case where only singleton subsets work |
[1,1,1] |
Tests odd count of ones |
[1,1,1,1] |
Tests even count of ones |
[2,2,4] |
Tests minimal nontrivial chain |
[2,2,4,4,16] |
Tests multi-level squaring |
[2] |
Tests single-element input |
[10,10] |
Tests pair without valid center |
[3,3,9,9,81] |
Tests another deep chain |
[2,2,4,4,16,16,256] |
Tests maximum-length chain growth |
[2,2,4,16] |
Tests incomplete chain |
[1000000000] |
Tests large-number handling |
Edge Cases
One important edge case involves the number 1. Since squaring 1 always produces 1, the normal chain-extension logic would loop forever if handled naively. The implementation treats 1 separately and uses the observation that a valid symmetric sequence must have odd length. This guarantees correct handling without infinite loops.
Another tricky case occurs when a value appears exactly twice but its square does not exist. For example:
[10,10]
We can place one 10 on each side, but there is no valid center. The implementation detects this because the chain eventually ends without a usable middle value, and it subtracts one from the computed length.
A third important edge case is extremely large values. Squaring grows very quickly, so values can exceed the problem constraints almost immediately. The implementation avoids unnecessary processing because it only continues while the squared value exists in the frequency map. Missing values terminate the chain naturally.