I am trying to solve a problem where recursion is a must. The tasks is: Write a function that takes in an integer n and returns the highest integer in the corresponding Collatz sequence.
My solution is this:
collatz = []
def max_collatz(num):
collatz.append(num)
if num == 1:
return max(collatz)
else:
return max_collatz(num / 2) if num%2 == 0 else max_collatz((3 * num) + 1)
However, I need to find a way to solve it without using a list outside of the function. I really couldn't find a solution, is there any?
It's either the current number or the largest in the rest of the sequence.
def max_collatz(n):
if n == 1:
return 1
elif n % 2:
return max_collatz(3 * n + 1)
else:
return max(n, max_collatz(n // 2))
I'm new to data structures and algorithms, so I was kind of confused when I was writing a function to find a pair of numbers from two arrays (one from each array) that the absolute value of difference of the two numbers is the smallest. My first draft was version 2, but it did not pass all the tests. However, when I assigned arrayOne[i] to firstNum and arrayTwo[j] to secondNum, it passed all the test cases. Could anyone explain what happened exactly? Since I thought these two versions were exactly the same.
Version 1:
def smallestDifference(arrayOne, arrayTwo):
arrayOne.sort()
arrayTwo.sort()
i = j = 0
smallest = float('inf')
current = float('inf')
smallestPair = []
while i < len(arrayOne) and j < len(arrayTwo):
firstNum = arrayOne[i]
secondNum = arrayTwo[j]
if firstNum < secondNum:
current = abs(firstNum - secondNum)
i += 1
elif firstNum > secondNum:
current = abs(firstNum - secondNum)
j += 1
else:
return [firstNum, secondNum]
if current < smallest:
smallest = current
smallestPair = [firstNum, secondNum]
return smallestPair
Version 2:
def smallestDifference(arrayOne, arrayTwo):
arrayOne.sort()
arrayTwo.sort()
i = j = 0
smallest = float('inf')
current = float('inf')
smallestPair = []
while i < len(arrayOne) and j < len(arrayTwo):
if arrayOne[i] < arrayTwo[j]:
current = abs(arrayOne[i] - arrayTwo[j])
i += 1
elif arrayOne[i] > arrayTwo[j]:
current = abs(arrayOne[i] - arrayTwo[j])
j += 1
else:
return [arrayOne[i], arrayTwo[j]]
if current < smallest:
smallest = current
smallestPair = [arrayOne[i], arrayTwo[j]]
return smallestPair
In version 1, you are indexing arrayOne and arrayTwo for your first if,elif,else statement which is fine, however you change the value of i/j so if the program hits the if current < smallest part of the algorithm, the value of i/j has changed.
For example lets say i and j where both 0 and arrayOne[0] < arraytwo[0], you would increment i and continue on. Once you get to the bottom and you want to delclare smallestPair, you are now setting the value to [arrayOne[1], arrayTwo[0] rather than [arrayOne[0], arrayTwo[0] because you incremented i.
Version one declares the two variables and uses those declarations for the WHOLE function.
How can I get this to print all triplets that have a sum less than or equal to a target? Currently this returns triplets that are = to the target. I've tried to change and think but can't figure out
def triplets(nums):
# Sort array first
nums.sort()
output = []
# We use -2 because at this point the left and right pointers will be at same index
# For example [1,2,3,4,5] current index is 4 and left and right pointer will be at 5, so we know we cant have a triplet
# _ LR
for i in range(len(nums) - 2):
# check if current index and index -1 are same if same continue because we need distinct results
if i > 0 and nums[i] == nums[i - 1]:
continue
left = i + 1
right = len(nums) - 1
while left < right:
currentSum = nums[i] + nums[left] + nums[right]
if currentSum <= 8:
output.append([nums[i], nums[left], nums[right]])
# below checks again to make sure index isnt same with adjacent index
while left < right and nums[left] == nums[left + 1]:
left += 1
while left < right and nums[right] == nums[right - 1]:
right -= 1
# In this case we have to change both pointers since we found a solution
left += 1
right -= 1
elif currentSum > 8:
left += 1
else:
right -= 1
return output
So for example input array is [1,2,3,4,5] we will get the result (1,2,3),(1,2,4),(1,2,5),(1,3,4) Because these have a sum of less than or equal to target of 8.
The main barrier to small changes to your code to solve the new problem is that your original goal of outputting all distinct triplets with sum == target can be solved in O(n^2) time using two loops, as in your algorithm. The size of the output can be of size proportional to n^2, so this is optimal in a certain sense.
The problem of outputting all distinct triplets with sum <= target, cannot always be solved in O(n^2) time, since the output can have size proportional to n^3; for example, with an array nums = [1,2,...,n], target = n^2 + 1, the answer is all possible triples of elements. So your algorithm has to change in a way equivalent to adding a third loop.
One O(n^3) solution is shown below. Being a bit more clever about filtering duplicate elements (like using a hashmap and working with frequencies), this should be improvable to O(max(n^2, H)) where H is the size of your output.
def triplets(nums, target=8):
nums.sort()
output = set()
for i, first in enumerate(nums[:-2]):
if first * 3 > target:
break
# Filter some distinct results
if i + 3 < len(nums) and first == nums[i + 3]:
continue
for j, second in enumerate(nums[i + 1:], i + 1):
if first + 2 * second > target:
break
if j + 2 < len(nums) and second == nums[j + 2]:
continue
for k, third in enumerate(nums[j + 1:], j + 1):
if first + second + third > target:
break
if k + 1 < len(nums) and third == nums[k + 1]:
continue
output.add((first, second, third))
return list(map(list, output))
This is my code:
list = []
line = 50
def genList(list):
i = 0
while i < 1000:
list.append(i)
i += 1
return list
def displayList(list, line):
i = 0
while i < 1000:
if i != 0 and i % line == 0:
print('\n')
print('{0:03}'.format(list[i]), end = ' ')
i += 1
print('\n')
def binarySearch(min, max, list, goal):
mid = min + (max - 1) // 2
if list[mid] == goal:
return mid
elif list[mid] > goal:
return binarySearch(min, mid - 1, list, goal)
else:
return binarySearch(mid + 1, max, list, goal)
genList(list)
displayList(list, line)
goal = int(input('What number do you want to find, from 0 to 999?\n'))
result = binarySearch(0, len(list) - 1, list, goal)
print(result)
...and like I said, certain numbers work but others don't, for example 999 will return 999 but 998 will return:
RecursionError: maximum recursion depth exceeded in comparison
Anyone know what's wrong with it? I'm at a bit of a loss...
mid = min + (max - 1) // 2 probably should be (min + max) // 2. Also, your code should have a base case that returns a value, say -1 if the element does not exist within the list. Something like if min > max: return -1 right at the beginning of your search function.
This line is definitely wrong:
mid = min + (max - 1) // 2
Perhaps you meant ( as #Nikolaos Chatzis pointed out)
mid = min + (max - min) // 2
but following works as well and needs one less operation:
mid = (min + max) // 2
Additionally you should abort recursion if minval > maxval (if the value you search for is not in the list)
I didn't look if you can do better. but this should work
def binarySearch(minval, maxval, list_, goal):
mid = (minval + maxval) // 2
# print(minval, maxval, mid, goal) # uncomment for debugging
if minval > maxval: # couldn't find search value
return None
if list_[mid] == goal:
return mid
elif list_[mid] > goal:
return binarySearch(minval, mid - 1, list_, goal)
else:
return binarySearch(mid + 1, maxval, list_, goal)
Also don't hesitate to add print statements (or logs) for debugging.
It makes it very often obvious what is going wrong.
What you could also do is run some simple test code to show that it works for all cases:
for v in range(1000):
assert binarySearch(0, len(list_) -1, list_, v) == v
Also as general recommendation to not stumble into funny or strange bugs.
min, max and list are all python keywords.
You can avoid funny surprises by not using variables with these names. It makes your code also look better with many basic syntax highlighters.
Using them as variable names is not forbidden, but I think it's not a good idea.
I have made a binary search algorithm, biSearch(A, high, low, key). It takes in a sorted array and a key, and spits out the position of key in the array. High and low are the min and max of the search range.
It almost works, save for one problem:
On the second "iteration" (not sure what the recursive equivalent of that is), a condition is met and the algorithm should stop running and return "index". I commented where this happens. Instead, what ends up happening is that the code continues on to the next condition, even though the preceding condition is true. The correct result, 5, is then overridden and the new result is a nonetype object.
within my code, I have commented in caps the problems at the location in which they occur. Help is much appreciated, and I thank you in advance!
"""
Created on Sat Dec 28 18:40:06 2019
"""
def biSearch(A, key, low = False, high = False):
if low == False:
low = 0
if high == False:
high = len(A)-1
if high == low:
return A[low]
mid = low + int((high -low)/ 2)
# if key == A[mid] : two cases
if key == A[mid] and high - low == 0: #case 1: key is in the last pos. SHOULD STOP RUNNING HERE
index = mid
return index
elif key == A[mid] and (high - low) > 0:
if A[mid] == A[mid + 1] and A[mid]==A[mid -1]: #case 2: key isnt last and might be repeated
i = mid -1
while A[i] == A[i+1]:
i +=1
index = list(range(mid- 1, i+1))
elif A[mid] == A[mid + 1]:
i = mid
while A[i]== A[i+1]:
i += 1
index = list(range(mid, i+1))
elif A[mid] == A[mid -1]:
i = mid -1
while A[i] == A[i +1]:
i += 1
index = list(range(mid, i +1))
elif key > A[mid] and high - low > 0: # BUT CODE EXECTUES THIS LINE EVEN THOUGH PRECEDING IS ALREADY MET
index = biSearch(A, key, mid+1, high)
elif key < A[mid] and high - low > 0:
index = biSearch(A, key, low, mid -1)
return index
elif A[mid] != key: # if key DNE in A
return -1
#biSearch([1,3,5, 4, 7, 7,7,9], 1, 8, 7)
#x = biSearch([1,3,5, 4, 7,9], 1, 6, 9)
x = biSearch([1,3,5, 4, 7,9],9)
print(x)
# x = search([1,3,5, 4, 7,9], 9)
This function is not a binary search. Binary search's time complexity should be O(log(n)) and works on pre-sorted lists, but the complexity of this algorithm is at least O(n log(n)) because it sorts its input parameter list for every recursive call. Even without the sorting, there are linear statements like list(range(mid, i +1)) on each call, making the complexity quadratic. You'd be better off with a linear search using list#index.
The function mutates its input parameter, which no search function should do (we want to search, not search and sort).
Efficiencies and mutation aside, the logic is difficult to parse and is overkill in any circumstance. Not all nested conditionals lead to a return, so it's possible to return None by default.
You can use the builtin bisect module:
>>> from bisect import *
>>> bisect_left([1,2,2,2,2,3,4,4,4,4,5], 2)
1
>>> bisect_left([1,2,2,2,2,3,4,4,4,4,5], 4)
6
>>> bisect_right([1,2,2,2,2,3,4,4,4,4,5], 4)
10
>>> bisect_right([1,2,2,2,2,3,4,4,4,4,5], 2)
5
>>> bisect_right([1,2,2,2,2,3,4,4,4,4,5], 15)
11
>>> bisect_right([1,2,5,6], 3)
2
If you have to write this by hand as an exercise, start by looking at bisect_left's source code:
def bisect_left(a, x, lo=0, hi=None):
"""Return the index where to insert item x in list a, assuming a is sorted.
The return value i is such that all e in a[:i] have e < x, and all e in
a[i:] have e >= x. So if x already appears in the list, a.insert(x) will
insert just before the leftmost x already there.
Optional args lo (default 0) and hi (default len(a)) bound the
slice of a to be searched.
"""
if lo < 0:
raise ValueError('lo must be non-negative')
if hi is None:
hi = len(a)
while lo < hi:
mid = (lo+hi)//2
# Use __lt__ to match the logic in list.sort() and in heapq
if a[mid] < x: lo = mid+1
else: hi = mid
This is easy to implement recursively (if desired) and then test against the builtin:
def bisect_left(a, target, lo=0, hi=None):
if hi is None: hi = len(a)
mid = (hi + lo) // 2
if lo >= hi:
return mid
elif a[mid] < target:
return bisect_left(a, target, mid + 1, hi)
return bisect_left(a, target, lo, mid)
if __name__ == "__main__":
from bisect import bisect_left as builtin_bisect_left
from random import choice, randint
from sys import exit
for _ in range(10000):
a = sorted(randint(0, 100) for _ in range(100))
if any(bisect_left(a, x) != builtin_bisect_left(a, x) for x in range(-1, 101)):
print("fail")
exit(1)
Logically, for any call frame, there's only 3 possibilities:
The lo and hi pointers have crossed, in which case we've either found the element or figured out where it should be if it were in the list; either way, return the midpoint.
The element at the midpoint is less than the target, which guarantees that the target is in the tail half of the search space, if it exists.
The element at the midpoint matches or is less than the target, which guarantees that the target is in the front half of the search space.
Python doesn't overflow integers, so you can use the simplified midpoint test.