For practice, I solved Leetcode15. 3Sum question:
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.
My code pass all testcases from my location but get Time Limit Exceeded from leetcode. Can anyone suggest me how to improve my code?
def threeSum(nums): """ :type nums: List[int] :rtype: List[List[int]] """ if len(nums) < 3: return [] len_n, res, n_map, target = len(nums), set(), dict(), 0 nums.sort() for i, n in enumerate(nums): for j, c in enumerate(nums[i + 1:], i + 1): s = n + c n_map[s] = n_map.get(s, []) + [(i, j)] for i, n in enumerate(nums): s = target - n for k in n_map.get(s, []): if i > k[1]: res.add((nums[i], nums[k[0]], nums[k[1]])) return list(map(list, res))- 1\$\begingroup\$You currently have a small error? typo? in your code:
len_n, res, n_map. target = len(nums), set(), dict(),is not actually ok. I am not sure how this is supposed to look like, so please just correct it.\$\endgroup\$MegaIng– MegaIng2019-02-12 22:03:23 +00:00CommentedFeb 12, 2019 at 22:03 - \$\begingroup\$thank you. updated already..it is typo when i post the code..\$\endgroup\$sln– sln2019-02-13 01:21:25 +00:00CommentedFeb 13, 2019 at 1:21
1 Answer1
I think the current way of solving this problem seems an acceptable, albeit naive, way to solve it, but there are some tweaks that can enhance the readability.
variables
len_n, res, n_map, target = len(nums), set(), dict(), 0is both unclear and unnecessary.
len_n is never used,res is only used far further in the code
collections.defaultdict
You do a lot ofn_map.get(s, []). Simpler would be to definen_map as acollectcions.defaultdict(list), and then for example just don_map[s].append((i, j))
indices
You add(i, j) ton_map, only to later retrieve them as tuplek. It would be easier to use tuple unpacking:
for k, n in enumerate(nums): # i is used s = target - n for i, j in n_map[s]: if k > j: res.add((nums[k], nums[i], nums[j]))Since you only usei andj here to retrievea andb, why not save them inn_map in the first place?
n_map = defaultdict(list)for i, a in enumerate(nums): for j, b in enumerate(nums[i + 1 :], i + 1): n_map[a + b].append((j, a, b))res = set()for k, c in enumerate(nums): for j, a, b in n_map[target - c]: result = c, a, b if k > j: ...res andyield
Definingres as a set is a good choice. I think it is easier to only add the tuple tores if it is not present yet, andyield it at the same time, instead of returninglist(map(list, res)) at the end
In total this gives:
def three_sum_maarten(nums, target=0): """ :type nums: List[int] :rtype: List[List[int]]In total this gives """ if len(nums) < 3: return [] n_map = defaultdict(list) nums = sorted(nums) for i, a in enumerate(nums): for j, b in enumerate(nums[i + 1 :], i + 1): n_map[a + b].append((j, a, b)) res = set() for k, c in enumerate(nums): for j, a, b in n_map[target - c]: result = c, a, b if k > j and result not in res: yield [c, a, b] res.add(result)With this leetcode boilerplate:
class Solution: def threeSum(self, nums: 'List[int]') -> 'List[List[int]]': return list(three_sum_maarten(nums))This passes all but one scenario. The scenario it fails isnums = [0] * 3000
To tackle this scenario, you can filter all numbers so only maximum 3 of each are present innums. I do this with the help of acollections.Counter:
def prepare_nums(nums): counter = Counter(nums) for n, c in sorted(counter.items()): yield from [n] * min(c, 3)and thennums = list(prepare_nums(nums)) instead ofnums = sorted(nums)
Alternative approach
You make about half of all combinations of 2 numbers innums. One extra bit of knowledge you can use to reduce this is to take into account that at least 1 negative and 1 positive number need to be present in each triplet.
counter = Counter(nums)positives = [i for i in counter if i > 0]negatives = [i for i in counter if i < 0]for a, b in product(positives, negatives): c = -(a + b) if c not in counter: continue result = a, b, cand then only yield the correct, unique results
result = a, b, c if c == a: if counter[a] >= 2: yield result elif c == b: if counter[b] >= 2: yield result elif a > c > b: yield resultand yield 1(0, 0, 0) triplet if there are 3 or more0s present
if counter[0] >= 3: yield (0, 0, 0)This solution is about 10 times faster, and uses 30 times less memory.
- \$\begingroup\$thank you. the review make so much sense now. However, couple of question for line ` for j, a, b in n_map[target - c]:` what if no key can found on n_map? and can you explain a littit bit about why it is good choice to use yield
yield [c, a, b]here?\$\endgroup\$sln– sln2019-02-13 16:11:01 +00:00CommentedFeb 13, 2019 at 16:11 - \$\begingroup\$If there is no element with key
target-cinn_map, thisdefaultdictreturns an empty list. Thefor-loop starts iterating over it, but since it's empty, there are no iterations. On your second question, the order ofc, a, bhas no effect\$\endgroup\$Maarten Fabré– Maarten Fabré2019-02-13 16:28:06 +00:00CommentedFeb 13, 2019 at 16:28 - \$\begingroup\$understand, thank you.. great solution for your alternative approach as well\$\endgroup\$sln– sln2019-02-13 16:30:37 +00:00CommentedFeb 13, 2019 at 16:30
You mustlog in to answer this question.
Explore related questions
See similar questions with these tags.
