1237. Find Positive Integer Solution for a Given Equation

Problem Description

In this problem, we are given a special callable function f(x, y) that has a hidden formula. The function f is known to be monotonically increasing in both its arguments x and y. This means f(x, y) is always less than f(x+1, y) and f(x, y+1). Our task is to identify all pairs of positive integers (x, y) such that when the function f is applied to these pairs, the result equals a given target value z. Note that x and y must be positive integers. The final answer can be returned in any order.

Since we don't know the exact formula of f, we cannot directly calculate the values. Therefore, we need to use the properties of the function to systematically find all pairs (x, y) that satisfy f(x, y) == z.


The key to solving this problem lies in understanding two main points:

  • The function f is monotonically increasing which means as we increase x or y, the value of f(x, y) increases.
  • The function f outputs positive integers, and we are looking for positive integer inputs (x, y) as well. This means there will be a distinct and finite number of solutions.

Given these properties, one efficient way to find all pairs (x, y) that satisfy f(x, y) == z is by using a two-pointer technique. We start with the smallest possible value for x (which is 1) and the largest possible value for y within a reasonable range (for example, 1000) and evaluate f(x, y):

  • If f(x, y) < z, it means that with the current pair (x, y), the result is too small. Since f is monotonically increasing and we cannot decrease y further (as we started with the maximum possible value), the only way to increase f(x, y) is by increasing x. Hence, we increment x.
  • If f(x, y) > z, the result is too large. We decrease y since decreasing y is certain to decrease the value of f(x, y).
  • If f(x, y) == z, we found a valid pair and we add it to our answer list. We then increment x and decrement y to find other possible pairs.

The algorithm continues this process until we exhaust all combinations up to the maximum value for both x and y. This approach leverages the function's monotonicity and avoids testing impossible combinations, leading to an efficient solution.

Learn more about Math, Two Pointers and Binary Search patterns.

Not Sure What to Study? Take the 2-min Quiz to Find Your Missing Piece:

Consider the classic dynamic programming of fibonacci numbers, what is the recurrence relation?

Solution Approach

The implementation is based on the two-pointer technique, as mentioned in the intuition. Here's a detailed walkthrough:

  1. Initialize two pointers, x and y. Set x to 1, as we want to start with the smallest positive integer for x, and set y to 1000, assuming that the function f doesn't have x or y values greater than 1000. This is a reasonable assumption for these types of problems unless specified otherwise.

  2. Use a while loop to iterate as long as x is less than or equal to 1000 and y is greater than 0. This ensures we consider all possible combinations of x and y within the given bounds.

  3. Inside the loop, call customfunction.f(x, y) with the current x and y variables to get the output of f.

  4. Check if the value of f(x, y) is less than the target value z. If it is, we need to increase x (since increasing x will increase the value of f(x, y) due to the monotonic nature of the function).

  5. If f(x, y) is greater than z, then our current output is too high, and we need to reduce it. We do this by reducing y, as decreasing y will decrease f(x, y).

  6. If f(x, y) equals z, we've found a valid pair, and we add [x, y] to the list ans. We then move both pointers—incrementing x and decrementing y—to continue searching for other possible solutions.

  7. The loop exits once we either exceed the maximum value for x or y reaches 0. At this point, we would have tested all reasonable x and y combinations.

  8. Return the list ans containing all pairs [x, y] that satisfy f(x, y) == z.

The algorithm effectively navigates the search space using the two-pointer technique, which exploits the monotonically increasing property of the function f. In terms of data structures, the implementation uses a list ans to store the pairs that fulfill the condition. No advanced data structures are needed since the problem is tackled primarily with algorithmic logic.

This approach is quite efficient, as it avoids unnecessary calculations for x and y pairs that obviously do not meet the condition. By carefully increasing and decreasing x and y, it homes in on the correct pairs without checking every possible combination.

Note: The approach described as "Binary search" in the provided reference solution approach appears to be a mislabel, as the actual implemented technique is, in fact, the two-pointer technique. Binary search would involve repeatedly halving the search space to find a solution, which isn't the case here.

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

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).

Example Walkthrough

Let's illustrate the solution approach with an example. Suppose we have the special function f(x, y) and our target value z is 14. We don't know the hidden formula within f, but we are given that f is monotonically increasing in both x and y, and we are tasked to find all positive integer pairs (x, y) that satisfy the equation f(x, y) == z.

Following our solution approach:

  1. We initialize x as 1 and y as 1000 (assuming f's range is within these bounds).

  2. We enter our while loop and start evaluating f(x, y) to see if it matches z.

  3. In our first iteration, we call f(1, 1000). If we find that f(1, 1000) < 14, we can conclude that we need to increase x because increasing x would increase the output of f. Hence, we move to x = 2.

  4. We call f(2, 1000) and suppose we find f(2, 1000) > 14. The result is too high, meaning y must be decreased. Let's say we choose y = 999.

  5. We call f(2, 999) and find that f(2, 999) == 14. We've found a matching pair (2, 999), so we add it to our answer list.

  6. We continue iterating, incrementing x to 3 and decrementing y to 998, and we call f(3, 998). If f(3, 998) < 14, we must increase x. If f(3, 998) > 14, we must decrease y. If f(3, 998) == 14, we've found another valid pair.

  7. The process repeats, traversing various (x, y) pairs and responding accordingly, until x > 1000 or y <= 0.

  8. We finish iterating and return the list ans that contains all our valid pairs [x, y].

Through this example, it becomes clear how the two-pointer technique effectively pinpoints the valid (x, y) combinations without redundant checks, by taking advantage of f's monotonicity. As such, assuming our y never needed to decrease below 995 and our x never rose above 10 before we finished, we might end up with a list ans that could look something like [ [2, 999], [4, 997], [8, 995] ]. The solution is efficient because it avoids unnecessarily checking combinations where f(x, y) would be guaranteed to miss the target z.

(Note: The actual results of f(2, 1000), f(2, 999), and f(3, 998) are arbitrary for the purposes of this example since the function f is unknown and merely illustrative to demonstrate the search process.)

Not Sure What to Study? Take the 2-min Quiz:

Is the following code DFS or BFS?

1void search(Node root) {
2  if (!root) return;
3  visit(root);
4  root.visited = true;
5  for (Node node in root.adjacent) {
6    if (!node.visited) {
7      search(node);
8    }
9  }

Python Solution

1# Define the interface for the CustomFunction, don't implement it.
2class CustomFunction:
3    # The CustomFunction interface includes a method f(x, y) that takes two integers.
4    # It is known that the function is increasing with respect to both x and y,
5    # which means f(x, y) < f(x + 1, y) and f(x, y) < f(x, y + 1).
6    def f(self, x: int, y: int) -> int:
7        pass
9class Solution:
10    def findSolution(self, custom_function: CustomFunction, target: int) -> List[List[int]]:
11        # Initialize an empty list to store the solutions.
12        solutions = []
14        # Start with the smallest possible value of x and the largest possible value of y.
15        x, y = 1, 1000
17        # Perform a 2-pointer approach from both ends of the possible values.
18        # Since the function is monotonic (increasing in both variables),
19        # We can increment x if the current function value is smaller than the target,
20        # and decrement y if the current function value is greater than the target.
21        while x <= 1000 and y > 0:
22            current_val = custom_function.f(x, y)  # Compute the current value.
23            if current_val < target:
24                # If the current value is less than the target, increase x.
25                x += 1
26            elif current_val > target:
27                # If the current value is greater than the target, decrease y.
28                y -= 1
29            else:
30                # If the current value is equal to the target, we found a valid (x, y) pair.
31                solutions.append([x, y])
33                # Proceed to the next potential solutions.
34                x += 1
35                y -= 1
37        # Return the list of valid (x, y) pairs that match the target value when plugged into custom_function.
38        return solutions

Java Solution

1// Import required package for using List and ArrayList
2import java.util.List;
3import java.util.ArrayList;
4import java.util.Arrays;
6// Definition for a point (x, y) in the custom function
7// Not to be implemented, just for reference
8class CustomFunction {
9    // Returns f(x, y) for any given positive integers x and y.
10    // Note that f(x, y) is increasing with respect to both x and y.
11    public int f(int x, int y) {
12        // Implementation provided by system
13        return -1; // Placeholder for the compiler
14    }
17public class Solution {
18    public List<List<Integer>> findSolution(CustomFunction customFunction, int target) {
19        // Initialize a list to store pairs (x, y) that meet the condition f(x, y) == z
20        List<List<Integer>> solutions = new ArrayList<>();
22        // Set initial values for x and y to start at opposite ends of the range [1, 1000]
23        int x = 1;
24        int y = 1000;
26        // Use a two-pointer approach to find all solution pairs
27        while (x <= 1000 && y > 0) {
28            // Evaluate the custom function for the current pair (x, y)
29            int result = customFunction.f(x, y);
30            if (result < target) {
31                // If the result is less than target, increment x to increase the result
32                x++;
33            } else if (result > target) {
34                // If the result is greater than target, decrement y to decrease the result
35                y--;
36            } else {
37                // If the result is equal to the target, add the pair (x, y) to the solutions list
38                solutions.add(Arrays.asList(x, y));
39                // Move both pointers to get closer to the next potential solution
40                x++;
41                y--;
42            }
43        }
44        return solutions;
45    }

C++ Solution

1class Solution {
3    // Finds all the pairs (x, y) where the result of customfunction.f(x, y) equals z
4    vector<vector<int>> findSolution(CustomFunction& customFunction, int targetValue) {
5        vector<vector<int>> solutions; // To store all the solution pairs
6        int x = 1;            // Start with the smallest possible value of x
7        int y = 1000;         // Start with the largest possible value of y due to the property of the custom function
9        // Loop until x is less than or equal to 1000 and y is positive
10        while (x <= 1000 && y > 0) {
11            int result = customFunction.f(x, y); // Compute the custom function result
13            // Compare the result with the target value
14            if (result < targetValue) {
15                x++; // If the result is less than the target, increment x to increase result
16            } else if (result > targetValue) {
17                y--; // If the result is greater than the target, decrement y to decrease result
18            } else { // If the result is equal to targetValue
19                solutions.push_back({x, y}); // Add the current pair (x, y) to the solutions
20                x++; // Increment x and decrement y to avoid repeating a solution
21                y--;
22            }
23        }
25        return solutions; // Return all solution pairs
26    }

Typescript Solution

1// The CustomFunction API is predefined.
2// You should not implement or assume the implementation of this class/interface.
3declare class CustomFunction {
4    // Method f should be defined in CustomFunction and it accepts two numbers x and y, and returns a number.
5    f(x: number, y: number): number;
9 * Finds all positive integer solutions to the equation customfunction.f(x, y) = z.
10 * Constraints: 1 <= x, y <= 1000
11 * Note: The CustomFunction API is used to determine the result for the current x and y values.
12 * 
13 * @param customfunction - An instance of CustomFunction that contains the definition for f(x, y).
14 * @param z - The result for which solutions are being searched.
15 * @returns An array of integer pairs (arrays) [x, y] where f(x, y) = z.
16 */
17function findSolution(customfunction: CustomFunction, z: number): number[][] {
18    let x = 1; // Initialize x to start from 1.
19    let y = 1000; // Initialize y to start from 1000.
20    const solutions: number[][] = []; // Array to store the pairs [x, y] satisfying the equation.
22    // Iterate as long as x and y remain within their respective bounds.
23    while (x <= 1000 && y >= 1) {
24        const result = customfunction.f(x, y); // Evaluate f(x, y) using the custom function.
26        if (result < z) {
27            ++x; // If the result is less than z, increment x to increase the result.
28        } else if (result > z) {
29            --y; // If the result is greater than z, decrement y to decrease the result.
30        } else {
31            // If the result is equal to z, we have found a valid solution.
32            solutions.push([x, y]); // Add the solution [x, y] to the solutions array.
33            x++; // Increment x to search for the next potential solution.
34            y--; // Decrement y to search for the next potential solution.
35        }
36    }
37    return solutions; // Return the array of solutions.
Fast Track Your Learning with Our Quick Skills Quiz:

What are the two properties the problem needs to have for dynamic programming to be applicable? (Select 2)

Time and Space Complexity

Time Complexity

The given code has a while loop that iterates at most min(1000, z) times because:

  • The maximum possible value for x and y is 1000 (as per the constraints).
  • For every iteration, either x is incremented or y is decremented.
  • The loop stops when x exceeds 1000 or y reaches below 1.

Assuming that the custom function f(x, y) is O(1) (since we don't have information about its internal implementation, we consider the worst-case scenario where it takes a constant time), the time complexity of the entire operation is O(min(1000, z)).

Space Complexity

The space complexity mainly depends on the number of valid (x, y) pairs that satisfy the equation f(x, y) = z. In the worst case, all pairs (x, y) where 1 <= x, y <= 1000 could be solutions, so there could be as many as 1000 solutions.

However, since the space used by ans depends on the output, which we do not count towards the space complexity in the analysis, the additional space used by the algorithm (i.e., for variables x, y, t) is constant.

Therefore, the space complexity of the code is O(1).

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

Recommended Readings

Got a question? Ask the Teaching Assistant anything you don't understand.

Still not clear? Ask in the Forum,  Discord or Submit the part you don't understand to our editors.

TA 👨‍🏫