2169. Count Operations to Obtain Zero
Problem Description
You are given two non-negative integers num1
and num2
.
The task is to perform a series of operations until one of the numbers becomes 0
. In each operation:
- If
num1 >= num2
, subtractnum2
fromnum1
- Otherwise, subtract
num1
fromnum2
For example:
- Starting with
num1 = 5
andnum2 = 4
:- Since
5 >= 4
, we performnum1 = 5 - 4 = 1
, resulting innum1 = 1
andnum2 = 4
- Since
- Starting with
num1 = 4
andnum2 = 5
:- Since
4 < 5
, we performnum2 = 5 - 4 = 1
, resulting innum1 = 4
andnum2 = 1
- Since
The goal is to return the total number of operations required to make either num1 = 0
or num2 = 0
.
This process simulates repeatedly subtracting the smaller number from the larger number until one of them reaches zero, counting each subtraction as one operation.
Intuition
The problem is essentially asking us to simulate a process that resembles finding the greatest common divisor (GCD) using the subtraction method, but we need to count the steps rather than find the GCD itself.
The key observation is that we're always subtracting the smaller number from the larger one. This process continues until one number becomes 0
. At each step, we're reducing the total sum of the two numbers, guaranteeing that the process will eventually terminate.
Let's trace through an example to understand the pattern:
- If we have
num1 = 7
andnum2 = 3
:- Step 1:
7 - 3 = 4
, sonum1 = 4
,num2 = 3
- Step 2:
4 - 3 = 1
, sonum1 = 1
,num2 = 3
- Step 3:
3 - 1 = 2
, sonum1 = 1
,num2 = 2
- Step 4:
2 - 1 = 1
, sonum1 = 1
,num2 = 1
- Step 5:
1 - 1 = 0
, sonum1 = 0
,num2 = 1
- Total operations: 5
- Step 1:
The straightforward approach is to directly simulate what the problem describes: keep track of both numbers, perform the subtraction based on which is larger, and count each operation. Since we're always making progress (reducing at least one number), and the numbers are non-negative, we're guaranteed to reach 0
eventually.
The simulation approach works well here because:
- The operation rules are clearly defined
- We have a clear termination condition (when either number becomes
0
) - Each operation brings us closer to the termination condition
Learn more about Math patterns.
Solution Approach
The solution implements a direct simulation approach using a simple while loop. Here's how the implementation works:
-
Initialize a counter: We start with
ans = 0
to keep track of the number of operations performed. -
Loop condition: We use
while num1 and num2
which continues as long as both numbers are non-zero. The loop terminates when eithernum1
ornum2
becomes0
. -
Perform the operation: Inside the loop, we check which number is larger:
- If
num1 >= num2
, we subtractnum2
fromnum1
:num1 = num1 - num2
- Otherwise, we subtract
num1
fromnum2
:num2 = num2 - num1
- If
-
Count the operation: After each subtraction, we increment the counter:
ans += 1
-
Return the result: Once the loop exits (when one number becomes
0
), we return the total countans
.
The algorithm follows these steps:
ans = 0 while num1 and num2: if num1 >= num2: num1 -= num2 else: num2 -= num1 ans += 1 return ans
Time Complexity: The time complexity depends on the values of num1
and num2
. In the worst case, if one number is much larger than the other, we might need to perform many subtractions. The complexity is proportional to the larger number divided by the smaller number.
Space Complexity: O(1)
as we only use a constant amount of extra space for the counter variable.
The beauty of this solution lies in its simplicity - it directly translates the problem statement into code without any complex transformations or optimizations.
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 the solution with num1 = 10
and num2 = 3
:
Initial state: num1 = 10
, num2 = 3
, ans = 0
Operation 1: Since 10 >= 3
, we subtract: num1 = 10 - 3 = 7
- Result:
num1 = 7
,num2 = 3
,ans = 1
Operation 2: Since 7 >= 3
, we subtract: num1 = 7 - 3 = 4
- Result:
num1 = 4
,num2 = 3
,ans = 2
Operation 3: Since 4 >= 3
, we subtract: num1 = 4 - 3 = 1
- Result:
num1 = 1
,num2 = 3
,ans = 3
Operation 4: Since 1 < 3
, we subtract: num2 = 3 - 1 = 2
- Result:
num1 = 1
,num2 = 2
,ans = 4
Operation 5: Since 1 < 2
, we subtract: num2 = 2 - 1 = 1
- Result:
num1 = 1
,num2 = 1
,ans = 5
Operation 6: Since 1 >= 1
, we subtract: num1 = 1 - 1 = 0
- Result:
num1 = 0
,num2 = 1
,ans = 6
Termination: Since num1 = 0
, the while loop exits.
Return: The function returns ans = 6
The process required 6 operations to reduce one of the numbers to 0. Notice how we always subtract the smaller value from the larger one, gradually reducing the numbers until one reaches zero.
Solution Implementation
1class Solution:
2 def countOperations(self, num1: int, num2: int) -> int:
3 """
4 Count the number of operations to reduce both numbers to zero.
5 In each operation, subtract the smaller number from the larger one.
6
7 Args:
8 num1: First positive integer
9 num2: Second positive integer
10
11 Returns:
12 Number of operations performed until one number becomes zero
13 """
14 # Initialize operation counter
15 operation_count = 0
16
17 # Continue operations while both numbers are non-zero
18 while num1 != 0 and num2 != 0:
19 # Subtract the smaller number from the larger one
20 if num1 >= num2:
21 num1 -= num2
22 else:
23 num2 -= num1
24
25 # Increment the operation counter
26 operation_count += 1
27
28 return operation_count
29
1class Solution {
2 /**
3 * Counts the number of operations to reduce one number to zero
4 * by repeatedly subtracting the smaller from the larger.
5 * This implements a variant of the Euclidean algorithm.
6 *
7 * @param num1 First non-negative integer
8 * @param num2 Second non-negative integer
9 * @return Number of operations performed until one number becomes zero
10 */
11 public int countOperations(int num1, int num2) {
12 // Initialize operation counter
13 int operationCount = 0;
14
15 // Continue operations while both numbers are non-zero
16 while (num1 != 0 && num2 != 0) {
17 // Subtract the smaller number from the larger number
18 if (num1 >= num2) {
19 num1 -= num2;
20 } else {
21 num2 -= num1;
22 }
23
24 // Increment the operation counter after each subtraction
25 operationCount++;
26 }
27
28 // Return the total number of operations performed
29 return operationCount;
30 }
31}
32
1class Solution {
2public:
3 /**
4 * Counts the number of operations needed until one number becomes zero.
5 * In each operation, subtract the smaller number from the larger number.
6 *
7 * @param num1 First positive integer
8 * @param num2 Second positive integer
9 * @return Number of operations performed
10 */
11 int countOperations(int num1, int num2) {
12 int operationCount = 0;
13
14 // Continue operations while both numbers are non-zero
15 while (num1 != 0 && num2 != 0) {
16 // Subtract the smaller number from the larger number
17 if (num1 >= num2) {
18 num1 -= num2;
19 } else {
20 num2 -= num1;
21 }
22
23 // Increment the operation counter
24 operationCount++;
25 }
26
27 return operationCount;
28 }
29};
30
1/**
2 * Counts the number of operations needed to reduce one of the two numbers to zero
3 * by repeatedly subtracting the smaller number from the larger number
4 * @param num1 - The first positive integer
5 * @param num2 - The second positive integer
6 * @returns The total number of operations performed
7 */
8function countOperations(num1: number, num2: number): number {
9 // Initialize operation counter
10 let operationCount: number = 0;
11
12 // Continue while both numbers are non-zero
13 while (num1 !== 0 && num2 !== 0) {
14 // Subtract the smaller number from the larger number
15 if (num1 >= num2) {
16 num1 -= num2;
17 } else {
18 num2 -= num1;
19 }
20
21 // Increment the operation counter
22 operationCount++;
23 }
24
25 // Return the total number of operations
26 return operationCount;
27}
28
Time and Space Complexity
The time complexity is O(max(num1, num2))
in the worst case. This occurs when one number is much larger than the other, or when the numbers are consecutive (like num1 = n
and num2 = 1
). In such cases, the algorithm essentially performs repeated subtractions similar to the Euclidean algorithm for finding GCD. For example, with num1 = 100
and num2 = 1
, the loop will execute 100 times, subtracting 1 from 100 repeatedly until num1
becomes 0.
More precisely, the number of operations is bounded by max(num1, num2) / min(num1, num2)
when min(num1, num2) > 0
. The algorithm behaves similarly to the subtraction-based GCD algorithm, where each iteration reduces the larger number by the smaller one.
The space complexity is O(1)
as the algorithm only uses a constant amount of extra space. It modifies the input parameters num1
and num2
in-place and only maintains one additional variable ans
to count the operations. No additional data structures or recursive calls are used that would increase the space complexity.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Inefficiency with Large Number Differences
The Pitfall: When one number is significantly larger than the other (e.g., num1 = 1000000
and num2 = 1
), the naive simulation approach performs an excessive number of operations. Each iteration only subtracts the smaller value once, leading to potentially millions of iterations.
Example Scenario:
- With
num1 = 1000
andnum2 = 3
, the algorithm would perform 333 subtractions one by one - This becomes extremely slow for larger values like
num1 = 10^9
andnum2 = 1
Solution - Optimization Using Division: Instead of subtracting repeatedly, we can calculate how many times the smaller number fits into the larger number using integer division. This reduces multiple operations into a single calculation:
class Solution:
def countOperations(self, num1: int, num2: int) -> int:
operation_count = 0
while num1 != 0 and num2 != 0:
if num1 >= num2:
# Calculate how many times num2 fits into num1
operations_batch = num1 // num2
operation_count += operations_batch
# Update num1 to the remainder
num1 = num1 % num2
else:
# Calculate how many times num1 fits into num2
operations_batch = num2 // num1
operation_count += operations_batch
# Update num2 to the remainder
num2 = num2 % num1
return operation_count
This optimization transforms the time complexity from O(max(num1, num2)) to O(log(min(num1, num2))), which is essentially the Euclidean algorithm for finding GCD.
2. Not Handling Edge Cases
The Pitfall: Failing to consider when one or both inputs are already zero.
Solution: Add an early return check:
if num1 == 0 or num2 == 0: return 0
3. Integer Overflow in Other Languages
The Pitfall: While Python handles arbitrary precision integers automatically, implementing this in languages like C++ or Java might face integer overflow issues during intermediate calculations.
Solution: Use appropriate data types (like long long
in C++ or long
in Java) and be mindful of the maximum values that can be handled.
What's the output of running the following function using input 56
?
1KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12def letter_combinations_of_phone_number(digits):
13 def dfs(path, res):
14 if len(path) == len(digits):
15 res.append(''.join(path))
16 return
17
18 next_number = digits[len(path)]
19 for letter in KEYBOARD[next_number]:
20 path.append(letter)
21 dfs(path, res)
22 path.pop()
23
24 res = []
25 dfs([], res)
26 return res
27
1private static final Map<Character, char[]> KEYBOARD = Map.of(
2 '2', "abc".toCharArray(),
3 '3', "def".toCharArray(),
4 '4', "ghi".toCharArray(),
5 '5', "jkl".toCharArray(),
6 '6', "mno".toCharArray(),
7 '7', "pqrs".toCharArray(),
8 '8', "tuv".toCharArray(),
9 '9', "wxyz".toCharArray()
10);
11
12public static List<String> letterCombinationsOfPhoneNumber(String digits) {
13 List<String> res = new ArrayList<>();
14 dfs(new StringBuilder(), res, digits.toCharArray());
15 return res;
16}
17
18private static void dfs(StringBuilder path, List<String> res, char[] digits) {
19 if (path.length() == digits.length) {
20 res.add(path.toString());
21 return;
22 }
23 char next_digit = digits[path.length()];
24 for (char letter : KEYBOARD.get(next_digit)) {
25 path.append(letter);
26 dfs(path, res, digits);
27 path.deleteCharAt(path.length() - 1);
28 }
29}
30
1const KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12function letter_combinations_of_phone_number(digits) {
13 let res = [];
14 dfs(digits, [], res);
15 return res;
16}
17
18function dfs(digits, path, res) {
19 if (path.length === digits.length) {
20 res.push(path.join(''));
21 return;
22 }
23 let next_number = digits.charAt(path.length);
24 for (let letter of KEYBOARD[next_number]) {
25 path.push(letter);
26 dfs(digits, path, res);
27 path.pop();
28 }
29}
30
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
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!