top

Search

Software Key Tutorial

.

UpGrad

Software Key Tutorial

Maximum Product Subarray

Introduction

Learning about arrays is an important part of studying computer science. It's a simple data structure that forms the basis for more complex coding problems. One such coding problem is finding the maximum product subarray. This is all about finding a continuous subarray within a given array that has the greatest product.

You may ask, Why do we care about this? Well, it's not just an abstract concept. This problem can help in various situations in real life. Like optimizing product sales based on a set of different factors or predicting stock prices in financial markets.

This comprehensive guide will break down the problems for you. We will discuss what the problem is, why it matters, and the solution. You will have a strong understanding of this complex problem by the end of this tutorial. You will also be ready to apply your knowledge to your coding challenges.

Overview

This maximum product subarray guide covers many aspects of the product subarray problem. We will explore dynamic programming methods for solving complex problems with a clear maximum product subarray example to ensure you understand the concept better.

You will also get to know Kadane's algorithm. This dynamic algorithm can solve many complex problems, like the maximum product subarray. We will discuss how to implement this algorithm in Python. Let’s get started with an example!

Maximum Product Subarray Example

Consider the array [-2, -3, 0, -2, -40]. You can draw a line of 5 squares on a piece of paper to visualize this array. Label each square with the corresponding number from the array.

Using the dynamic programming approach to solve the problem.

  1. Initialize the maximum product as the first element, which is -2.

  1. Continue to the second element, -3. The maximum product could either be -3 itself or -2 multiplied by -3, which is 6. So, we choose 6.

  1. When we reach the third element, 0, the maximum product can either be 0 or 6 multiplied by 0. Ultimately, 0 is the greater option.

  1. Moving onto the fourth element, -2, the maximum product can either be -2 or 0 multiplied by -2. We choose -2.

  1. When we reach -40, the maximum product is either -40 or -2 multiplied by -40, which is 80./

Our maximum product subarray is thus [-2, -40] with a product of 80.

We can discuss the maximum product subarray on HackerRank. It is a platform that millions of people use to refine their coding skills. You will be well-equipped to face challenges and ace problems like this on platforms like HackerRank as we go through this guide. 

Maximum Product Subarray Dynamic Programming

Dynamic programming is a method for solving complex problems by breaking them down into simpler ones.

We keep track of the maximum product we can get at each position in the array. We will use a practical example to make this easier.

  1. We have an array [2, -3, -4].

  2. The maximum subarray in this case is the entire array itself.

  3. The product of all numbers is 24.

  4. 24 is the highest possible product.

We start at the first position to solve this with dynamic programming. The maximum product at the first position (2) is the number itself.

We move to the second position (-3). The maximum product can either be -3 or the product of 2 and -3 which is -6. We choose -3 as we are looking for the maximum product. 

The maximum product can be one of three values in the third position (-4).

-4 is the product of -3 and -4 which is 12, or the product of 2, -3, and -4, which is 24. So, we chose 24.

The maximum product we have found at the end is 24. Therefore, that's our answer. This approach makes it easier to solve the maximum product subarray problem for larger arrays.

Maximum Product Subarray Kadane

Kadane's algorithm is an efficient solution for the maximum subarray problem. The problem requires a small change since, when multiplying two negative numbers, the product is a positive number. 

For example, (-3)×(−3)=9.

We will keep track of the maximum and minimum product endings at each position to solve the maximum subarray issue using Kadane's algorithm.

Take an array [2, 3, -2, 4].

1. Initialize max product (max_prod) and min product (min_prod) as the first elements. It is 2 in this case. The answer (max_so_far) is 2. 

2. Move on to the next element. Max_prod is the maximum of 3, 3 * max_prod (which is 6), and 3 * min_prod (which is also 6). The new max_prod is 6. Update min_prod and make it the minimum of the same three values. So min_prod is 3. Max_so_far is updated to 6 since it's greater than the current max_so_far.

3. Follow the same steps for the third element. The new max_prod is 6 (the maximum of -2, -2 * max_prod = -12, and -2 * min_prod = -6), the new min_prod is -12, and max_so_far remains 6.

  1. Max_prod becomes 24 for the fourth element. Min_prod becomes -48 and max_so_far becomes 24.

Our maximum product subarray is [2, 3, -2, 4] with a product of 24.

Maximum Product Subarray Python

Here's a Python function that solves the maximum product subarray problem using the method we discussed earlier. It is a modification of Kadane's algorithm. This function takes a list of integers as input and returns the maximum product of a subarray.

Code-

def max_product_subarray(nums):
    if not nums:
        return 0

    max_prod = min_prod = max_so_far = nums[0]

    for num in nums[1:]:
        if num < 0:
            max_prod, min_prod = min_prod, max_prod
        max_prod = max(num, max_prod * num)
        min_prod = min(num, min_prod * num)

        max_so_far = max(max_so_far, max_prod)

    return max_so_far

# Test the function with a list of numbers
nums = [2, 3, -2, 4]
print(max_product_subarray(nums))  # Output: 24

The function calculates the maximum product of a subarray for the list [2, 3, -2, 4] in this example. The output here is 24. It matches the result we calculated before.

Simple Brute Force Approach

A simple brute force approach will calculate the product of every possible subarray and then return the maximum product. This approach takes a lot of time for larger arrays (O(n^2) time complexity), where n is the size of the array.

Below are implementations of this approach in C++, Java, and Python-

 C++

Code-

#include<bits/stdc++.h>
using namespace std;

int maxProductSubarray(vector<int>& nums) {
    int n = nums.size();
    int max_product = INT_MIN;
    for(int i = 0; i < n; i++){
        int product = 1;
        for(int j = i; j < n; j++){
            product *= nums[j];
            max_product = max(max_product, product);
        }
    }
    return max_product;
}

int main(){
    vector<int> nums = {2, 3, -2, 4};
    cout << maxProductSubarray(nums);  // Output: 24
    return 0;
}

Java-

Code-

public class Main {
    public static int maxProductSubarray(int[] nums) {
        int n = nums.length;
        int max_product = Integer.MIN_VALUE;
        for(int i = 0; i < n; i++){
            int product = 1;
            for(int j = i; j < n; j++){
                product *= nums[j];
                max_product = Math.max(max_product, product);
            }
        }
        return max_product;
    }
    public static void main(String[] args) {
        int[] nums = {2, 3, -2, 4};
        System.out.println(maxProductSubarray(nums));  // Output: 24
    }
}

Output-

Python

Code-

def max_product_subarray(nums):
    n = len(nums)
    max_product = float('-inf')
    for i in range(n):
        product = 1
        for j in range(i, n):
            product *= nums[j]
            max_product = max(max_product, product)
    return max_product

nums = [2, 3, -2, 4]
print(max_product_subarray(nums))  # Output: 24

Note-

Each of these code examples calculates the maximum product of a subarray for the array [2, 3, -2, 4]. The output is 24 in all cases. Also, this brute force approach is not an efficient solution for larger arrays, but it can be useful for understanding the problem.

Optimized Brute Force Approach

In an optimized brute force approach, we can calculate the product of all numbers in an array by dividing the product of all numbers in the array by each number. This allows us to calculate the product of all numbers in the array once and then calculate each subarray product by dividing the total product by each number not in the subarray. You should also know that this approach can fail when zero (0) is involved.

Note:

The true optimized approach for this problem is the one that uses dynamic programming, or Kadane's algorithm. They run at O(n) time complexity.

Here's the optimized brute force approach in C++, Java, and Python-

C++

Code-

#include<bits/stdc++.h>
using namespace std;

int maxProductSubarray(vector<int>& nums) {
    int n = nums.size();
    int total_product = 1;
    for(int num : nums){
        total_product *= num;
    }

    int max_product = total_product;
    for(int i = 0; i < n; i++){
        int product = total_product;
        for(int j = i; j < n; j++){
            product /= nums[j];
            max_product = max(max_product, product);
        }
    }
    return max_product;
}

int main(){
    vector<int> nums = {2, 3, -2, 4};
    cout << maxProductSubarray(nums);  // Output: 24
    return 0;
}

Output-

Python

Code-

def max_product_subarray(nums):
    n = len(nums)
    total_product = 1
    for num in nums:
        total_product *= num

    max_product = total_product
    for i in range(n):
        product = total_product
        for j in range(i, n):
            product //= nums[j]
            max_product = max(max_product, product)
    return max_product

nums = [2, 3, -2, 4]
print(max_product_subarray(nums))  # Output: 24

Java

Code-

public class Main {
    public static int maxProductSubarray(int[] nums) {
        int n = nums.length;
        int total_product = 1;
        for(int num : nums){
            total_product *= num;
        }

        int max_product = total_product;
        for(int i = 0; i < n; i++){
            int product = total_product;
            for(int j = i; j < n; j++){
                product /= nums[j];
                max_product = Math.max(max_product, product);
            }
        }
        return max_product;
    }
    public static void main(String[] args) {
        int[] nums = {2, 3, -2, 4};
        System.out.println(maxProductSubarray(nums));  // Output: 24
    }
}

Efficient Dynamic Programming Approach

In the dynamic programming approach, we maintain two variables, max_prod and min_prod. It stores the maximum and minimum products of the subarray ending at the current position. These variables are updated at each position based on the current number and the max_prod and min_prod at the previous position.

Here are the implementations of the dynamic programming approach in C++, Java, and Python-

C++

Code-

#include<bits/stdc++.h>
using namespace std;

int maxProductSubarray(vector<int>& nums) {
    int n = nums.size();
    int max_prod = nums[0];
    int min_prod = nums[0];
    int max_so_far = nums[0];

    for(int i = 1; i < n; i++){
        if(nums[i] < 0)
            swap(max_prod, min_prod);

        max_prod = max(nums[i], max_prod * nums[i]);
        min_prod = min(nums[i], min_prod * nums[i]);

        max_so_far = max(max_so_far, max_prod);
    }
    return max_so_far;
}

int main(){
    vector<int> nums = {2, 3, -2, 4};
    cout << maxProductSubarray(nums);  // Output: 24
    return 0;
}

Java

Code-

public class Main {
    public static int maxProductSubarray(int[] nums) {
        int n = nums.length;
        int max_prod = nums[0];
        int min_prod = nums[0];
        int max_so_far = nums[0];

        for(int i = 1; i < n; i++){
            int temp_max = max_prod;
            max_prod = Math.max(nums[i], Math.max(max_prod * nums[i], min_prod * nums[i]));
            min_prod = Math.min(nums[i], Math.min(temp_max * nums[i], min_prod * nums[i]));

            max_so_far = Math.max(max_so_far, max_prod);
        }
        return max_so_far;
    }
    public static void main(String[] args) {
        int[] nums = {2, 3, -2, 4};
        System.out.println(maxProductSubarray(nums));  // Output: 24
    }
}

Python

Code-

def max_product_subarray(nums):
    max_prod = min_prod = max_so_far = nums[0]

    for num in nums[1:]:
        if num < 0:
            max_prod, min_prod = min_prod, max_prod
        max_prod = max(num, max_prod * num)
        min_prod = min(num, min_prod * num)

        max_so_far = max(max_so_far, max_prod)
        
    return max_so_far

nums = [2, 3, -2, 4]
print(max_product_subarray(nums))  # Output: 24

Note:

These code examples calculate the maximum product of a subarray for the array [2, 3, -2, 4]. The output is 24 in each case. This dynamic programming approach provides a solution with O(n) time complexity, where n is the length of the array.

Two-Traversal Approach

The two-traversal approach for the maximum subarray problem involves going through the array twice.

  1. We start from the beginning of the array

  2. We calculate the product until the current index

  3. We start from the end of the array and again calculate the product until the current index

The maximum product obtained during both of these traversals is the answer.

Here are the implementations in C++, Java, and Python-

 C++

Code-

#include<bits/stdc++.h>
using namespace std;

int maxProductSubarray(vector<int>& nums) {
    int n = nums.size();
    int prod = 1;
    int max_product = INT_MIN;

    for(int i = 0; i < n; i++){
        prod *= nums[i];
        max_product = max(max_product, prod);
        if(prod == 0)
            prod = 1;
    }

    prod = 1;
    for(int i = n - 1; i >= 0; i--){
        prod *= nums[i];
        max_product = max(max_product, prod);
        if(prod == 0)
            prod = 1;
    }

    return max_product;
}

int main(){
    vector<int> nums = {2, 3, -2, 4};
    cout << maxProductSubarray(nums);  // Output: 24
    return 0;
}

Java

Code-

public class Main {
    public static int maxProductSubarray(int[] nums) {
        int n = nums.length;
        int prod = 1;
        int max_product = Integer.MIN_VALUE;

        for(int i = 0; i < n; i++){
            prod *= nums[i];
            max_product = Math.max(max_product, prod);
            if(prod == 0)
                prod = 1;
        }

        prod = 1;
        for(int i = n - 1; i >= 0; i--){
            prod *= nums[i];
            max_product = Math.max(max_product, prod);
            if(prod == 0)
                prod = 1;
        }

        return max_product;
    }
    public static void main(String[] args) {
        int[] nums = {2, 3, -2, 4};
        System.out.println(maxProductSubarray(nums));  // Output: 24
    }
}

Python

Code-

def max_product_subarray(nums):
    n = len(nums)
    prod = 1
    max_product = float('-inf')

    for num in nums:
        prod *= num
        max_product = max(max_product, prod)
        if prod == 0:
            prod = 1

    prod = 1
    for num in reversed(nums):
        prod *= num
        max_product = max(max_product, prod)
        if prod == 0:
            prod = 1

    return max_product

nums = [2, 3, -2, 4]
print(max_product_subarray(nums))  # Output: 24

Kadane’s Algorithm: 

Here is the implementation of Kadane’s Algorithm in C++, Java, and Python.

C++

Code-

#include<bits/stdc++.h>
using namespace std;

int maxProductSubarray(vector<int>& nums) {
    int n = nums.size();
    int max_prod = nums[0];
    int min_prod = nums[0];
    int max_so_far = nums[0];

    for(int i = 1; i < n; i++){
        if(nums[i] < 0)
            swap(max_prod, min_prod);

        max_prod = max(nums[i], max_prod * nums[i]);
        min_prod = min(nums[i], min_prod * nums[i]);

        max_so_far = max(max_so_far, max_prod);
    }
    return max_so_far;
}

int main(){
    vector<int> nums = {2, 3, -2, 4};
    cout << maxProductSubarray(nums);  // Output: 24
    return 0;
}

Java

Code-

public class Main {
    public static int maxProductSubarray(int[] nums) {
        int n = nums.length;
        int max_prod = nums[0];
        int min_prod = nums[0];
        int max_so_far = nums[0];

        for(int i = 1; i < n; i++){
            int temp_max = max_prod;
            max_prod = Math.max(nums[i], Math.max(max_prod * nums[i], min_prod * nums[i]));
            min_prod = Math.min(nums[i], Math.min(temp_max * nums[i], min_prod * nums[i]));

            max_so_far = Math.max(max_so_far, max_prod);
        }
        return max_so_far;
    }
    public static void main(String[] args) {
        int[] nums = {2, 3, -2, 4};
        System.out.println(maxProductSubarray(nums));  // Output: 24
    }
}

Python

Code-

def max_product_subarray(nums):
    max_prod = min_prod = max_so_far = nums[0]

    for num in nums[1:]:
        if num < 0:
            max_prod, min_prod = min_prod, max_prod
        max_prod = max(num, max_prod * num)
        min_prod = min(num, min_prod * num)

        max_so_far = max(max_so_far, max_prod)
        
    return max_so_far

nums = [2, 3, -2, 4]
print(max_product_subarray(nums))  # Output: 24

Note-

Each of these code examples calculates the maximum product of a subarray for the array [2, 3, -2, 4]. The output is 24 in each case. This version of Kadane’s algorithm provides a solution with O(n) time complexity, where n is the length of the array.

Conclusion

We can solve the maximum product subarray in several ways. The five approaches we have covered in this tutorial include simple brute force, optimized brute force, efficient dynamic programming, two traversals, and Kadane’s algorithm. Each approach has its strengths and drawbacks. The best choice depends on the requirements and constraints of your problem.

The simple brute force approach is the easiest to implement. But it has high time complexity. Whereas dynamic programming and Kadane's algorithm-based approaches are efficient with a linear time complexity. They are suitable for large inputs. The two-traversal approach also has linear time complexity and can handle arrays containing zeros.

Understanding the various approaches will give you a solid foundation for tackling such problems and further enhance your problem-solving skills in programming.

FAQs

  1. What is the time and space complexity of Kadane's algorithm?

The time complexity of Kadane's algorithm is O(n), where n is the length of the array. The space complexity is O(1), as it only requires constant extra space.

  1. How does the two-traversal approach handle arrays with zero values?

The two-traversal approach handles zeros by resetting the product when it encounters zero. This ensures that the zero doesn't affect the product of subsequent elements in the array.

  1. Why do we need to keep track of both the maximum and minimum products in Kadane’s algorithm for this problem?

We keep track of both the maximum and minimum products because a negative number could change the maximum product into the minimum product, and vice versa.

  1. What is the main disadvantage of the simple brute force approach for solving the maximum subarray problem?

The simple brute force approach has high time complexity. It checks all possible subarrays of the input array, leading to a time complexity of O(n^2), which is inefficient for large inputs.

  1. Why is Kadane's algorithm often preferred for solving the maximum product subarray problem?

Kadane's algorithm is preferred for the maximum subarray problem due to its efficiency (linear time complexity), minimal extra space requirement, and ability to handle negative numbers and zeros effectively.

Leave a Reply

Your email address will not be published. Required fields are marked *