Leetcode 413: Arithmetic slices Python - python

Hi I'm trying to solve Leetcode 413: Arithmetic slices. I'm trying to start with a brute force recursive solution.
def numberOfArithmeticSlices(self, nums: List[int]) -> int:
def slices(nums: List[int], i: int):
if (i < 2):
return 0
if nums[i] - nums[i-1] == nums[i-1] - nums[i-2]:
return 1 + slices(nums, i -1)
else:
return slices(nums, i-1)
if len(nums) < 3:
return 0
return slices(nums, len(nums)-1)
This doesn't work for the test case [1,2,3,4] (it returns 2 instead of 3). In my head I know it doesn't work because when the function is called, 1 + slices([1,2,3], 2) returns 2. How can I fix my code to get the arithmetic slice coming from the entire array [1,2,3,4]?

For solving this problem you have to take two steps.
First you have to find all possible contiguous sub-arrays
You have to check them, if they are arithmetic slices.
An understandable solution which is not memory and time efficient is as below:
def numberOfArithmeticSlices(self, nums: List[int]) -> int:
if len(nums) <= 2:
return 0
sub_arrays = self.contiguous_subarray(nums) # type List[List[int]] all contiguous sub arrays with length 3 or more
count = 0
for subset in sub_arrays:
count = count + self.is_arithmetic_subset(subset)
return count
#staticmethod
def is_arithmetic_subset(subset):
if len(subset) <= 2:
return 0
diff = subset[1] - subset[0]
for i in range(2, len(subset)):
if subset[i] - subset[i - 1] != diff:
return 0
return 1
#staticmethod
def contiguous_subarray(nums):
return [nums[i:i + j] for i in range(0, len(nums)) for j in range(3, len(nums) - i + 1)]
But a solution that is little more harder to grasp but is memory and time efficient is as bellow(You could still replace the recursive call with a loop and I think you would get better results doing so):
def numberOfArithmeticSlices(self, nums: List[int]) -> int:
array_len = len(nums)
if array_len <= 2:
return 0
count = self.numberOfArithmeticSlices(nums[:array_len - 1])
diff = nums[array_len - 1] - nums[array_len - 2]
for i in range(2, array_len):
if nums[array_len - i ] - nums[array_len - i - 1] == diff:
count += 1
else:
break
return count

Related

Binary search when solution is at boundaries?

When working with binary search algorithms, one updates one of the two pointers at each iteration.
However, there are cases like the LeetCode problem where this would miss the solution.
For example, the following solution of threeSumClosest works
class Solution:
def threeSumClosest(self, nums: List[int], target: int) -> int:
nums.sort()
distance = float("inf")
for idx, num in enumerate(nums):
if num >= target:
l = 0
r = idx - 1
else:
l = idx + 1
r = len(nums) -1
while l < r:
res = num + nums[l] + nums[r]
if abs(target-res) < abs(distance):
distance = target - res
if res < target:
l +=1
else:
r -= 1
return target - distance
However, computing mid and using l = mid + 1 or r = mid - 1 misses the solution. How do you handle these cases?
I was expecting that updating l or r to mid +1 or mid -1 the algorithm would find the right solution
This question also appears in other forms, like finding the floor/ceiling of a number in a sorted list, or finding the insertion point of a number in a sorted list. In normal binary search if the middle element doesn’t match the predicate we look left or right, but in all of these problems we need to include it.
For example, given list [1, 2, 4], find insertion point of 3.
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
lo = 0
hi = len(nums) - 1
while lo < hi:
mid = lo + (hi - lo) // 2
if nums[mid] == target:
return mid
if nums[mid] < target:
lo = mid + 1
else:
hi = mid
return lo + int(nums[lo] < target)

Leetcode question '3Sum' algorithm exceeds time limit, looking for improvement

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/

Optimize solution runtime to 3Sum problem

I am trying to find all unique triplets within a given set of integers that sum to zero. I have the following code which to me works logically but I keep getting a timeout error. Any advice on what I am missing or how to optimize? I have gone over it several times and I can't see how to make it any faster. Thanks in advance!
class Solution:
def threeSum(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
nums.sort()
for i in range(len(nums)-2):
if i > 0 and nums[i] == nums[i-1]:
continue
target = 0 - nums[i]
j = i +1
k = len(nums) - 1
tgt_sum = 0
trpls = []
while j < k:
tgt_sum = nums[j] + nums[k]
if tgt_sum == target:
x = []
x.append([i,j,k])
if all(item in trpls for item in x) == True:
trpls_list.append([i,j,k])
elif tgt_sum < target:
j += 1
elif tgt_sum > target:
k -= 1
else:
k -= 1
test = Solution()
given_nums = [-1,0,1,2,-1,-4]
print(test.threeSum(given_nums))

Subset sum (dynamic programming) in Python - complexity problem

I have a problem with some implementation of a function that solves the problem of a subset sum in Python.
We have dynamic programming here, so the complexity should be polynomial.
The problem is that if the size of set grows linearly and the size of the numbers also increases linearly (of course it is not a logarithm of numbers) then the code execution time can grow exponentially.
My guess is that this may be due to a particular implementation.
Is it possible to improve it somehow?
Code in Python:
def subsetsum(array,num):
if num == 0 or num < 1:
return None
elif len(array) == 0:
return None
else:
if array[0] == num:
return [array[0]]
else:
with_v = subsetsum(array[1:],(num - array[0]))
if with_v:
return [array[0]] + with_v
else:
return subsetsum(array[1:],num)
You're using slices to pass suffixes of array, this will make a copy which has linear runtime. To avoid that you can pass indices instead.
Another advantage is that indices are hashable, so you can cache (or memoize) and avoid recomputing answers:
from functools import lru_cache
def ssum(array, N):
#lru_cache(maxsize=None)
def subsetsum(idx, num):
if num < 1 or idx >= len(array):
return frozenset()
if array[idx] == num:
return frozenset([idx])
with_v = subsetsum(idx + 1, num - array[idx])
if with_v:
return with_v | frozenset([idx])
else:
return subsetsum(idx + 1, num)
return list(array[i] for i in subsetsum(0, N))
>>> ssum([1,1,2], 4)
[1, 1, 2]
Unfortunately, there's still the cost of copying the answer obtained from the suffix

Find whether the largest element in the array is at least twice as much as every other number in the array?

I'm trying to run a program that finds the index of the number that is at least two times larger than all other number in the array.
Here's my code
def dominantIndex(self, nums):
max_num = max(nums)
max_i =nums.index(max_num)
if len(nums) == 1:
return nums.index(max_num)
for num in nums:
if max_num >= 2*num:
return num.index(max_num)
return -1
However , it doesn't work perfectly for all inputs. Could someone please fix it and check for inputs like :
[1,0]
[0,3,4,8]
[0,3,5,2]
This checks for many possible input problems.
Then it sorts the list to get the answer you are looking for. I decided to sort, for simplicity, but you could use other methods as well. I added comments so everything is clear, especially about the input tests, as asked.
def dominantIndex(nums):
# If the array is empty or None or not a list, return -1
if not nums or type(nums) != list:
return -1
# If the array is of length 1, return the only index, 0
elif len(nums) == 1:
return 0
sorted_numbers = sorted(nums)
# If the highest number is twice the second largest, return it's index
if sorted_numbers[-2] * 2 <= sorted_numbers[-1]:
return nums.index(sorted_numbers[-1])
else:
return -1
There actually is a library function nlargest in the heapq module.
>>> L1 = [1, 0]
>>> L2 = [0, 3, 4, 8]
>>> L3 = [0, 4, 5, 2]
>>>
>>> from heapq import nlargest
>>> def dom_ind(nums):
... a, b = nlargest(2, range(len(nums)), key=nums.__getitem__)
... return a if nums[a] >= 2 * nums[b] else -1
...
>>>
>>> dom_ind(L1)
0
>>> dom_ind(L2)
3
>>> dom_ind(L3)
-1
Use below code
def check(nums):
if len(nums) == 1:
return 0
max_num = max(nums)
ind = nums.index(max_num)
updated_array = map(lambda x: x if x != max_num else -1, nums)
if max_num >= 2*max(updated_array):
return ind
return -1
Output:
check([1,0])
0
>>> check([0,3,4,8])
3
>>> check([0,3,5,2])
-1
You can get the second highest as well;:
def dominantIndex(nums):
max_num = max(nums)
secondHighest = max( n for n in nums if n != max_num)
if max_num > 2 * secondHighest:
return nums.index(max_num)
return -1
print(dominantIndex( [1,2,39,7]))
print(dominantIndex( [1,2,3,5]))
Output:
2
-1
I'd iterate over the list and find the two largest elements. Then, if the largest element is at least twice as large as the second largest, you can return its index.
def dominantIndex(nums):
# Assumption - nums has a length of at least 2.
# Bootsrap the rist two indexes
if nums[0] > nums[1]:
max_ind = 0
next_ind = 1
else:
max_ind = 1
next_ind = 0
# Go over the rest
for i in range(2, len(nums)):
if nums[i] > nums[max_ind]:
next_ind = max_ind
max_ind = i
elif nums[i] > nums[next_ind]:
next_ind = i
if nums[max_ind] >= nums[next_ind] * 2:
retrun max_ind
return -1
I think none of the other suggested solutions works if the largest number appears multiple times in nums. This does:
def dominantIndex(self, nums):
s = sorted(nums)
if s[-1] >= 2 * s[-2]:
return nums.index(s[-1])
else:
return -1
In addition, you might want to check if nums has more than one element.

Categories