need to re-write my code in less than O(n2) complexity - python

I have an algorithm written which gives result.
Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target.
You may assume that each input would have exactly one solution, and you may not use the same element twice.
You can return the answer in any order.
But it is too slow,
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
res=[]
for i in range(len(nums)):
first_val=nums[i]
second_val=target - nums[i]
for j in range(len(nums)):
if i!=j:
if nums[j]==second_val:
res.append(i)
res.append(j)
return res
return res
Could anyone assist me in rewriting this algorithm in Follow-up: Can you come up with an algorithm that is less than O(n2) time complexity?

In O(n) time complexity it can be done as below code.
logic is as, since there is solution pair in the given input, ie sum of these value make up to target, ie val1 + val2 = target, so we need to iterate through the list and search for val2 = target - val1, if we found it then return result.
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
info = {}
for i, v in enumerate(nums):
if target-v not in info:
info[v] = i
else:
return [info[target-v], i]

You can do it in O(n) with a collections.Counter:
from collections import Counter
def two_sum(nums: list[int], target: int) -> tuple[int, int]:
c = Counter(nums)
for a in c:
b = target - a
if c[b] >= 1 + (a == b):
return a, b
Building the Counter is O(n), iterating over it is also O(n), and checking for elements inside the iteration is O(1).

Related

Given an array nums of distinct integers, return all the possible permutations. You can return the answer in any order?

I am working on LeetCode problem 46. Permutations:
Given an array nums of distinct integers, return all the possible permutations. You can return the answer in any order.
I thought to solve this using backtracking. My idea is to image this problem as a binary tree and step down a path. When I get to a leaf I pop the visit array and restore to a new root number.
My code below:
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
perms = []
def dfs(curr, l):
if len(nums) == len(curr):
perms.append([int(i) for i in curr])
return
visit = []
for t in nums:
if str(t) not in curr:
visit.append(t)
dfs(curr + str(l), t)
visit.pop()
return
dfs('', nums[0])
return perms
I get wrong output for the following test case:
nums = [1,2,3]
The expected output is:
[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
But my code outputs:
[[1,1,2],[1,1,2],[1,1,3],[1,1,3],[1,2,2],[1,2,3],[1,3,2],[1,3,3]]
I don't understand why my output has lists with duplicate ones, even though I check that str(t) not in curr to avoid that duplicate use of a number.
What am I missing?
Here's the backtracking version:
def f(lst, curr = []):
if len(lst) == len(curr):
return [tuple(curr)]
result = []
for e in lst:
if e not in curr:
curr.append(e)
result.extend(f(lst, curr))
curr.pop()
return result
lst = [1, 2, 3, 4]
print(f(lst))
The main reason you have duplicate 1 in your output tuples (in the example) is that in the recursive call you are not appending the right number to curr. l is already in curr once you are in a recursive call! It should be dfs(curr + str(t), t) since you have verified that t is not in curr, it should be that number that is appended to it.
And when you make this change, there is no more need for the l parameter in dfs, as l is no longer used.
There are a few other issues however:
return perms has a wrong indentation (probably a typo in your question?)
The code assumes that numbers are always single characters when converted to string, but the code challenge indicates that a number may be 10 or may be negative, and so the way you check that a number is already in curr will not be reliable. For instance, if you first visit "10" and then want to visit "1", it will not work because if str(t) not in curr: will not be true.
Secondly, [int(i) for i in curr] will extract only one-digit numbers, and will fail if you had appended a negative number in curr, as then int('-') will raise an exception.
Not a problem, but the visited list is useless in your code. It is never used to verify anything. And also the return as last statement in dfs is not really needed, as this is the default behaviour.
I would suggest to make curr a list of numbers instead of a string.
Here is your code with the above changes applied:
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
perms = []
def dfs(curr):
if len(nums) == len(curr):
perms.append(curr)
return
for t in nums:
if t not in curr:
dfs(curr + [t])
dfs([])
return perms
It would be nice to use a generator here:
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
def dfs(curr):
if len(nums) == len(curr):
yield curr
return
for t in nums:
if t not in curr:
yield from dfs(curr + [t])
return list(dfs([]))
Finally, there is of course ... itertools:
import itertools
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
return list(itertools.permutations(nums))

LeetCode 347 Solution Time Complexity Calculation

I have been following Neetcode and working on several Leetcode problems. Here on 347 he advises that his solution is O(n), but I am having a hard time really breaking out the solution to determine why that is. I feel like it is because the nested for loop only runs until len(answers) == k.
I started off by getting the time complexity of each individual line and the first several are O(n) or O(1), which makes sense. Once I got to the nested for loop I was thrown off because it would make sense that the inner loop would run for each outer loop iteration and result in O(n*m), or something of that nature. This is where I think the limiting factor of the return condition comes in to act as the ceiling for the loop iterations since we will always return once the answers array length equals k (also, in the problem we are guaranteed a unique answer). What is throwing me off the most is that I default to thinking that any nested for loop is going to be O(n^2), which doesn't seem to be uncommon, but seems to not be the case every time.
Could someone please advise if I am on the right track with my suggestion? How would you break this down?
class Solution:
def topKFrequent(self, nums: List[int], k: int) -> List[int]:
countDict = {}
frequency = [[] for i in range(len(nums)+1)]
for j in nums:
countDict[j] = 1 + countDict.get(j, 0)
for c, v in countDict.items():
frequency[v].append(c)
answer = []
for n in range(len(frequency)-1, 0, -1):
for q in frequency[n]:
print(frequency[n])
answer.append(q)
if len(answer) == k:
return answer
frequency is mapping between frequency-of-elements and the values in the original list. The number of total elements inside of frequency is always equal or less than the number of original items in in nums (because it is mapping to the unique values in nums).
Even though there is a nested loop, it is still only ever iterating at some O(C*n) total values, where C <= 1 which is equal to O(n).
Note, you could clean up this answer fairly easily with some basic helpers. Counter can get you the original mapping for countDict. You can use a defaultdict to construct frequency. Then you can flatten the frequency dict values and slice the final result.
from collections import Counter, defaultdict
class Solution:
def top_k_frequent(self, nums: list[int], k: int) -> list[int]:
counter = Counter(nums)
frequency = defaultdict(list)
for num, freq in counter.items():
frequency[freq].append(num)
sorted_nums = []
for freq in sorted(frequency, reverse=True):
sorted_nums += frequency[freq]
return sorted_nums[:k]
A fun way to do this in a one liner!
class Solution:
def top_k_frequent(self, nums: list[int], k: int) -> list[int]:
return sorted(set(nums), key=Counter(nums).get, reverse=True)[:k]

Memoized solution to Combination IV on Leetcode gives TLE when an array is used for caching

While trying to solve Combination IV on Leetcode, I came up with this memoized solution:
def recurse(nums, target, dp):
if dp[target]!=0:
return dp[target]
if target==0:
return dp[0]
for n in nums:
if n<=target:
dp[target] += recurse(nums, target-n, dp)
return dp[target]
class Solution:
def combinationSum4(self, nums: List[int], target: int) -> int:
dp = [0]*(target+1)
dp[0] = 1
return recurse(nums, target, dp)
But this gives me a Time Limit Exceeded error.
Another memoized solution, that uses a dictionary to cache values instead of a dp array, runs fine and does not exceed time limit. The solution is as follows:
class Solution:
def combinationSum4(self, nums: List[int], target: int) -> int:
memo = {}
def dfs(nums, t, memo):
if t in memo:
return memo[t]
if t == 0:
return 1
if t < 0:
return 0
res = 0
for i in nums:
res += dfs(nums, t-i, memo)
memo[t] = res
return res
return (dfs(nums, target, memo))
Why does using a dict instead of an array improve runtime? It is not like we are iterating through the array or dict, we are only using them to store and access values.
EDIT: The test case on which my code crashed is as follows:
nums = [10,20,30,40,50,60,70,80,90,100,110,120,130,140,150,160,170,180,190,200,210,220,230,240,250,260,270,280,290,300,310,320,330,340,350,360,370,380,390,400,410,420,430,440,450,460,470,480,490,500,510,520,530,540,550,560,570,580,590,600,610,620,630,640,650,660,670,680,690,700,710,720,730,740,750,760,770,780,790,800,810,820,830,840,850,860,870,880,890,900,910,920,930,940,950,960,970,980,990,111]
target = 999
The two versions of the code are not the same. In the list version, you keep recursing if your "cached" value is 0. In the dict version, you keep recursing if the current key is not in the cache. This makes a difference when the result is 0. For example, if you try an example with nums=[2, 4, 6, 8, 10] and total=1001, there is no useful caching done in the list version (because every result is 0).
You can improve your list version by initializing every entry to None rather than 0, and using None as a sentinel value to determine if the result isn't cached.
It's also easier to drop the idea of a cache, and use a dynamic programming table directly. For example:
def ways(total, nums):
r = [1] + [0] * total
for i in range(1, total+1):
r[i] = sum(r[i-n] for n in nums if i-n>=0)
return r[total]
This obviously runs in O(total * len(nums)) time.
It's not necessary here (since total is at most 1000 in the question), but you can in principle keep only the last N rows of the table as you iterate (where N is the max of the nums). That reduces the space used from O(total) to O(max(nums)).

Contains Duplicate II Exceeded time solution

I've written a Python solution for the Contains Duplicate II leetcode problem, but when I test it I get a "time limit exceeded" message. However, I'm confused because I thought my solution is O(n) runtime. Could someone please explain?
def containsNearbyDuplicate(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: bool
"""
for i in range(len(nums)):
lookingfor = nums[i]
rest = nums[i+1: ]
if lookingfor in rest:
secondindex = rest.index(lookingfor)+i+1
if abs(i - secondindex) <= k:
return True
return False
In general, using in to search for an element in a list takes linear time.
Applying this to your code, we can observe that the search in rest takes O(len(nums)) times, which you repeat O(len(nums)) times. That leads to a quadratic runtime, causing your submission to TLE.
To get a linear runtime, use a dictionary:
class Solution:
def containsNearbyDuplicate(self, nums, k):
seen = {}
for index, element in enumerate(nums):
if element in seen and index - seen[element] <= k:
return True
seen[element] = index
return False

leetcode solving single number problem without extra memory

I've come across a solution to the single number problem from https://www.youtube.com/watch?v=-_6l_ijmcgs which proposes solution below
class Solution:
def singleNumber(self, nums: List[int]) -> int:
result = 0
for i,num in enumerate(nums):
result ^= num
return result
but isn't this technically using extra memory since space complexity will be always O(1)?
You can use a simple code to just check for the non-duplicate element in given array. Solution to Single Number question in LeetCode.
def singleNumber(self, nums: List[int]) -> int:
rslt = 0
for i in nums:
rslt ^= i
return rslt
This will take almost 280ms(196ms first time).

Categories