MaxProductOfThree how to increase the performance

Viewed 269

MaxProductOfThree lesson : https://app.codility.com/programmers/lessons/6-sorting/max_product_of_three/

For solving this Codility lesson I've coded this function like below;

import itertools

def solution(A):
    combs=[]

    for pivot_element in A:
        to_comb=A[A.index(pivot_element):]

        for comb in itertools.combinations(to_comb, 3):
           mul_comb= comb[0] * comb[1] * comb[2]
           combs.append(mul_comb) 

    return max(combs)

My results were,


result_of_algorithm={
                    "correctness":100,
                    "performance":0,
                    "overall":44,
                    "time complexity": "O(N^3)"
                    }

How can I increase its performance to O(n) time complexity? Can you please explain?

4 Answers

O(n) version of schwobaseggl's solution, also got 100%:

from heapq import nsmallest, nlargest

def solution(A):
    a, b = nsmallest(2, A)
    z, y, x = nlargest(3, A)
    return max(a*b*z, x*y*z)

Benchmarks with the largest allowed case (100,000 values from -1000 to 1000):

204 ms  206 ms  208 ms  210 ms  212 ms  sort
 76 ms   77 ms   78 ms   79 ms   81 ms  heapq
134 ms  135 ms  135 ms  135 ms  136 ms  oneloop
144 ms  146 ms  147 ms  149 ms  151 ms  twoloops
  3 ms    3 ms    3 ms    3 ms    4 ms  baseline

Benchmark code (Try it online!):

from timeit import repeat
from random import choices
from heapq import nsmallest, nlargest
import sys

def sort(A):
    A.sort()
    p1 = A[0] * A[1] * A[-1]
    p2 = A[-3] * A[-2] * A[-1]
    return max(p1, p2)

def heapq(A):
    a, b = nsmallest(2, A)
    z, y, x = nlargest(3, A)
    return max(a*b*z, x*y*z)

def oneloop(A):
    min1 = sys.maxsize * 2
    min2 = sys.maxsize * 2
    max1 = -sys.maxsize * 2
    max2 = -sys.maxsize * 2
    max3 = -sys.maxsize * 2
    for Ai in A:
        if Ai <= min1:
            min2 = min1
            min1 = Ai
        elif Ai <= min2:
            min2 = Ai
        if Ai >= max1:
            max3 = max2
            max2 = max1
            max1 = Ai
        elif Ai >= max2:
            max3 = max2
            max2 = Ai
        elif Ai >= max3:
            max3 = Ai
    return max([min1 * min2 * max1, max1 * max2 * max3])

def twoloops(A):
    min1 = sys.maxsize * 2
    min2 = sys.maxsize * 2
    max1 = -sys.maxsize * 2
    max2 = -sys.maxsize * 2
    max3 = -sys.maxsize * 2
    for Ai in A:
        if Ai <= min1:
            min2 = min1
            min1 = Ai
        elif Ai <= min2:
            min2 = Ai
    for Ai in A:
        if Ai >= max1:
            max3 = max2
            max2 = max1
            max1 = Ai
        elif Ai >= max2:
            max3 = max2
            max2 = Ai
        elif Ai >= max3:
            max3 = Ai
    return max([min1 * min2 * max1, max1 * max2 * max3])

def baseline(A):
    pass

funcs = sort, heapq, oneloop, twoloops, baseline

for _ in range(3):
    A = choices(range(-1000, 1001), k=100_000)
    for func in funcs:
        times = sorted(repeat(lambda: func(A[:]), number=10))
        print(*('%3d ms ' % (t * 1e3) for t in times), func.__name__)
    print()

Naively, you can do this in log-linear time:

def solution(A):
    A.sort()
    p1 = A[0] * A[1] * A[-1]
    p2 = A[-3] * A[-2] * A[-1]
    return max(p1, p2)

The sorting allows you to just try the numbers on both extremes of the sort order. The two options account for the possibility of including negative numbers. It achieves 100% on both correctness and performance.

From this source I've reached a very simple solution: https://youtu.be/qr3i9cXAjbc

The Algorithm is the same algorithm in the answer of @schwobaseggl and simply like below. Works %100 in both performance and correctness.

def solution(A):
    A.sort()
    N=len(A)

    P1 = A[N-1] * A[0] * A[1]
    P2 = A[N-1] * A[N-2] * A[N-3]

    return max(P1,P2)

The idea is described here: https://afteracademy.com/blog/maximum-product-of-three-numbers

There are only two possible options:

  1. You need the three biggest numbers (this is obviously the case if all numbers are positive)
  2. You need the biggest number and the two smallest numbers. This can only happen if the two smallest numbers are negative and if their product is larger than the product of the second and third largest numbers. Clearly, the product of the two smallest numbers then is positive and you then need to multiply it with the largest number.

You need to check both cases and return whatever is larger. I adapted the code from the link to Python. It has time complexity O(n) as we iterate through the list only one:

# solution.py
import sys


def solution(A):
    min1 = sys.maxsize * 2
    min2 = sys.maxsize * 2
    max1 = -sys.maxsize * 2
    max2 = -sys.maxsize * 2
    max3 = -sys.maxsize * 2

    for Ai in A:
        if Ai <= min1:
            min2 = min1
            min1 = Ai
        elif Ai <= min2:
            min2 = Ai
        if Ai >= max1:
            max3 = max2
            max2 = max1
            max1 = Ai
        elif Ai >= max2:
            max3 = max2
            max2 = Ai
        elif Ai >= max3:
            max3 = Ai
    return max([min1 * min2 * max1, max1 * max2 * max3])

For example, print(solution([-11, -10, 1, 2, 3, 4, 5, 6, 7, 8, 9])) gives you 990 (-11 * -10 * 9)

Related