1237. Find Positive Integer Solution for a Given Equation
Problem Description
You are given a callable function f(x, y)
with a hidden formula and a target value z
. Your task is to find all positive integer pairs (x, y)
such that f(x, y) == z
.
The function f(x, y)
has the following properties:
- It takes two positive integers
x
andy
as inputs - It returns a positive integer based on a hidden formula
- It is monotonically increasing in both parameters:
f(x, y) < f(x + 1, y)
(increasing with respect tox
)f(x, y) < f(x, y + 1)
(increasing with respect toy
)
You need to implement a findSolution
method that:
- Takes a
CustomFunction
object and an integerz
as parameters - Returns a list of all
[x, y]
pairs wheref(x, y) == z
- The pairs can be returned in any order
- Both
x
andy
must be positive integers
The solution approach leverages the monotonic property of the function. Since f(x, y)
is increasing in both dimensions, for each value of x
from 1
to z
, we can use binary search to find if there exists a y
value such that f(x, y) == z
. The binary search is performed on the range [1, z]
for y
, using the function value f(x, y)
as the comparison key. If a valid y
is found for a given x
, the pair [x, y]
is added to the result list.
Intuition
The key insight comes from understanding the monotonic property of the function. Since f(x, y)
increases as either x
or y
increases, we can think of the function values as forming a 2D grid where values grow both rightward and upward.
Imagine we fix x
at some value. As y
increases from 1
onwards, f(x, y)
will produce an increasing sequence of values. At some point, this sequence might contain our target z
, or it might skip over it entirely. Because the sequence is sorted, we can efficiently search for z
using binary search instead of checking every possible y
value.
Why enumerate x
and binary search y
? We could do it either way, but we need to fix one variable to create a one-dimensional sorted sequence that we can binary search. By iterating through all possible x
values from 1
to z
, we ensure we don't miss any valid pairs.
Why can we limit both x
and y
to the range [1, z]
? Since f(x, y)
is monotonically increasing and both x
and y
are positive integers starting from 1
, the minimum possible value is f(1, 1)
. If f(1, 1) ≤ z
, then for any x > z
or y > z
, we would have f(x, 1) > f(z, 1) ≥ f(1, 1) ≥ z
or similarly for y
. This means f(x, y)
would definitely exceed z
when either parameter is greater than z
.
The binary search finds the leftmost position where f(x, y) >= z
. If the function value at that position equals z
, we've found a valid pair. If it's greater than z
, it means no y
value for this particular x
gives us exactly z
.
Learn more about Math, Two Pointers and Binary Search patterns.
Solution Approach
The implementation uses enumeration combined with binary search to efficiently find all valid pairs.
Step-by-step implementation:
-
Initialize the result list: Create an empty list
ans
to store all valid[x, y]
pairs. -
Enumerate x values: Loop through all possible
x
values from1
toz
:for x in range(1, z + 1):
-
Binary search for y: For each fixed
x
, use binary search to find ay
value wheref(x, y) = z
. The code uses Python'sbisect_left
function with a custom key:y = 1 + bisect_left( range(1, z + 1), z, key=lambda y: customfunction.f(x, y) )
Here's what happens:
range(1, z + 1)
creates the search space fory
valuesbisect_left
searches for the leftmost position where we could insertz
to maintain sorted orderkey=lambda y: customfunction.f(x, y)
tells the binary search to compare based on function values, not they
values themselves- We add
1
to the result becausebisect_left
returns an index (0-based) but oury
values start from1
-
Check if exact match found: After binary search, verify if the found position gives us exactly
z
:if customfunction.f(x, y) == z: ans.append([x, y])
The binary search finds where
f(x, y) >= z
. Iff(x, y) == z
, we have a valid pair. Iff(x, y) > z
, it means noy
value for thisx
produces exactlyz
. -
Return all pairs: After checking all possible
x
values, return the collected pairs.
Time Complexity: O(z * log z)
- We iterate through z
values of x
, and for each x
, we perform a binary search over at most z
values of y
.
Space Complexity: O(1)
if we don't count the output array, as we only use a constant amount of extra space for variables.
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 concrete example where we have a hidden function and need to find all pairs (x, y) where f(x, y) = 5.
Suppose the hidden function is f(x, y) = x + y (we don't know this, but it helps visualize the process).
Target: z = 5
We'll iterate through x values from 1 to 5 and use binary search to find corresponding y values:
When x = 1:
- We binary search for y in range [1, 5] where f(1, y) = 5
- Binary search process:
- Check middle: y = 3, f(1, 3) = 4 < 5 (go right)
- Check y = 4, f(1, 4) = 5 = 5 ✓
- Found pair: [1, 4]
When x = 2:
- Binary search for y in range [1, 5] where f(2, y) = 5
- Binary search process:
- Check middle: y = 3, f(2, 3) = 5 = 5 ✓
- Found pair: [2, 3]
When x = 3:
- Binary search for y in range [1, 5] where f(3, y) = 5
- Binary search process:
- Check middle: y = 3, f(3, 3) = 6 > 5 (go left)
- Check y = 1, f(3, 1) = 4 < 5 (go right)
- Check y = 2, f(3, 2) = 5 = 5 ✓
- Found pair: [3, 2]
When x = 4:
- Binary search for y in range [1, 5] where f(4, y) = 5
- Binary search process:
- Check middle: y = 3, f(4, 3) = 7 > 5 (go left)
- Check y = 1, f(4, 1) = 5 = 5 ✓
- Found pair: [4, 1]
When x = 5:
- Binary search for y in range [1, 5] where f(5, y) = 5
- Binary search process:
- Check middle: y = 3, f(5, 3) = 8 > 5 (go left)
- Check y = 1, f(5, 1) = 6 > 5 (go left further)
- No valid y exists (all values > 5)
- No pair found
Result: [[1, 4], [2, 3], [3, 2], [4, 1]]
The key insight is that for each fixed x, the function values f(x, 1), f(x, 2), f(x, 3)... form an increasing sequence, allowing us to efficiently binary search for the target value rather than checking every possibility.
Solution Implementation
1from typing import List
2from bisect import bisect_left
3
4
5class Solution:
6 def findSolution(self, customfunction: 'CustomFunction', z: int) -> List[List[int]]:
7 """
8 Find all pairs [x, y] where customfunction.f(x, y) == z.
9
10 Since f(x, y) is strictly increasing with respect to both x and y,
11 we can use binary search to find valid y values for each x.
12
13 Args:
14 customfunction: Custom function object with method f(x, y)
15 z: Target value to find
16
17 Returns:
18 List of [x, y] pairs where f(x, y) == z
19 """
20 result = []
21
22 # Try each possible x value from 1 to z
23 for x in range(1, z + 1):
24 # Binary search for y where f(x, y) == z
25 # Since f is increasing, we search in range [1, z]
26 left, right = 1, z
27
28 while left <= right:
29 mid = (left + right) // 2
30 current_value = customfunction.f(x, mid)
31
32 if current_value == z:
33 # Found a valid pair
34 result.append([x, mid])
35 break
36 elif current_value < z:
37 # Need larger y value
38 left = mid + 1
39 else:
40 # Need smaller y value
41 right = mid - 1
42
43 return result
44
1/*
2 * // This is the custom function interface.
3 * // You should not implement it, or speculate about its implementation
4 * class CustomFunction {
5 * // Returns f(x, y) for any given positive integers x and y.
6 * // Note that f(x, y) is increasing with respect to both x and y.
7 * // i.e. f(x, y) < f(x + 1, y), f(x, y) < f(x, y + 1)
8 * public int f(int x, int y);
9 * };
10 */
11
12class Solution {
13 public List<List<Integer>> findSolution(CustomFunction customfunction, int z) {
14 // List to store all pairs (x, y) where f(x, y) = z
15 List<List<Integer>> result = new ArrayList<>();
16
17 // Iterate through all possible x values from 1 to 1000
18 for (int x = 1; x <= 1000; x++) {
19 // Use binary search to find y such that f(x, y) = z
20 int left = 1;
21 int right = 1000;
22
23 // Binary search for the target y value
24 while (left < right) {
25 int mid = (left + right) / 2;
26
27 // Since f is increasing with respect to y:
28 // If f(x, mid) >= z, the answer is in the left half (including mid)
29 if (customfunction.f(x, mid) >= z) {
30 right = mid;
31 } else {
32 // Otherwise, the answer is in the right half (excluding mid)
33 left = mid + 1;
34 }
35 }
36
37 // Check if we found an exact match where f(x, left) = z
38 if (customfunction.f(x, left) == z) {
39 // Add the valid pair [x, y] to the result list
40 result.add(Arrays.asList(x, left));
41 }
42 }
43
44 return result;
45 }
46}
47
1/*
2 * // This is the custom function interface.
3 * // You should not implement it, or speculate about its implementation
4 * class CustomFunction {
5 * public:
6 * // Returns f(x, y) for any given positive integers x and y.
7 * // Note that f(x, y) is increasing with respect to both x and y.
8 * // i.e. f(x, y) < f(x + 1, y), f(x, y) < f(x, y + 1)
9 * int f(int x, int y);
10 * };
11 */
12
13class Solution {
14public:
15 vector<vector<int>> findSolution(CustomFunction& customfunction, int z) {
16 vector<vector<int>> result;
17
18 // Iterate through all possible x values from 1 to 1000
19 for (int x = 1; x <= 1000; ++x) {
20 // Binary search for y value where f(x, y) == z
21 // Since f is monotonically increasing with respect to y
22 int left = 1;
23 int right = 1000;
24
25 // Binary search to find the smallest y where f(x, y) >= z
26 while (left < right) {
27 int mid = (left + right) >> 1; // Equivalent to (left + right) / 2
28
29 if (customfunction.f(x, mid) >= z) {
30 // If f(x, mid) >= z, the answer might be at mid or smaller
31 right = mid;
32 } else {
33 // If f(x, mid) < z, we need to search in the right half
34 left = mid + 1;
35 }
36 }
37
38 // Check if we found an exact match where f(x, y) == z
39 if (customfunction.f(x, left) == z) {
40 result.push_back({x, left});
41 }
42 }
43
44 return result;
45 }
46};
47
1/**
2 * // This is the CustomFunction's API interface.
3 * // You should not implement it, or speculate about its implementation
4 * class CustomFunction {
5 * f(x: number, y: number): number {}
6 * }
7 */
8
9/**
10 * Finds all pairs [x, y] where customfunction.f(x, y) equals z
11 * The function f is guaranteed to be monotonically increasing
12 *
13 * @param customfunction - The custom function object with method f(x, y)
14 * @param z - The target value to find
15 * @returns Array of [x, y] pairs where f(x, y) = z
16 */
17function findSolution(customfunction: CustomFunction, z: number): number[][] {
18 // Array to store all valid [x, y] pairs
19 const results: number[][] = [];
20
21 // Iterate through all possible x values from 1 to 1000
22 for (let x = 1; x <= 1000; ++x) {
23 // Binary search for y value where f(x, y) = z
24 let left = 1;
25 let right = 1000;
26
27 // Binary search to find the smallest y where f(x, y) >= z
28 while (left < right) {
29 // Calculate middle point using bit shift for efficiency
30 const mid = (left + right) >> 1;
31
32 // If f(x, mid) >= z, the answer is in the left half (including mid)
33 if (customfunction.f(x, mid) >= z) {
34 right = mid;
35 } else {
36 // Otherwise, the answer is in the right half (excluding mid)
37 left = mid + 1;
38 }
39 }
40
41 // Check if we found an exact match where f(x, left) = z
42 if (customfunction.f(x, left) === z) {
43 results.push([x, left]);
44 }
45 }
46
47 return results;
48}
49
Time and Space Complexity
Time Complexity: O(n log n)
, where n
is the value of z
.
The outer loop iterates from x = 1
to x = z
, which gives us O(n)
iterations. For each value of x
, we perform a binary search using bisect_left
on the range [1, z]
to find the appropriate value of y
. The binary search operation takes O(log n)
time since we're searching through a range of size z
. The key function lambda y: customfunction.f(x, y)
is called O(log n)
times during each binary search. Since we perform this binary search for each of the n
values of x
, the total time complexity is O(n) × O(log n) = O(n log n)
.
Space Complexity: O(1)
(excluding the output array).
The algorithm uses only a constant amount of extra space for variables x
and y
. The bisect_left
function operates on a range object and uses O(1)
additional space for its internal operations. The ans
list stores the output, but this is typically not counted in space complexity analysis as it's required for the return value. Therefore, the auxiliary space complexity is O(1)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Inefficient Search Range for y
Pitfall: The code searches for y
in the range [1, z]
for every x
value, but this can be optimized. Due to the monotonic property, once we find a valid pair [x₁, y₁]
, for the next iteration with x₂ = x₁ + 1
, we know that if a valid y₂
exists, it must satisfy y₂ < y₁
(since f(x₂, y₁) > f(x₁, y₁) = z
).
Solution: Use a two-pointer approach that leverages both monotonic properties simultaneously:
def findSolution(self, customfunction: 'CustomFunction', z: int) -> List[List[int]]:
result = []
x, y = 1, z
while x <= z and y >= 1:
current = customfunction.f(x, y)
if current == z:
result.append([x, y])
x += 1
y -= 1
elif current < z:
x += 1
else:
y -= 1
return result
This reduces time complexity from O(z log z) to O(z).
2. Early Termination Opportunity Missed
Pitfall: The current solution continues searching even when it's impossible to find more solutions. For example, if f(x, 1) > z
, then for all y ≥ 1
, f(x, y) > z
, so we can stop searching entirely.
Solution: Add an early termination check:
for x in range(1, z + 1):
# Early termination: if f(x, 1) > z, no valid y exists for this or larger x
if customfunction.f(x, 1) > z:
break
# Binary search for y...
3. Unnecessary Function Calls in Binary Search
Pitfall: The shown implementation using bisect_left
with a lambda function can result in redundant function calls. After bisect_left
returns, we call f(x, y)
again to check if it equals z
.
Solution: Use a manual binary search that stores the function result:
left, right = 1, z while left <= right: mid = (left + right) // 2 current_value = customfunction.f(x, mid) if current_value == z: result.append([x, mid]) break elif current_value < z: left = mid + 1 else: right = mid - 1
This ensures each f(x, y)
value is computed only once during the search.
How would you design a stack which has a function min
that returns the minimum element in the stack, in addition to push
and pop
? All push
, pop
, min
should have running time O(1)
.
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
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
Want a Structured Path to Master System Design Too? Don’t Miss This!