Maximum Product Subarray GFG Question Python - python

I was practising DSA questions.
Given an array Arr[] that contains N integers (may be positive, negative or zero). Find the product of the maximum product subarray.
I am unable to complete this code. Please check the code and help. Thanks in advance.
def maxSubarrayProduct(arr, n):
result = arr[0]
for i in range(n):
mul = arr[i]
for j in range(i + 1, n):
result = max(result, mul)
mul *= arr[j]
result = max(result, mul)
return result

I think the program you wrote comes under brute force methods which have the worst time complexity. To improve its time and space complexity you can just make 2 variables that will store your current max and current min value. Here is the code for reference. Happy coding. Let me know if this helps.
def maxProduct(self,arr, n):
res = max(arr)
currMax, currMin = 1,1
for i in arr:
if i == 0:
currMax, currMin = 1,1
continue
#Storing max value to use it later on.
temp = currMax*i
currMax = max(currMax*i, currMin*i, i)
currMin = min(currMin*i, temp, i)
res = max(res, currMax)
return res

Related

Maximum Subarray Recursion

The 'Maximum Subarray' question:
Given an integer array nums, find the contiguous subarray (containing
at least one number) which has the largest sum and return its sum.
Example:
Input: nums = [-2,1,-3,4,-1,2,1,-5,4]
Output: 6
This should be an easy problem, but for some reason, my code runs endlessly when given a big array as input.
I hope someone can help:
This is my code:
def maxSubCaller(nums):
return maxSub(nums, 0, len(nums)-1)
def maxSub(nums, i, j):
if i == j:
return nums[i]
if j < i or i > j:
return min(nums)
sum_res = sum(nums[i:j + 1])
left_sub = maxSub(nums, i, j-1)
right_sub = maxSub(nums, i+1, j)
return max(sum_res, left_sub, right_sub)
This may be because your implementation is actually quite time complex. For every recursive step, you are summing between two indexes. The sum function needs to loop through the passed parameters, and therefore your worst case time complexity is proportional to n^2, where n is the length of the input list.
This problem can be solved in linear time proportional to the length of the input list as follows:
def max_sub_arr(nums):
max_sum = nums[0]
local_sum = nums[0]
for i in range(1, len(nums)):
if (nums[i] > local_sum + nums[i]):
local_sum = nums[i]
else:
local_sum += nums[i]
max_sum = max(local_sum, max_sum)
return max_sum
if __name__ == "__main__":
print(max_sub_arr([-2,1,-3,4,-1,2,1,-5,4]))
This is a different algorithm to yours, and only loops through the array once (doesn't use the sum function), and should execute faster.
There are probably further optimisations to this code, and there is no error handling (i.e. when nums is empty). The above should be fast enough to complete your problem however.

Python code for multiplying adjacent digits of a big number and finding maximum possible product, not giving desired result

Here is a Python code for finding maximum product we can get from 13 adjacent digits of a number. There is no error message, but this program is not giving the desired output. I am getting(in repl.it) 1 everytime, though it is clear that the answer is not 1. I am new to programming.
My attempt
I have converted the number into an string and stored it into a array to get element by element. The outer for loop traversing over all the numbers(last time when i have value len(n)-12, i+j will reach the last entry of the array. (Though the array stores the number in a reverse order, I haven't reversed it because, we don't need to).
n = "123899778978978787888787778788767677667"
arr = []
for i in range(len(n)):
arr.append(int(n)%10)
n = str(int(n)//10)
mul = 1
max_mult = 1
for i in range(len(n)-12):
for j in range(13):
mul = mul * int(arr[i+j])
if(max_mult<mul):
max_mult = mul
print(max_mult)
Can anyone tell me where I am going wrong? Any help will be appreciated.
Your logic can be simplified somewhat using zip:
n_list = list(map(int, list(n)))
res = max(i * j for i, j in zip(n_list, n_list[1:])) # 81
If you insist on using a for loop:
n_list = list(map(int, list(n)))
max_mult = 0
for i, j in zip(n_list, n_list[1:]):
mult = i * j
if mult > max_mult:
max_mult = mult
print(max_mult) # 81
Note you can modify your existing range-based iteration, but this is not considered Pythonic:
for i in range(len(n_list) - 1):
mult = n_list[i] * n_list[i+1]
if mult > max_mult:
max_mult = mult

Looking to speed up this max pairwise product

I'm very new to programming, so please go easy on me :)
How can I make the following python code output quicker-
n = int(input())
a = [int(x) for x in input().split()]
assert(len(a) == n)
result = 0
for i in range(0, n):
for j in range(i+1, n):
if a[i]*a[j] > result:
result = a[i]*a[j]
print(result)
What are some options to maximize its speed?
All you are trying to do is to find two different elements whos product is the biggest. This happens when two numbers are the biggest. So you basically have find two biggest numbers in the array. This can be done in O(n).
One example how this can be done. Find the biggest number and it's position. Save it, remove it and find the max of the result (which will be second biggest). Now multiply both.
You are trying to compute the maximal product of distinct integers in a list.
Computing product for every pair has O(N^2) time complexity.
You can reduce this to O(N log(N)) by sorting the list.
a = sorted(a)
ans = max(a[0] * a[1], a[-2] * a[-1])
You can further improve this to linear time, but I will let you figure it out.
I see, Stepik.org course lesson "Maximum pairwise product":
Try the following, instead of the cascaded for-loop:
n1 = max(a)
a.remove(n1)
result = n1*max(a)
Which essentially saves the biggest value in a variable, removes it from the sequence, and multiplies it with the now biggest value of the sequence (2nd biggest value in the original sequence).
My solution would use sorted and set,as this algorithm work on duplicate items as well.
x, y = sorted(set(nums))[-2:]
print(x * y)
I came up with this O(n) solution:
def maxPairwise(l):
if len(l) == 2:
return l[0]*l[1]
elif len(l) == 1:
return l[0]
else:
max1 = 0
max2 = 0
for i in l:
if i > max1:
max1 = i
if i > max2 and i != max1:
max2 = i
return max1 * max2

Iterate through sum combinations in Python 3

I'm look for a way to find all combinations of sums with elements of a Fibonacci sequence with a given limit which equal that same value. I know that combinations() from itertools is our best bet in solving such problems, but as I'm new in Python I like to know how I can retain the matching combinations (as only one is correct, as this is not the end of the algorithm).
I currently have:
# Call Fibonacci sequence with a given limit:
def fib1(n):
result = []
a, b = 1, 1
while a < n:
result.append(a)
a, b = b, a + b
return result
# Wrong code, skeleton:
def zeckendorf(n):
from itertools import combinations
seq = fib1(n)
row = []
sum = 0
for i in combinations(seq, len(seq)):
sum += i
row.append(i)
if sum > n:
sum = 0
row = []
continue
elif sum == n:
break
return row
Now I know the second bit is wrong in many ways. Also, I make the mistake as to try to add tuples to integers. I just need to know how I can iterate through separate elements of those combinations separately using the itertools module. Only the combinations with sum 'n' are useful to me.
If I understand correctly what you are trying to achieve then use the following code:
def zeckendorf(n):
seq = fib1(n)
for i in range(1, len(seq)):
for comb in itertools.combinations(seq, i):
if sum(comb) == n:
return list(comb)
return []
If you need further explanation on this code just ask :)
To find all the combinations with the desired sum, append each combination to a result list:
def combinations_with_sum(sequence, desired_sum):
results = []
for i in range(len(sequence)):
results.extend([combination for combination in combinations(sequence, i)
if sum(combination) == desired_sum])
return results
Let's say you want to find all subsets of your input with sum < max_sum and the number of elements between min_terms and max_terms.
Here is a couple of ways to do it, I'm including the whole script to make it easier to test and play around with but basically you only need *LimitedSums() functions to get the answer.
Brute-force approach is to iterate through all subsets and check the sum and the number of elements for each subset. That's effectively what SlowLimitedSums() does - although it takes advantage of itertools.combinations() to iterate through subsets and doesn't consider subsets with more than max_terms elements.
Potentially more efficient approach is to only consider the subsets which sum up to less than max_sum. If you are building subsets recursively you can simply stop recursion as soon as the sum of your current subset exceeds max_sum, assuming all your input numbers are non-negative, or the number of elements exceeds max_terms. This is implemented in FasterLimitedSums().
Note that in the worst case your result will contain all 2^len(v) subsets - in this case there should be no significant running time difference between the two versions of *LimitedSums().
import itertools
import random
def SlowLimitedSums(v, max_sum, min_terms=None, max_terms=None):
min_terms = 0 if min_terms is None else min_terms
max_terms = len(v) if max_terms is None else max_terms
return sorted(set(
sum(c) for nc in range(min_terms, max_terms + 1)
for c in itertools.combinations(v, nc)
if sum(c) <= max_sum))
def FasterLimitedSums(v, max_sum, min_terms=None, max_terms=None):
l = sorted(v)
n = len(v)
min_terms = 0 if min_terms is None else min_terms
max_terms = n if max_terms is None else max_terms
result = set([])
def RecursiveSums(s, n_terms, start_pos):
if start_pos >= n or s > max_sum or n_terms > max_terms:
return
if n_terms >= min_terms:
result.add(s)
for p in range(start_pos, n):
RecursiveSums(s + v[p], n_terms + 1, p + 1)
RecursiveSums(0, 0, -1)
return sorted(result)
def main():
mass_list = [4, 1, 8]
mass = 10
print(sorted(mass_list + SlowLimitedSums(mass_list, mass, min_terms=2)))
print(sorted(mass_list + FasterLimitedSums(mass_list, mass, min_terms=2)))
if __name__ == "__main__":
main()

Subset sum Problem

recently I became interested in the subset-sum problem which is finding a zero-sum subset in a superset. I found some solutions on SO, in addition, I came across a particular solution which uses the dynamic programming approach. I translated his solution in python based on his qualitative descriptions. I'm trying to optimize this for larger lists which eats up a lot of my memory. Can someone recommend optimizations or other techniques to solve this particular problem? Here's my attempt in python:
import random
from time import time
from itertools import product
time0 = time()
# create a zero matrix of size a (row), b(col)
def create_zero_matrix(a,b):
return [[0]*b for x in xrange(a)]
# generate a list of size num with random integers with an upper and lower bound
def random_ints(num, lower=-1000, upper=1000):
return [random.randrange(lower,upper+1) for i in range(num)]
# split a list up into N and P where N be the sum of the negative values and P the sum of the positive values.
# 0 does not count because of additive identity
def split_sum(A):
N_list = []
P_list = []
for x in A:
if x < 0:
N_list.append(x)
elif x > 0:
P_list.append(x)
return [sum(N_list), sum(P_list)]
# since the column indexes are in the range from 0 to P - N
# we would like to retrieve them based on the index in the range N to P
# n := row, m := col
def get_element(table, n, m, N):
if n < 0:
return 0
try:
return table[n][m - N]
except:
return 0
# same definition as above
def set_element(table, n, m, N, value):
table[n][m - N] = value
# input array
#A = [1, -3, 2, 4]
A = random_ints(200)
[N, P] = split_sum(A)
# create a zero matrix of size m (row) by n (col)
#
# m := the number of elements in A
# n := P - N + 1 (by definition N <= s <= P)
#
# each element in the matrix will be a value of either 0 (false) or 1 (true)
m = len(A)
n = P - N + 1;
table = create_zero_matrix(m, n)
# set first element in index (0, A[0]) to be true
# Definition: Q(1,s) := (x1 == s). Note that index starts at 0 instead of 1.
set_element(table, 0, A[0], N, 1)
# iterate through each table element
#for i in xrange(1, m): #row
# for s in xrange(N, P + 1): #col
for i, s in product(xrange(1, m), xrange(N, P + 1)):
if get_element(table, i - 1, s, N) or A[i] == s or get_element(table, i - 1, s - A[i], N):
#set_element(table, i, s, N, 1)
table[i][s - N] = 1
# find zero-sum subset solution
s = 0
solution = []
for i in reversed(xrange(0, m)):
if get_element(table, i - 1, s, N) == 0 and get_element(table, i, s, N) == 1:
s = s - A[i]
solution.append(A[i])
print "Solution: ",solution
time1 = time()
print "Time execution: ", time1 - time0
I'm not quite sure if your solution is exact or a PTA (poly-time approximation).
But, as someone pointed out, this problem is indeed NP-Complete.
Meaning, every known (exact) algorithm has an exponential time behavior on the size of the input.
Meaning, if you can process 1 operation in .01 nanosecond then, for a list of 59 elements it'll take:
2^59 ops --> 2^59 seconds --> 2^26 years --> 1 year
-------------- ---------------
10.000.000.000 3600 x 24 x 365
You can find heuristics, which give you just a CHANCE of finding an exact solution in polynomial time.
On the other side, if you restrict the problem (to another) using bounds for the values of the numbers in the set, then the problem complexity reduces to polynomial time. But even then the memory space consumed will be a polynomial of VERY High Order.
The memory consumed will be much larger than the few gigabytes you have in memory.
And even much larger than the few tera-bytes on your hard drive.
( That's for small values of the bound for the value of the elements in the set )
May be this is the case of your Dynamic programing algorithm.
It seemed to me that you were using a bound of 1000 when building your initialization matrix.
You can try a smaller bound. That is... if your input is consistently consist of small values.
Good Luck!
Someone on Hacker News came up with the following solution to the problem, which I quite liked. It just happens to be in python :):
def subset_summing_to_zero (activities):
subsets = {0: []}
for (activity, cost) in activities.iteritems():
old_subsets = subsets
subsets = {}
for (prev_sum, subset) in old_subsets.iteritems():
subsets[prev_sum] = subset
new_sum = prev_sum + cost
new_subset = subset + [activity]
if 0 == new_sum:
new_subset.sort()
return new_subset
else:
subsets[new_sum] = new_subset
return []
I spent a few minutes with it and it worked very well.
An interesting article on optimizing python code is available here. Basically the main result is that you should inline your frequent loops, so in your case this would mean instead of calling get_element twice per loop, put the actual code of that function inside the loop in order to avoid the function call overhead.
Hope that helps! Cheers
, 1st eye catch
def split_sum(A):
N_list = 0
P_list = 0
for x in A:
if x < 0:
N_list+=x
elif x > 0:
P_list+=x
return [N_list, P_list]
Some advices:
Try to use 1D list and use bitarray to reduce memory footprint at minimum (http://pypi.python.org/pypi/bitarray) so you will just change get / set functon. This should reduce your memory footprint by at lest 64 (integer in list is pointer to integer whit type so it can be factor 3*32)
Avoid using try - catch, but figure out proper ranges at beginning, you might found out that you will gain huge speed.
The following code works for Python 3.3+ , I have used the itertools module in Python that has some great methods to use.
from itertools import chain, combinations
def powerset(iterable):
s = list(iterable)
return chain.from_iterable(combinations(s, r) for r in range(len(s)+1))
nums = input("Enter the Elements").strip().split()
inputSum = int(input("Enter the Sum You want"))
for i, combo in enumerate(powerset(nums), 1):
sum = 0
for num in combo:
sum += int(num)
if sum == inputSum:
print(combo)
The Input Output is as Follows:
Enter the Elements 1 2 3 4
Enter the Sum You want 5
('1', '4')
('2', '3')
Just change the values in your set w and correspondingly make an array x as big as the len of w then pass the last value in the subsetsum function as the sum for which u want subsets and you wl bw done (if u want to check by giving your own values).
def subsetsum(cs,k,r,x,w,d):
x[k]=1
if(cs+w[k]==d):
for i in range(0,k+1):
if x[i]==1:
print (w[i],end=" ")
print()
elif cs+w[k]+w[k+1]<=d :
subsetsum(cs+w[k],k+1,r-w[k],x,w,d)
if((cs +r-w[k]>=d) and (cs+w[k]<=d)) :
x[k]=0
subsetsum(cs,k+1,r-w[k],x,w,d)
#driver for the above code
w=[2,3,4,5,0]
x=[0,0,0,0,0]
subsetsum(0,0,sum(w),x,w,7)

Categories

Resources