2154. Keep Multiplying Found Values by Two
Problem Description
You have an array of integers nums
and a starting integer value called original
. Your task is to perform a series of operations on original
based on whether it exists in the array.
The process works as follows:
- Check if
original
exists innums
- If it exists, multiply
original
by 2 (updateoriginal
to2 * original
) - If it doesn't exist, stop the process
- Repeat steps 1-3 with the new value of
original
Keep repeating this process until you encounter a value that is not present in nums
. Return the final value of original
when the process stops.
For example, if nums = [5, 3, 6, 1, 12]
and original = 3
:
3
is innums
, sooriginal
becomes6
6
is innums
, sooriginal
becomes12
12
is innums
, sooriginal
becomes24
24
is not innums
, so we stop and return24
The solution uses a hash table (set) to store all numbers from nums
for O(1) lookup time. It then continuously doubles original
(using left shift <<= 1
which is equivalent to multiplying by 2) while it exists in the set, returning the final value when it's no longer found.
Intuition
The key insight is that we need to repeatedly check if a value exists in the array. Since we might need to check multiple values (original, 2×original, 4×original, etc.), doing a linear search through the array each time would be inefficient.
This naturally leads us to think about preprocessing the array into a data structure that supports fast lookups. A hash table (or set in this case) is perfect because:
- We can check if a value exists in O(1) time
- We don't care about the order or frequency of elements, just their presence
- The one-time cost of O(n) to build the set is worth it since we might need multiple lookups
Once we have fast lookups, the algorithm becomes straightforward - we just keep checking and doubling. The doubling operation can be done with multiplication original * 2
or with a left bit shift original << 1
(shifting bits left by 1 position doubles the number in binary).
The process must eventually terminate because we're doubling the value each time. Even if every possible doubled value existed in the array, we would eventually exceed the maximum possible integer value in the array, guaranteeing the loop stops.
Learn more about Sorting patterns.
Solution Approach
The implementation uses a hash table to optimize the lookup operations:
-
Convert array to set: First, we create a set
s
from the input arraynums
. This conversion takes O(n) time but allows us to check membership in O(1) time for all subsequent operations.s = set(nums)
-
Iterative doubling with lookup: We use a while loop to continuously check if
original
exists in the set. As long as it exists, we double the value:while original in s: original <<= 1
The operation
original <<= 1
is a left bit shift by 1 position, which effectively multiplies the number by 2. This is equivalent tooriginal = original * 2
but is slightly more efficient at the bit level. -
Return the result: Once
original
is no longer in the set, the while loop terminates and we return the current value oforiginal
.
Time Complexity: O(n + m) where n is the length of nums
and m is the number of doubling operations performed. In the worst case, m could be log(max_value/original) where max_value is the largest number in the array.
Space Complexity: O(n) for storing the set containing all elements from nums
.
The beauty of this solution lies in its simplicity - by preprocessing the array into a set, we transform what could be an O(n×m) algorithm (checking the array m times) into an O(n + m) algorithm.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example with nums = [2, 8, 4, 16]
and original = 2
:
Step 1: Create a set from the array
- Convert
nums
to a set:s = {2, 4, 8, 16}
- This allows O(1) lookup time for checking if values exist
Step 2: Iteratively check and double
Iteration 1:
- Check: Is
original = 2
in sets
? YES (2 is in {2, 4, 8, 16}) - Action: Double the value using left shift:
original = 2 << 1 = 4
Iteration 2:
- Check: Is
original = 4
in sets
? YES (4 is in {2, 4, 8, 16}) - Action: Double the value:
original = 4 << 1 = 8
Iteration 3:
- Check: Is
original = 8
in sets
? YES (8 is in {2, 4, 8, 16}) - Action: Double the value:
original = 8 << 1 = 16
Iteration 4:
- Check: Is
original = 16
in sets
? YES (16 is in {2, 4, 8, 16}) - Action: Double the value:
original = 16 << 1 = 32
Iteration 5:
- Check: Is
original = 32
in sets
? NO (32 is not in {2, 4, 8, 16}) - Action: Stop the loop
Step 3: Return the final value
- Return
original = 32
The process traced through the sequence: 2 → 4 → 8 → 16 → 32, stopping at 32 because it's not in the original array.
Solution Implementation
1class Solution:
2 def findFinalValue(self, nums: List[int], original: int) -> int:
3 # Convert the list to a set for O(1) lookup time
4 nums_set = set(nums)
5
6 # Keep doubling the original value while it exists in the set
7 while original in nums_set:
8 # Left shift by 1 is equivalent to multiplying by 2
9 original = original << 1
10
11 # Return the final value after all doublings
12 return original
13
1class Solution {
2
3 /**
4 * Finds the final value after doubling the original value whenever it exists in the array.
5 * The process continues until the value is not found in the array.
6 *
7 * @param nums The input array of integers
8 * @param original The starting value to check and potentially double
9 * @return The final value after all doubling operations
10 */
11 public int findFinalValue(int[] nums, int original) {
12 // Create a HashSet to store all unique values from the array for O(1) lookup
13 Set<Integer> numSet = new HashSet<>();
14
15 // Add all elements from the array to the set
16 for (int num : nums) {
17 numSet.add(num);
18 }
19
20 // Keep doubling the original value while it exists in the set
21 while (numSet.contains(original)) {
22 // Double the original value using left shift operation (equivalent to multiplying by 2)
23 original <<= 1;
24 }
25
26 // Return the final value that is not present in the set
27 return original;
28 }
29}
30
1class Solution {
2public:
3 int findFinalValue(vector<int>& nums, int original) {
4 // Create a hash set from the input array for O(1) lookup
5 unordered_set<int> numSet(nums.begin(), nums.end());
6
7 // Keep doubling the original value while it exists in the set
8 while (numSet.count(original)) {
9 original *= 2; // Double the value (equivalent to left shift by 1)
10 }
11
12 // Return the final value that doesn't exist in the set
13 return original;
14 }
15};
16
1/**
2 * Finds the final value after repeatedly doubling the original value
3 * while it exists in the array
4 * @param nums - Array of numbers to check against
5 * @param original - Starting value to potentially double
6 * @returns The final value after all doubling operations
7 */
8function findFinalValue(nums: number[], original: number): number {
9 // Create a set from the input array for O(1) lookup time
10 const numSet: Set<number> = new Set(nums);
11
12 // Keep doubling the original value while it exists in the set
13 while (numSet.has(original)) {
14 // Left shift by 1 is equivalent to multiplying by 2
15 original <<= 1;
16 }
17
18 // Return the final value when it's no longer in the set
19 return original;
20}
21
Time and Space Complexity
The time complexity is O(n)
, and the space complexity is O(n)
. Here, n
is the length of the array nums
.
Time Complexity Analysis:
- Converting the list
nums
to a set takesO(n)
time - The while loop performs membership checks in the set, where each check takes
O(1)
time on average - The maximum number of iterations in the while loop is bounded by
O(log(max_value))
wheremax_value
is the maximum possible value thatoriginal
can reach - Since the values are bounded by practical integer limits, the while loop iterations are effectively constant
- Therefore, the overall time complexity is dominated by the set creation:
O(n)
Space Complexity Analysis:
- Creating a set from the array
nums
requiresO(n)
space to store all unique elements - The only other variable used is
original
, which takesO(1)
space - Therefore, the total space complexity is
O(n)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Integer Overflow in Other Languages
While Python handles arbitrarily large integers automatically, in languages like Java or C++, continuously doubling values can lead to integer overflow. For instance, if you start with a large original
value and it exists multiple times in doubled form, you might exceed the maximum integer limit.
Solution: In languages with fixed integer sizes, consider:
- Using long/long long data types
- Adding overflow checks before doubling
- Setting a reasonable upper bound for the operation
2. Inefficient Array Lookup Instead of Set
A common mistake is checking membership directly in the array instead of converting it to a set first:
# Inefficient approach - DO NOT USE while original in nums: # O(n) lookup each time original <<= 1
This degrades performance from O(n + m) to O(n × m) where m is the number of doubling operations.
Solution: Always convert to a set first for O(1) lookups:
nums_set = set(nums)
while original in nums_set: # O(1) lookup
original <<= 1
3. Modifying the Original Array
Some might attempt to remove elements from nums
as they're found, thinking it optimizes the search:
# Wrong approach - modifies input while original in nums: nums.remove(original) # O(n) operation and changes input original <<= 1
This not only has poor performance but also modifies the input array, which may not be allowed.
Solution: Use the immutable set approach without modifying the original input.
4. Bit Shift Operator Confusion
Using the wrong bit shift operator or shift amount:
# Common mistakes original >> 1 # Right shift (divides by 2) instead of left shift original << 2 # Shifts by 2 positions (multiplies by 4) instead of 2
Solution: Use original << 1
or original <<= 1
for doubling, or simply use original * 2
for clarity if bit operations are unfamiliar.
Which technique can we use to find the middle of a linked list?
Recommended Readings
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!