Reputation: 4727
I have a recursive function, that finds the maximum difference between any two integers, provided that the value at the second index is higher than the value at the first index:
def func(arr):
if len(arr) <= 1:
return 0;
left = arr[ : (len(arr) // 2)]
right = arr[(len(arr) // 2) : ]
leftBest = func(left)
rightBest = func(right)
crossBest = max(right) - min(left)
return max(leftBest, rightBest, crossBest)
If I am given the list [12, 9, 18, 3]
, then I would compute the maximum difference between any two elements i
, j
such that j > i
and the element at j
minus the element at i
is of max difference.
In this case we have j = 2, i = 1
, representing 18 - 9
for the biggest difference of 9
.
My current algorithm returns 9
, but I would like it to return the indicies (1, 2)
. How can I modify this divide and conquer approach to do this?
Upvotes: 1
Views: 220
Reputation: 1121744
Instead of returning the maximum difference, return both the indices and the maximum difference. Because you pass in sliced lists to the recursive calls, you'll have to adjust the indices returned from recursive rightBest
calls (the leftBest
calls always start with a list running from 'current' 0
to the midpoint).
Use min()
and max()
with a key that alters how the maximum is picked. For picking the index of the maximum value in a a list, generate the indices with range()
, then map those indices back to the list with arr.__getitem__
:
def func(arr):
if len(arr) <= 1:
return 0, 0, 0
mid = len(arr) // 2
left = arr[:mid]
right = arr[mid:]
leftBest = func(left)
ri, rj, rm = func(right)
rightBest = (ri + mid, rj + mid, rm)
key = arr.__getitem__
ci = min(range(0, mid), key=key)
cj = max(range(mid, len(arr)), key=key)
crossBest = (ci, cj, arr[cj] - arr[ci])
return max(leftBest, rightBest, crossBest, key=lambda ijm: ijm[2])
This returns the 2 indices as well as the difference:
>>> func([12, 9, 18, 3])
(1, 2, 9)
You'd have to slice the result to get just the i
, and j
values:
>>> func([12, 9, 18, 3])[:2]
(1, 2)
I'd not even slice the arr
list, because there is really no need to create copies here. Just pass along start
and stop
indices to the recursive functions to replace 0
and len(arr)
. I'd also use a nested function to do the recursive work; the outer function can be used to extract just the (i, j)
pair:
def best_difference_indices(arr):
arr_get = arr.__getitem__
max_get = lambda ijm: ijm[2]
def _func(start, stop):
if stop - start <= 1:
return start, start, 0
mid = (stop - start) // 2 + start
left_best = _func(start, mid)
right_best = _func(mid, stop)
ci = min(range(start, mid), key=arr_get)
cj = max(range(mid, stop), key=arr_get)
cross_best = (ci, cj, arr[cj] - arr[ci])
return max(left_best, right_best, cross_best, key=max_get)
i, j, _ = _func(0, len(arr))
return i, j
Not having to slice makes this faster; with an input of 1000 elements, the second version takes < 3ms to find the 2 indices, while the version with slicing takes ~3.6 ms; a 20% increase.
For 1 million elements, that becomes 3.7 seconds and 4.22 seconds, respectively.
Upvotes: 1
Reputation: 73460
I don't know if you are nailed to a recursive divide'n'conquer implementation. If you are not, here is an iterative, linear solution:
def func(arr):
min_i = lower_i = 0
max_dif = upper_i = None
for i in range(1, len(arr)):
if max_dif is None or max_dif < arr[i] - arr[min_i]:
max_dif = arr[i] - arr[min_i]
lower_i = min_i
upper_i = i
if arr[i] < arr[min_i]:
min_i = i
return lower_i, upper_i
Upvotes: 2
Reputation: 714
Try this:
In [1] arr = [12, 9, 18, 3]
In [2]: diff = 0
In [3]: for i in range(len(arr)):
...: for j in range(i):
...: tmp = arr[i] - arr[j]
...: if tmp > diff:
...: diff = tmp
...: result = j, i
...:
...:
...:
In [4]: result
Out[4]: (1, 2)
Upvotes: -1