Im having a problem to understand this algorithm, how would you describe it, mostly the while loops. I understand that this i an iterative function, and the use of Hoare partition scheme. But what does the while loops do, and why does it use breaks.
def inplace_quick_sort_nonrecursive(S):
if len(S) < 2:
return S
stack = [] #initialize the stack
stack.append([0, len(S) - 1])
while len(stack) > 0: #loop till the stack is empty
low, high = stack.pop() #pop low and high indexes
pivot = S[(low + high) // 2] #create pivot, of any S[] except S[high]
i = low - 1 #Hoare partition
j = high + 1
while True:
while True: #while (S[++i] < pivot)
i += 1
if(S[i] >= pivot):
break
while True: #while(S[--j] < p)
j -= 1
if(S[j] <= pivot):
break
if (i >= j): #if indexes met or crossed, break
break
S[i], S[j] = S[j], S[i] #else swap the elements
if (j > low): #push the indexes into the stack
stack.append([low, j])
j += 1
if (high > j):
stack.append([j, high])
So basically I'm trying to complete this assignment: https://www.codewars.com/kata/5279f6fe5ab7f447890006a7
The question: Why does my function count position 11 as a local maxima of this particular array?
Here is my code:
def main():
arr = [1,2,5,4,3,2,3,6,4,1,2,3,3,4,5,3,2,1,2,3,5,5,4,3]
maxes = []
positions = []
dic = {"pos":positions, "peaks":maxes}
for i in range(1,len(arr)-1):
try:
if arr[i] > arr[i+1] and arr[i] > arr[i-1]:
maxes.append(arr[i])
positions.append(i)
elif arr[i] > arr[i-1] and arr[i] == arr[i+1]:
for a in range(i+1,len(arr)):
if arr[a] > arr[a+1]:
maxes.append(arr[i])
positions.append(i)
break
except IndexError:
pass
print(dic)
main()
My output:
{'pos': [2, 7, 11, 14, 20], 'peaks': [5, 6, 3, 5, 5]}
Correct output:
{'pos': [2, 7, 14, 20], 'peaks': [5, 6, 5, 5]}
When you see a situation like "2,3,3, 4,5,3...?" you save the position and run further try to find out: 1) at the end the value is less (then this is the peak and the saved value came in handy), 2) the value is larger (not a peak, plateau, the saved value was not useful).
In you particular situation in second condition:
elif arr[i] > arr[i-1] and arr[i] == arr[i+1]: you begin to iterate from second 3 and stop only when arr[a] > arr[a+1]. In this moment you append you old i value (which indicates first 3):
maxes.append(arr[i]); positions.append(i)
that is, you do not take into account that the value further may be greater and you need to interrupt.
A working example of the function can be seen below:
def main():
arr = [1,2,5,4,3,2,3,6,4,1,2,3,3,4,5,3,2,1,2,3,5,5,4,3]
dic = {'pos' : [], 'peaks' : []}
i, n = 1, len(arr)
while i < n - 1:
if arr[i-1] < arr[i] > arr[i+1]:
dic['peaks'].append(arr[i])
dic['pos'].append(i)
i += 1
else:
if arr[i-1] < arr[i] and arr[i] == arr[i+1]:
mem = i
while i < n -1 and arr[i] == arr[i+1]:
i += 1
if i == n - 1:
return dic
if arr[i] > arr[i+1]:
dic['peaks'].append(arr[mem])
dic['pos'].append(mem)
else:
i += 1
else:
i += 1
return dic
print(main())
When you find a following value that is equal and continue to search for a lesser value you should also break the loop if you find a greater value. It's not a peak if the group of equals are followed by a greater value.
A small addition is enough to make your code work.
def main():
arr = [1,2,5,4,3,2,3,6,4,1,2,3,3,4,5,3,2,1,2,3,5,5,4,3]
maxes = []
positions = []
dic = {"pos":positions, "peaks":maxes}
for i in range(1,len(arr)-1):
try:
if arr[i] > arr[i+1] and arr[i] > arr[i-1]:
maxes.append(arr[i])
positions.append(i)
elif arr[i] > arr[i-1] and arr[i] == arr[i+1]:
for a in range(i+1,len(arr)):
if arr[a] > arr[a+1]:
maxes.append(arr[i])
positions.append(i)
break
# also break if a greater value is found
elif arr[a] < arr[a+1]:
break
except IndexError:
pass
print(dic)
main()
A simpler approach may be to iterate just once and take note of the position every time you find an increase. Then save that position and its value when you find a decrease afterwards.
def pick_peaks(arr):
dic = {"pos":[], "peaks":[]}
possible_peak = None
for i in range(1,len(arr)):
if arr[i] > arr[i-1]:
# Value is going up. Remember position
possible_peak = i
elif arr[i] < arr[i-1] and possible_peak != None:
# It was indeed a peak. Save it.
dic["pos"].append(possible_peak)
dic["peaks"].append(arr[possible_peak])
# reset memory until new increase is found
possible_peak = None
return dic
input_arr = [1,2,5,4,3,2,3,6,4,1,2,3,3,4,5,3,2,1,2,3,5,5,4,3]
print(pick_peaks(input_arr))
Given an array nums of n integers, are there elements a, b, c in nums such that a + b + c = 0? Find all unique triplets in the array which gives the sum of zero.
class Solution:
def threeSum(self, nums):
data = []
i = j = k =0
length = len(nums)
for i in range(length):
for j in range(length):
if j == i:
continue
for k in range(length):
if k == j or k == i:
continue
sorted_num = sorted([nums[i],nums[j],nums[k]])
if nums[i]+nums[j]+nums[k] == 0 and sorted_num not in data:
data.append(sorted_num)
return data
My soulution is working well but it appears that it may be too slow.
Is there a way to improve my codes without changing it significantly?
This is a O(n^2) solution with some optimization tricks:
import itertools
class Solution:
def findsum(self, lookup: dict, target: int):
for u in lookup:
v = target - u
# reduce duplication, we may enforce v <= u
try:
m = lookup[v]
if u != v or m > 1:
yield u, v
except KeyError:
pass
def threeSum(self, nums: List[int]) -> List[List[int]]:
lookup = {}
triplets = set()
for x in nums:
for y, z in self.findsum(lookup, -x):
triplets.add(tuple(sorted([x, y, z])))
lookup[x] = lookup.get(x, 0) + 1
return [list(triplet) for triplet in triplets]
First, you need a hash lookup to reduce your O(n^3) algorithm to O(n^2). This is the whole idea, and the rest are micro-optimizations:
the lookup table is build along with the scan on the array, so it is one-pass
the lookup table index on the unique items that seen before, so it handles duplicates efficiently, and by using that, we keep the iteration count of the second-level loop to the minimal
This is an optimized version, will pass through:
from typing import List
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
unique_triplets = []
nums.sort()
for i in range(len(nums) - 2):
if i > 0 and nums[i] == nums[i - 1]:
continue
lo = i + 1
hi = len(nums) - 1
while lo < hi:
target_sum = nums[i] + nums[lo] + nums[hi]
if target_sum < 0:
lo += 1
if target_sum > 0:
hi -= 1
if target_sum == 0:
unique_triplets.append((nums[i], nums[lo], nums[hi]))
while lo < hi and nums[lo] == nums[lo + 1]:
lo += 1
while lo < hi and nums[hi] == nums[hi - 1]:
hi -= 1
lo += 1
hi -= 1
return unique_triplets
The TLE is most likely for those instances that fall into these two whiles:
while lo < hi and nums[lo] == nums[lo + 1]:
while lo < hi and nums[lo] == nums[lo + 1]:
References
For additional details, please see the Discussion Board where you can find plenty of well-explained accepted solutions with a variety of languages including low-complexity algorithms and asymptotic runtime/memory analysis1, 2.
I'd suggest:
for j in range(i+1, length):
This will save you len(nums)^2/2 steps and first if statement becomes redundant.
sorted_num = sorted([nums[i],nums[j],nums[k]])
if nums[i]+nums[j]+nums[k] == 0 and sorted_num not in data:
sorted_num = sorted([nums[i],nums[j],nums[k]])
data.append(sorted_num)
To avoid unneeded calls to sorted in the innermost loop.
Your solution is the brute force one, and the slowest one.
Better solutions can be:
Assume you start from an element from array. Consider using a Set for finding next two numbers from remaining array.
There is a 3rd better solution as well. See https://www.gyanblog.com/gyan/coding-interview/leetcode-three-sum/
I want to check that my understanding of how python handles slices is correct.
Here's my implementation of merge sort:
def merge_sort(L):
def merge(a, b):
i, j = 0, 0
c = []
while i < len(a) and j < len(b):
if a[i] < b[j]:
c.append(a[i])
i += 1
elif b[j] < a[i]:
c.append(b[j])
j += 1
if a[i:]:
c.extend(a[i:])
if b[j:]:
c.extend(b[j:])
return c
if len(L) <= 1:
return L
else:
mid = len(L) // 2
left = merge_sort(L[:mid])
right = merge_sort(L[mid:])
return merge(left, right)
Am I right in thinking that I could replace this:
if a[i:]:
c.extend(a[i:])
if b[j:]:
c.extend(b[j:])
With this:
while i < len(a):
c.append(a[i])
i += 1
while j < len(b):
c.append(b[j])
j += 1
And have the exact same level of complexity? My understanding of slicing is that its complexity is equivalent to slice length? Is that correct?
Does the fact that I'm calling a slice twice (first in the condition, second time inside of it) make it 2x complexity?
Your implementation of mergesort has problems:
in the merge function's main loop, you do nothing if the values in a[i] and b[j] are equal, or more precisely if you have neither a[i] < b[i] nor a[i] > b[i]. This causes an infinite loop.
there is no need to define merge as a local function, actually there is no need to make it a separate function, you could inline the code and save the overhead of a function call.
Here is a modified version:
def merge_sort(L):
if len(L) <= 1:
return L
else:
mid = len(L) // 2
a = merge_sort(L[:mid])
b = merge_sort(L[mid:])
i, j = 0, 0
c = []
while i < len(a) and j < len(b):
if a[i] <= b[j]:
c.append(a[i])
i += 1
else:
c.append(b[j])
j += 1
if a[i:]:
c.extend(a[i:])
else:
c.extend(b[j:])
return c
Regarding performance, slicing or iterating has no impact on complexity since both operations have linear time cost.
Regarding performance, here are directions to try:
replace the test if a[i:] with if i < len(a). Creating the slice twice is costly.
perform the sort in place, avoiding the append operations
restructure the main loop to have a single test per iteration
Here is a modified version:
def merge_sort(L):
if len(L) <= 1:
return L
else:
mid = len(L) // 2
a = merge_sort(L[:mid])
b = merge_sort(L[mid:])
i, j, k = 0, 0, 0
while True:
if a[i] <= b[j]:
L[k] = a[i]
k += 1
i += 1
if (i == len(a)):
L[k:] = b[j:]
return L
else:
L[k] = b[j]
k += 1
j += 1
if (j == len(b)):
L[k:] = a[i:]
return L
I am trying to understand the following program to find a number of the subset which forms a given sum from a given list.
def count_sets(arr, total):
return rec(arr, total, len(arr)-1)
def rec(arr, total, i):
print(arr, total, i)
if total == 0:
return 1
if i < 0:
return 0
if arr[i] > total:
return rec(arr, total, i-1)
else:
return rec(arr, total-arr[i], i-1) + rec(arr, total, i-1)
arr = [2,10,6,4]
print(count_sets(arr, 16))
the program works without any error, however, I am not able to find how it works.
It is a backtracking algorithm. In recursion rec(arr, total, i), it picks the last element arr[i] in rest array, and here are two main situations:
use arr[i]: rec(arr, total-arr[i], i-1)
not use arr[i]: rec(arr, total, i-1), and when arr[i] > total of course you can only not use it.
and we must have terminate condition for recursion, that is:
[success] find the subarray equal to total: if total == 0: return 1
[failed] reach the head: if i < 0: return 0
Hope I make it clear, and comment if you have further questions. : )