leetcode solving single number problem without extra memory - python

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).

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))

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

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).

How is an iterable created when the first argument to sum includes a for loop?

The Python documentation for sum shows that an iterable has to be passed in as the first argument for sum.
I was solving this leetcode problem 1295. Find Numbers with Even Number of Digits
:
Given an array nums of integers, return how many of them contain an even number of digits.
I was solving this the long way like this:
# # longer as in more lines of code
class Solution:
def findNumbers(self, nums: List[int]) -> int:
count = 0
for num in nums:
if len(str(num)) % 2 == 0:
count += 1
return count
but decided to look at the other suggested answers to see if I missed something. One never knows with Easy questions on Leetcode. That's when I ran into this line of code that made me wonder if I understood what was going on.
from typing import List
# one line solution
class Solution:
def findNumbers(self, nums: List[int]) -> int:
return sum(len(str(num)) %2 == 0 for num in nums)
Could someone clarify how "future" booleans lead to an iterable? Is a list being created from the for loop?
That's not something special to sum. len(str(num)) %2 == 0 for num in nums is a generator expression, it's like list comprehension but does not create a list. Instead an iterator that calculates values on demand when they're needed.
Notice that replacing the generator expression with a list comprehension gives the same result:
return sum([len(str(num)) %2 == 0 for num in nums])
But leads to more memory usage.
Then there is what Samwise mentioned, True == 1 and False == 0. You can change your code to use this fact
class Solution:
def findNumbers(self, nums: List[int]) -> int:
count = 0
for num in nums:
count += len(str(num)) % 2 == 0
return count
The ... for ... expression inside the sum() is a grammar sugar to create annoymous generator in python (see here for more details). The equlivent code shall be
def f(num):
for num in nums:
yield len(str(num)) %2 == 0
class Solution:
def findNumbers(self, nums: List[int]) -> int:
return sum(f(nums))

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)).

Why is graph DFS faster with LRU Cache than Dictionary lookup. LeetCode 128 solution difference

I'm doing leetcode 128 (longest consecutive sequence). This is the problem:
problem picture
I wrote 2 working solutions, both using DFS.
The 1st solution uses lrucache to cache the DFS function
The 2nd solution uses a dictionary to do the same thing (I think).
Solution 1 is significantly faster (~500ms vs ~4000ms) but I don't really understand why. To my understanding, they are essentially doing the same thing. What am I missing?
Here are the 2 solutions:
Solution 1 (LRU Cache) (Faster):
def longestConsecutive(self, nums: List[int]) -> int:
graph = dict()
for num in nums:
graph[num] = num+1
#functools.lru_cache(maxsize = None)
def dfs(num):
if num in graph:
return 1 + dfs(graph[num])
return 0
longest = 0
for num in nums:
longest = max(longest, dfs(num))
return longest
Solution 2 (Dictionary Lookup) (Slower):
def longestConsecutive(self, nums: List[int]) -> int:
graph = dict()
for num in nums:
graph[num] = num+1
def dfs(num):
if num in graph:
return 1 + dfs(graph[num])
return 0
longest = 0
for num in nums:
if num-1 not in graph:
longest = max(longest, dfs(num))
return longest
The code 'does the same thing' when all numbers are unique. With caching or uniqueness filtering, the runtime is guaranteed to be linear, but currently is quadratic.
This problem is finding the maximum length of a path in a directed graph, where the graph is composed of disjoint unions of paths. The DFS just follows edges until it reaches a sink.
The first version guarantees each path is walked exactly once using caching; the second version tries to guarantee each path is walked exactly once by only calling DFS if we are at the start of a path.
Consider the following input:
nums = [1,2, ..., n] + [0]*n
Then we walk the full path of length n once for each 0, so n walks in total. You can fix the second version to be O(n) by adding 5 characters to remove duplicates:
for num in set(nums):
if num-1 not in graph:
longest = max(longest, dfs(num))

Categories