2961. Double Modular Exponentiation
Problem Description
In this problem, you are given a two-dimensional array named variables
where each element is a list of four integers [a_i, b_i, c_i, m_i]
. In addition, you are provided with an integer target
. An index i
in the variables
array is considered good if it satisfies a certain mathematical condition based on the values of a_i
, b_i
, c_i
, and m_i
. The condition is that the expression ((a_i^b_i % 10)^c_i) % m_i
must be equal to the target
. Here the ^
represents exponentiation, and %
is the modulo operation.
The task is to identify all the indices that are good and return them as an array in any order. An index is good if the above formula matches the target when evaluated with the values at that index in the variables array. You need to consider each index i
from 0
to variables.length - 1
and check the formula for each.
Intuition
The intuition behind the solution lies in understanding how modulo arithmetic can be used to simplify calculations and the properties of exponents. Since the final expression involves taking a_i
to the power of b_i
, then to the power of c_i
, and applying modulo m_i
to check if it equals the target
, we can take advantage of the following properties:
-
Exponentiation by Squaring (Fast Power Method): This method allows us to compute
a^b
efficiently by squaring and reducing the number of multiplications we need to perform. This is especially useful whenb
is very large. -
Modulo Properties: We can use the property
(a * b) % m = ((a % m) * (b % m)) % m
, which states that the result of a product modulom
is the same regardless of whether we perform the modulo before or after multiplication. We can also apply this principle to exponentiation.
By applying these optimizations, we can effectively reduce the calculations needed to verify whether an index i
satisfies the condition. Hence, the process for each index i
is to apply the modulo operation as soon as possible during the exponentiation steps to keep the numbers manageable and prevent overflow. This is done by calculating a^b % 10
and then ((a^b % 10)^c) % m_i
.
The solution applies the Python pow
function, which takes an additional modulo argument, making these operations straightforward and efficient. By doing so, we loop through each variables[i]
and apply the property (a^b % 10)^c % m_i
and compare it to the target
. If it matches, we add the index i
to our output list. This approach is both clean and efficient, allowing us to handle even large powers effectively.
Learn more about Math patterns.
Solution Approach
The implementation of the solution uses a relatively simple approach aligned with the properties of modulo arithmetic and exponentiation. Here's how it works:
-
Iteration: We iterate through each index
i
of the inputvariables
array using a for-loop. -
Exponentiation: For every tuple
(a, b, c, m)
at indexi
, we need to calculate the expression((a^b % 10)^c) % m
and check if it equals thetarget
. -
Exponentiation with Modulo: The Python
pow
function is perfect for this task because it allows us to efficiently calculate powers with a modulo. The regular power operationa^b
may lead to very large numbers, especially whenb
andc
are large, which is computationally expensive and can cause integer overflows. However, by using thepow
function with a modulo, we can calculatea^b % 10
much more quickly and safely.- The first power operation is
pow(a, b, 10)
which computesa^b % 10
. We modulo by10
because exponentiation modulo10
gives us the last digit ofa^b
, which is all we need for the next power. - The resulting number then needs to be exponentiated again with
c
and taken modulom
. This is continued with anotherpow
operation -pow(result, c, m)
whereresult
isa^b % 10
.
- The first power operation is
-
Check Against Target: The final result of the nested
pow
function is compared to thetarget
. If they match, the indexi
is considered good. -
Building the Output List: The indices where the condition holds true are gathered into a list using a list comprehension. This list is returned as the final output.
The process uses a direct simulation of the problem's formula. The fast power method (also known as exponentiation by squaring) is implicitly applied through Python's built-in pow
function with mod argument, as this is a known efficient way to compute powers in modular arithmetic.
Here's a breakdown of the algorithm based on the solution code:
class Solution:
def getGoodIndices(self, variables: List[List[int]], target: int) -> List[int]:
return [
i
for i, (a, b, c, m) in enumerate(variables)
if pow(pow(a, b, 10), c, m) == target
]
In the provided code, enumerate(variables)
gives us both the index i
and the list [a, b, c, m]
. For each index and list, if pow(pow(a, b, 10), c, m) == target
evaluates the condition. If the condition is true, the i
is included in the output list. This is an elegant and effective way to solve the problem with minimal code and high readability.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's work through a small example to illustrate the solution approach.
Assume we have the following variables
array and the target
value:
variables = [ [2, 3, 1, 4], # Index 0 [3, 2, 2, 3], # Index 1 [2, 5, 2, 1], # Index 2 ] target = 1
We need to find all indices where the condition ((a_i^b_i % 10)^c_i) % m_i == target
holds true.
Step 1: We start by iterating over each index in the variables
array.
For index 0: The tuple is [2, 3, 1, 4]
, representing a=2, b=3, c=1, m=4
.
- We calculate
2^3 % 10
, which is8 % 10
giving us8
. - Next, we calculate
(8^1) % 4
, which is simply8 % 4
giving us0
. - The result is
0
, which does not match thetarget
of1
. Hence, index0
is not good.
For index 1: The tuple is [3, 2, 2, 3]
, representing a=3, b=2, c=2, m=3
.
- We calculate
3^2 % 10
, which is9 % 10
giving us9
. - Next, we calculate
(9^2) % 3
, which is81 % 3
giving us0
. - The result is
0
, which does not match thetarget
of1
. Hence, index1
is not good.
For index 2: The tuple is [2, 5, 2, 1]
, representing a=2, b=5, c=2, m=1
.
- We calculate
2^5 % 10
, which is32 % 10
giving us2
. - Next, we calculate
(2^2) % 1
, which is4 % 1
giving us0
. - However, since any number modulo
1
is0
, this does not match ourtarget
of1
. Hence, index2
is not good.
At the end of the iteration, unfortunately, no indices satisfy the condition for our small example, and therefore the resulting array of good indices would be empty - []
.
The solution code using this approach would be:
class Solution:
def getGoodIndices(self, variables: List[List[int]], target: int) -> List[int]:
return [
i
for i, (a, b, c, m) in enumerate(variables)
if pow(pow(a, b, 10), c, m) == target
]
The code works efficiently through the properties of exponents and modulo arithmetic, using Python's pow
function to simplify the computations and avoid overflow, while checking each index against the target.
Solution Implementation
1from typing import List
2
3class Solution:
4 def get_good_indices(self, variables: List[List[int]], target: int) -> List[int]:
5 # Iterate over the list enumerating the variables
6 # where 'i' is the index and 'variable' is a list [a, b, c, m]
7 return [
8 i
9 for i, variable in enumerate(variables)
10 # Check if the complex power expression matches the target
11 # a raised to the power of b mod 10, raised to the power of c mod m should equal target
12 if pow(pow(variable[0], variable[1], 10), variable[2], variable[3]) == target
13 ]
14
15# Example usage:
16# solution = Solution()
17# result = solution.get_good_indices([[2, 3, 1, 10], [3, 3, 2, 12]], 8)
18# print(result) # Output: list of indices that match the criteria
19
1class Solution {
2 // Method to find all the indices in 'variables' array where the recursive power operation result equals 'target'.
3 public List<Integer> getGoodIndices(int[][] variables, int target) {
4 List<Integer> goodIndices = new ArrayList<>(); // List to hold the indices that meet the criteria.
5 // Iterate over each variable array.
6 for (int i = 0; i < variables.length; ++i) {
7 int[] variableSet = variables[i];
8 // Extract individual variables from the current set.
9 int a = variableSet[0];
10 int b = variableSet[1];
11 int c = variableSet[2];
12 int m = variableSet[3];
13 // If the recursive power operation result equals 'target', add the index to the list.
14 if (quickPow(quickPow(a, b, 10), c, m) == target) {
15 goodIndices.add(i);
16 }
17 }
18 return goodIndices; // Return the list of indices.
19 }
20
21 // Method to compute (a^b) % mod quickly using binary exponentiation.
22 private int quickPow(long a, int n, int mod) {
23 long result = 1; // Initialize result to 1.
24 // Loop until n is zero.
25 for (; n > 0; n >>= 1) {
26 // If the current bit in n is set, multiply result by 'a' and take mod.
27 if ((n & 1) == 1) {
28 result = (result * a) % mod;
29 }
30 // Square 'a' and take mod to use for the next iteration.
31 a = (a * a) % mod;
32 }
33 return (int) result; // Cast the long result back to int and return.
34 }
35}
36
1#include <vector>
2
3class Solution {
4public:
5 // Function to find all the indices of variables that meet a certain condition.
6 std::vector<int> getGoodIndices(std::vector<std::vector<int>>& variables, int target) {
7 std::vector<int> goodIndices; // This will store the indices that meet the condition
8
9 // Lambda function for fast exponentiation under modulo (to avoid integer overflow)
10 auto fastPowerModulo = [&](long long base, int exponent, int modulo) -> int {
11 long long result = 1; // Start with a result of 1
12
13 // Continuously square base and multiply by base when the exponent's current bit is 1
14 while (exponent > 0) {
15 if (exponent & 1) { // Check if the current bit is 1
16 result = (result * base) % modulo; // Multiply by base and take modulo
17 }
18 base = (base * base) % modulo; // Square the base and take modulo
19 exponent >>= 1; // Shift exponent right by 1 bit (divide by 2)
20 }
21
22 return static_cast<int>(result); // Cast result to int before returning
23 };
24
25 // Iterate over all the variable sets
26 for (int i = 0; i < variables.size(); ++i) {
27 auto& variableSet = variables[i]; // Reference for better performance and readability
28 int a = variableSet[0]; // Base part of the power
29 int b = variableSet[1]; // Exponent part
30 int c = variableSet[2]; // Exponent for the result of a^b
31 int modulo = variableSet[3]; // The modulo value
32
33 // Apply the fast power modulo function twice as specified by the problem
34 // and compare with the target value
35 if (fastPowerModulo(fastPowerModulo(a, b, 10), c, modulo) == target) {
36 goodIndices.push_back(i); // If condition is met, add index to the list
37 }
38 }
39
40 return goodIndices; // Return the list of good indices
41 }
42};
43
1// Function to perform quick exponentiation by squaring, modulo a given modulus 'mod'.
2const quickPowerMod = (base: number, exponent: number, modulus: number): number => {
3 let answer = 1;
4 while(exponent > 0) {
5 if(exponent & 1) {
6 answer = Number((BigInt(answer) * BigInt(base)) % BigInt(modulus));
7 }
8 base = Number((BigInt(base) * BigInt(base)) % BigInt(modulus));
9 exponent >>= 1;
10 }
11 return answer;
12};
13
14// Function to find and return the indices of the 'variables' array where the double exponentiation
15// result matches the target after applying the modulus 'm' in each inner calculation.
16function getGoodIndices(variables: number[][], target: number): number[] {
17 const resultIndices: number[] = [];
18
19 for(let index = 0; index < variables.length; ++index) {
20 // Destructuring each 'variables' element into separate constants for clarity.
21 const [base, firstExponent, secondExponent, modulus] = variables[index];
22
23 // Compute the nested power-mod operation: ((a^b)^c) % m.
24 const doubleExponentiationResult = quickPowerMod(
25 quickPowerMod(base, firstExponent, 10),
26 secondExponent,
27 modulus
28 );
29
30 // If the result matches the target, add the current index to the 'resultIndices' array.
31 if(doubleExponentiationResult === target) {
32 resultIndices.push(index);
33 }
34 }
35
36 // Return the array with the good indices.
37 return resultIndices;
38}
39
Time and Space Complexity
The given Python code in the Solution
class contains a method called getGoodIndices
which computes good indices based on the power operation condition.
The time complexity of this code is primarily dependent on the enumerate
function that iterates through the variables
list and the pow
function used inside the list comprehension. The pow
function in Python has a time complexity of O(log n)
, where n
is the exponent.
Here, the pow
function is used twice; the first call executes pow(a, b, 10)
, and the second call executes pow(result_of_first_call, c, m)
. Since b
and c
are the exponents used in each call, the time complexity for each iteration is controlled by these values.
Considering M
as the maximum value among all values of b_i
and c_i
, and with M
being limited to 10^3
in the problem, the time complexity per iteration is O(log M)
. Since there are n
elements in variables
, where n
is the length of variables
, the total time complexity is O(n * log M)
.
The space complexity of the code is O(1)
because the extra space required is constant and does not depend on the input size. There are no additional data structures that grow with the size of variables
. The list comprehension simply produces the final list of indices, which the function returns and does not count towards extra space as it is the required output.
Learn more about how to find time and space complexity quickly using problem constraints.
Which algorithm is best for finding the shortest distance between two points in an unweighted graph?
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
LeetCode 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 we
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 algomonster s3 us east 2 amazonaws com recursion jpg You first
Want a Structured Path to Master System Design Too? Don’t Miss This!