Python Code Optimization Problem (Lintcode Problem 1886 Moving Targets) - python

I have been working on a problem on https://www.lintcode.com/ and I have ran into a problem while doing one of the questions. The problem requires me to write a function with two parameters. A list of nums and a target num. You have to take all instances of the target from the list and move them to the front of the original list and the function cannot have a return value. The length of the list is between 1 and 1000000. You also have to do it within a time limit, which is around 400 milliseconds. I can solve the problem, I can't pass the last test case where the length of the list is 1000000. Does anyone know how I can make my code faster?
Original Problem Description for anyone who still isn't clear:
Current Code:
def MoveTarget(nums, target):
if len(set(nums)) == 1:
return nums
index = [i for i in range(len(nums)) if nums[i] == target]
for i in index:
nums.insert(0, nums.pop(i))
It works if you do:
def MoveTarget(nums, target):
count = 0
left, right = len(nums) - 1, len(nums) - 1
while left >= 0:
if nums[left] != target:
nums[right] = nums[left]
right -= 1
else:
count += 1
left -= 1
for i in range(count):
nums[i] = target
but I was wondering if there was another, less complicated way.

Here is a simple and relatively efficient implementation:
def MoveTarget(nums, target):
n = nums.count(target)
nums[:] = [target] * n + [e for e in nums if e != target]
It creates a new list with the n target values in the front and append all the other values that are not target. The input list nums is mutated thanks to the expression nums[:] = ....
The solution run in linear time as opposed to the previously proposed implementations (running in quadratic time). Indeed, insert runs in linear time in CPython.

Your code uses 2 loops. One in:
index = [i for i in range(len(nums)) if nums[i] == target]
And one in:
for i in index:
nums.insert(0, nums.pop(i))
Instead, you can combine finding the target and moving it to the front of array with only one loop, which will greatly reduce the execution time:
def MoveTarget(nums, target):
if len(set(nums)) == 1:
return nums
for num in nums:
if num == target:
nums.insert(0, nums.pop(num))

Related

Index out of range | Python

I am attempting the TwoSum Question using python and have tried the two pointer method essentially having a pointer at the beginning and end of the array, when the sum of the two indexes is greater than the value where are looking for it decrements the ending pointer one index less and if the sum is less than it increments the beginning pointer by 1. I keep getting an index out of range error and would like to know why it happens. I stepped through my code and everything seems to make sense and my test case passes but it gives me an out of range error when I get a list like this:
[-1,-2,-3,-4,-5] and the target being -8
the goal is to output index positions [2,4] using the two pointer technique
Here is my code:
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
stack = list()
n = len(nums)
j = (len(nums)-1)
i = 0
while i < n:
tempSum = nums[i] + nums[j]
if tempSum == target:
stack.append(i)
stack.append(j)
break
if tempSum > target:
j-=1
else:
i+=1
return stack
The error happens due to the logic you have built in your while loop. Whenever tempSum is larger than target, you subtract 1 from j. When working with negative numbers, this means that your loop will continue reducing j until it reaches j=-6, at which point it will produce an out of range error.
Try taking the absolute value of tempSum and target within your if statement, then the evaluation will work in case of negative numbers as well.
if abs(tempSum) > abs(target):
j-=1
else:
i+=1

simplify code in depth first search function call

I am one of the many hanging around stack overflow for knowledge and help, especially those who are out of school already. Much of my CS knowledge is learnt from this excellent web. Sometimes my question can get quite silly. Please forgive me as a newbie.
I am working on the Largest Divisible Subset problem on leetcode. There are many good solutions there, but I try to solve with my own thought first. My strategy is turning this problem into a combination problem and find the largest one who meets the divisible requirement.I use depth-first search method and isDivisible to create such a combinations. All the combinations I found meet the divisible requirement.
Here is how I would code to conduct all possible combinations of a given sequence.
def combinations(nums, path, res):
if not nums:
res.append(path)
for i in range(len(nums)):
combinations(nums[i+1:], path+[nums[i]], res)
Following is my code to create a combination of all possible divisible subsets. The code is almost exactly the same as the above code, except that that I add isDivisible to determine whether or not to add the nums[i] to the path.
def isDivisible(num, list_):
return all([num%item==0 or item%num==0 for item in list_])
def dfs(nums, path, res):
if not nums:
res.append(path)
return
for i in range(len(nums)):
# if not path or isDivisible(nums[i], path):
# path = path + [nums[i]]
# dfs(nums[i+1:], path , res)
dfs(nums[i+1:], path + ([nums[i]] if not path or isDivisible(nums[i], path) else []), res)
path = []
res = []
dfs(nums, [], res)
return sorted(res, key=len)
It works fine (almost got accepted but exceeded the time limit for large input) because of the performance of dfs. My question here is how I can simplify the last line of code in dfs by moving ([nums[i]] if not path or isDivisible(nums[i], path) else []) out of the function call, which is too bulky inside a function call. I tried to use the three lines in the comment to replace the last line of code, but it failed because path will propagate every nums[i] who meets the condition to next dfs. Could you please teach me to simplify the code and give some general suggestions. Thank you very much.
Not sure about your method, check first to see if it would get accepted.
Here is a bit simpler to implement solution (which I guess it would be one of Stefan's suggested methods):
class Solution:
def largestDivisibleSubset(self, nums):
hashset = {-1: set()}
for num in sorted(nums):
hashset[num] = max((hashset[k] for k in hashset if num % k == 0), key=len) | {num}
return list(max(hashset.values(), key=len))
Here is LeetCode's DP solution with comments:
class Solution(object):
def largestDivisibleSubset(self, nums):
"""
:type nums: List[int]
:rtype: List[int]
"""
if len(nums) == 0:
return []
# important step !
nums.sort()
# The container that keep the size of the largest divisible subset that ends with X_i
# dp[i] corresponds to len(EDS(X_i))
dp = [0] * (len(nums))
""" Build the dynamic programming matrix/vector """
for i, num in enumerate(nums):
maxSubsetSize = 0
for k in range(0, i):
if nums[i] % nums[k] == 0:
maxSubsetSize = max(maxSubsetSize, dp[k])
maxSubsetSize += 1
dp[i] = maxSubsetSize
""" Find both the size of largest divisible set and its index """
maxSize, maxSizeIndex = max([(v, i) for i, v in enumerate(dp)])
ret = []
""" Reconstruct the largest divisible subset """
# currSize: the size of the current subset
# currTail: the last element in the current subset
currSize, currTail = maxSize, nums[maxSizeIndex]
for i in range(maxSizeIndex, -1, -1):
if currSize == dp[i] and currTail % nums[i] == 0:
ret.append(nums[i])
currSize -= 1
currTail = nums[i]
return reversed(ret)
I guess maybe this LeetCode solution would be a bit closer to your method, is a recursion with memoization.
class Solution:
def largestDivisibleSubset(self, nums: List[int]) -> List[int]:
def EDS(i):
""" recursion with memoization """
if i in memo:
return memo[i]
tail = nums[i]
maxSubset = []
# The value of EDS(i) depends on it previous elements
for p in range(0, i):
if tail % nums[p] == 0:
subset = EDS(p)
if len(maxSubset) < len(subset):
maxSubset = subset
# extend the found max subset with the current tail.
maxSubset = maxSubset.copy()
maxSubset.append(tail)
# memorize the intermediate solutions for reuse.
memo[i] = maxSubset
return maxSubset
# test case with empty set
if len(nums) == 0: return []
nums.sort()
memo = {}
# Find the largest divisible subset
return max([EDS(i) for i in range(len(nums))], key=len)
References
For additional details, you can see the Discussion Board. There are plenty of accepted solutions with a variety of languages and explanations, efficient algorithms, as well as asymptotic time/space complexity analysis1, 2 in there.

Why was my algorithm for this interview a sub-optimal approach?

Given a list of integers, l = [1,5,3,2,6] and a target t = 6, return true if the list contains two distinct integers that sum to the target
I was given this question on a technical Python interview that caused me not to pass. My answer was:
def two_Sum(l, target):
for num in l:
for secondNum in l:
if num != secondNum:
if num + secondNum == target:
return True
The feedback I was given was that my solution was "not optimal". Please help me to understand why this was not the optimal solution and explain in detail what would be optimal for this case!
Your solution has a nested loop iterating the list, which means it's O(n^2) time complexity - and O(1) space, since you don't need to store any data during the iteration.
Reducing to O(n) time complexity is possible like this, coming at the cost of increasing to O(n) space complexity:
def two_sum(l, target):
s = set(l)
for n in l:
delta = target - n
if delta != n and delta in s:
return True
return False
As a slight improvement, you can even avoid to traverse the entire list, but it's still O(n):
def two_sum(l, target):
seen = set()
for n in l:
delta = target - n
if delta != n and delta in seen:
return True
seen.add(n)
return False
you can start by having two pointers (start,end), start will point to start of the list and end will point to end of list, then add them and see if it equals to your target, if equals then print or add to result.
if sum is greater then your target that means decrease your end pointer by 1 and if it's equal to or smaller than your target then increase your start pointer.
def two_Sum(l,target):
start=0
end=len(l)-1
while start!=end:
pair_sum=l[start]+l[end]
if pair_sum==target:
print l[start],l[end]
if pair_sum <= target:
start=start+1
if pair_sum > target:
end = end-1
l=[1,2,3,4,5,6,7,8,9,10]
two_Sum(l,9)
The most efficient way is to hash T-I[i] for each i and check each element as you see it
def sum2(I,T):
h = {}
for itm in I:
if itm in h:
return True
h[T-itm] = 1
return False
This will only go once through your list:
def two_sum(l, t):
s = set(l)
for n in s:
if t-n in s:
if n != t-n:
return True
return False
Your solution is O(n²), as you do a nested iteration of the whole list.
A simple solution with time complexity n log(n) would be:
sort your list
iterate doing a binary search for the complementary to target
Supposing you have binary search implemented in function bs(item, sorted_list):
def two_Sum(l, target):
l_sorted = sorted(l) # n log(n)
return any(bs(target - x, l_sorted) for x in l_sorted) # n log(n)
You can also do some other optimisation like stop iterating if you reach target/2.
Caveat: I don't garantee nor really believe this is the optimal solution, but rather intended to show you a better one and give insight to your improving your own.

Optimizing Python Function to Submit on Codewars

I have a solution for this problem on codewars.com that works when I run it in Sublime, but when I try to submit, I get this error:
Process was terminated. It took longer than 12000ms to complete
Why did my code time out?
Our servers are configured to only allow a certain amount of time for your code to execute. In rare cases the server may be taking on too much work and simply wasn't able to run your code efficiently enough. Most of the time though this issue is caused by inefficient algorithms. If you see this error multiple times you should try to optimize your code further.
The goal of the function is to find the next biggest number after a given number that you can make by rearranging the digits of a given number. For example, if I was given 216, I would need to return 261.
This is the code I have now:
import itertools
def next_bigger(n):
# takes a number like 472 and puts it in a list like so: [4, 7, 2]
num_arr = [int(x) for x in str(n)]
perms = []
total = ''
# x would be a permutation of num_arr, like [7, 2, 4]
for x in itertools.permutations(num_arr):
for y in x:
total += str(y)
perms.append(int(total))
total = ''
# bigger is all permutations that are bigger than n,
# so bigger[0] is the next biggest number.
# if there are no bigger permutations, the function returns -1
bigger = sorted([x for x in perms if x > n])
return bigger[0] if bigger else -1
I'm new to coding in Python, so is there some mistake I am making which causes my code to be extremely inefficient? Any suggestions are welcome.
Thanks for all the help you guys gave me. I ended up finding a solution from here using the Next Lexicographical Permutation Algorithm
This is my tidied up version of the solution provided here:
def next_bigger(n):
# https://www.nayuki.io/res/next-lexicographical-permutation-algorithm/nextperm.py
# https://www.nayuki.io/page/next-lexicographical-permutation-algorithm
# Find non-increasing suffix
arr = [int(x) for x in str(n)]
i = len(arr) - 1
while i > 0 and arr[i - 1] >= arr[i]:
i -= 1
if i <= 0:
return -1
# Find successor to pivot
j = len(arr) - 1
while arr[j] <= arr[i - 1]:
j -= 1
arr[i - 1], arr[j] = arr[j], arr[i - 1]
# Reverse suffix
arr[i : ] = arr[len(arr) - 1 : i - 1 : -1]
return int(''.join(str(x) for x in arr))
Why are you getting TLE (time limit exceeded)?
Because your algorithm has wrong complexity. How much permutations you will find for list with 3 elements? Only 6. But what if we use list with 23 elements? 25852016738884976640000.
This is too much for time limit.
So, if you want to have solve this problem you have to find solution without permutations. Please rethink how the numbers are written. The number 271 is bigger then 216 because the number on the second position has bigger value 7>1.
So, your solution has to find two numbers and swap them position. The number on the left have to smaller then the second one.
For example - for 111115444474444 you should find 5 and 7.
Then you swap them - and now you should sort sublist on right from the first position.
For example after swapped the values (111117444454444) you have to sort (444454444) -> (444444445). Now merge all, and you have solution.
import functools
def next_bigger(a):
a = map(int, str(a))
tmp = list(reversed(a))
for i, item_a in enumerate(reversed(a)):
for j in (range(i)):
if item_a < tmp[j]:
#you find index of number to swap
tmp[i]=tmp[j]
print(list(reversed(tmp[i:])))
tmp[j]=item_a
fin = list(reversed(tmp[i:])) + sorted(tmp[:i])
return functools.reduce(lambda x,y: x*10+y, fin)
return -1
A simple backtracking approach is to consider the digits one at a time. Starting from the most significant digit, pick the smallest number you have left that doesn't prevent the new number from exceeding the input. This will always start by reproducing the input, then will have to backtrack to the next-to-last digit (because there aren't any other choices for the last digit). For inputs like 897654321, the backtracking will immediately cascade to the beginning because there are no larger digits left to try in any of the intermediate slots.
You should sorting the num_arr in desc order and creating a number by combining the result.
Since OP required, next largest, OP needs to check starting from right, which right digit is larger then its very left digit and rotate their position.
Here is the final code:
def next_bigger(n):
num_arr = [int(x) for x in str(n)]
i = 0
i = len(num_arr) - 1
while(i > 0):
if num_arr[i] > num_arr[i-1]:
a = num_arr[i]
num_arr[i] = num_arr[i-1]
num_arr[i-1] = a
break
else:
i = i-1
newbig = "".join(str(e) for e in num_arr)
return int(newbig)
Now I edit to calculate next bigger element.
def perms(s):
if(len(s)==1):
return [s]
result=[]
for i,v in enumerate(s):
result += [v+p for p in perms(s[:i]+s[i+1:])]
return result
a=input()
b=perms(str(a))
if len(b)!=1:
for i in range(0,len(b)):
if b[i]==a:
print (b[i+1])
break
else:
print ("-1")

Find the Duplicate Number

Given an array nums containing n + 1 integers where each integer is between 1 and n (inclusive), prove that at least one duplicate number must exist. Assume that there is only one duplicate number, find the duplicate one.
My solution:
def findDuplicate(nums):
slow = fast = finder = 0
while fast is not None:
slow = nums[slow]
fast = nums[nums[fast]]
if fast is slow:
return slow
return False
nums = [1,2,2,3,4]
print findDuplicate(nums)
My above solution works and gives me o/p 2 but it doesn't work for every input for example it doesn't work for [11,15,17,17,14] or [3,1,2,6,2,3] and gives me error IndexError: list index out of range. I am not able to find patterns and am not able to track down the exact problem. Also tried to change my while condition:
while fast is not None and nums[nums[fast]] is not None:
your help will be greatly appreciated! Thank you.
Since the numbers are between 1 and n and you have been told there is only one duplicate, you can use difference between the sum of the numbers in the array and the sum of numbers from 1 to n to get the duplicate.
def findDuplicate(l):
n = len(l) - 1 # Get n as length of list - 1
return sum(l) - (n * (n + 1) / 2) # n*(n+1)/2 is the sum of integers from 1 to n
So the duplicate is the sum of the list - n*(n+1)/2
Of course, this doesn't generalize to finding duplicates for any list. For that case, you need to use #Jalepeno112 's answer.
The fact that the first one works is a fluke. Let's look at what it does on the first pass.
nums = [1,2,2,3,4]
# slow starts as index 0. So now, you've reassigned slow to be nums[0] which is 1.
# so slow equals 1
slow = nums[slow]
# now you are saying that fast equals nums[nums[0]].
# nums[0] is 1. nums[1] is 2
# so fast = 2
fast = nums[nums[fast]]
On the next pass, slow will be nums[1] which is 2. fast will be nums[nums[2]] which is nums[2] which is 2. At this point slow and fast are equal.
In your second example, you are getting an IndexError because of fast = nums[nums[fast]] If the value at nums[fast] is not a valid index, then this code will fail. Specifically in the second example, nums[0] is 11. nums doesn't have an element at index 11, so you get an error.
What you really want to be doing is performing a nested for loop on the array:
# range(0,len(nums)-1) will give a list of numbers from [0, to the length of nums-1)
# range(1, len(nums)) does the same,
# except it will start at 1 more than i is currently at (the next element in the array).
# So it's range is recomputed on each outer loop to be [i+1, length of nums)
for i in range(0,len(nums)-1):
for j in range(i+1,len(nums)):
# if we find a matching element, return it
if nums[i] == nums[j]:
return nums[i]
# if we don't find anything return False
return False
There are likely other more Pythonic ways to achieve this, but that wasn't your original question.
first you must ensure all numbers in list satisfy your constrains.
to find duplicated numbers in a list Use Counter in collections it will return each number and number of occurrence example :
>>> from collections import Counter
>>> l=Counter([11,15,17,17,14])
>>> l
Counter({17: 2, 11: 1, 14: 1, 15: 1})
to get the most common one use :
>>> l.most_common(n=1)
[(17, 2)]
where n is the number most common numbers you want to get
def duplicates(num_list):
if type(num_list) is not list:
print('No list provided')
return
if len(num_list) is 0 or len(num_list) is 1:
print('No duplicates')
return
for index,numA in enumerate(num_list):
num_len = len(num_list)
for indexB in range(index+1, num_len):
if numA == num_list[indexB]:
print('Duplicate Number:'+str(numA))
return
duplicates([11,15,17,17,14])
duplicates([3,1,2,6,2,3])
duplicates([])
duplicates([5])
l=[]
n= int(input("the number of digit is :"))
l=[0 for k in range(n)]
for j in range(0,n):
l[j]=int(input("the component is"))
print(l)
b=0; c=0
for i in range(n):
if l[i]== l[n-1-i]:
b=1;c=i
if b==1:
print("duplicate found! it is",l[c])
elif b==0:
print("no duplicate")
The answer is unfinished. It tries to convert the array to a linked list. So far it found where the slow pointer and fast pointer met, but this is the halfway solution. To get the solution, we need to initialize another pointer from the beginning of the linked list and walk towards each other. When they meet, that point is the where cycle is detected, in our question it is where the single point is:
class Solution:
def findDuplicate(self, nums: List[int]) -> int:
slow,fast=0,0
while True:
slow=nums[slow]
fast=nums[nums[fast]]
if slow==fast:
break
slow2=0
while True:
slow2=nums[slow2]
slow=nums[slow]
if slow==slow2:
return slow2

Categories