Remove Duplicates from Sorted Array in O(1) Space - python

Question: https://leetcode.com/problems/remove-duplicates-from-sorted-array/description/
Basically, duplicates in a sorted array needs to be removed without creating a new one. I have tested my solution, and it falters when there are 3, 5, 7 (and so on) number of duplicates of the same number. What techniques can I try?
Code:
def removeDuplicates(nums):
i = 0 # moving pointer
end = len(nums)-1 # points to the end of the array
# iterates over the array
while i != end:
# if current number is equal to the next
if (nums[i] == nums[i+1]):
# pop it off
nums.pop(i)
# decrement the length of array
end -= 1
i += 1 # increments the moving pointer
return i + 1
nums = [0,0,1,1,1,2,2,3,3,4]
print(removeDuplicates(nums))

The way to remove duplicates from a sorted list in O(1) space and O(n) time is to pack the unique elements at the beginning of the list, and then truncate the list:
def removeDuplicates(nums):
#new length
newlen = 0
for val in nums:
if newlen == 0 or nums[newlen-1] != val:
# no matter what, we've already retrieved nums[newlen],
# so this write doesn't interfere with the rest of the loop
nums[newlen] = val
newlen += 1
# delete the unused space at the end of the list, all at once
if newlen < len(nums):
del nums[newlen:]
return newlen
nums = [0,0,1,1,1,2,2,3,3,4]
print(removeDuplicates(nums))
print(nums)
Your original code fails, because you remove elements from the start of the array as you're iterating through the end. The iteration remembers its position in the array in i, and when you remove an element, the elements after it are shifted into lower positions.
You could fix that by putting the i += 1 into an else, but the resulting implementation would still take O(N^2) time, which isn't really acceptable.

Related

What is the time complexity of popping an element and appending the same element to a Python list?

So I know popping an element from an an array is O(n) since you have to shift all the elements over by one. BUT, what if you popped an element AND appended that same element to the back of the array, all in one line. Would this be constant operation O(1) since you don't have to shift anything? Please see my code below with the comment.
def moveZeroes(self, nums: List[int]) -> None:
index = 0
zeroes = nums.count(0)
counter = 0
while True:
if counter == zeroes:
break
if nums[index] == 0:
nums.append(nums.pop(index)) # THIS LINE RIGHT HERE
counter += 1
else:
index += 1
When you pop any element using its index, you make at most len(array) shifts to reduce the length by 1.
You can append an element into an array in O(1) time complexity.
However, when you call nums.append(nums.pop(index)). It will first do step 1 and then do step 2. So overall, you still do O(n) operations.

Why does my list of zeroes result in IndexError?

I get an error on occurence[j] = 0. I do not really understand the origins of this error in my code, as it is of length dna, because I append at the top of the code len(dna) zeroes and then I assign some value to the same list occurence in my nested loop, where j can only reach the value of len(dna).
for i in range(len(dna)):
occurence.append(0)
print(f"{len(dna)}")
print(f"{len(occurence)}")
#Calculating consecutive sequences and creating a 2D array occurence...
for i in types:
for j in range(len(dna)):
if (dna[j:j+len(i)] != i):
occurence[j] = 0
else:
space = len(i)
while(dna.find(i, space+len(i)) != -1):
index = dna.find(i, space+len(i))
space = space + len(i)
if (index == len(i)):
occurence[j] += 1
for k in range(len(occurence)):
maximum = 0
if(occurence[k] > maximum):
maximum = occurence[k]
counts.append(maximum)
maximum = 0
occurence.clear()
At the end of the first iteration over types, you call occurence.clear(), which will result in occurence being an empty list. Then, when you try to access occurence[j] on the second iteration, this throws an IndexError since the list is empty.
I think you instead want to initialize your list inside the for i in types loop, e.g.:
for i in types:
occurence = [0] * len(dna)
for j in range(len(dna)):
...
You would then not need to call the clear method on your list, since it would be redefined as a list of zeroes on each iteration.

How to loop my definition? ~sum values untill limit is reached and loop again for the rest

I'm struggling with while looping.
I have a list with Widths (double or integer, doesn't matter - don't need precision).
Basically I need number of items that sum is lower than limit.
Now it finds only the first number.
I'm not able to adapt while loop, so it would start calculation over again with the rest of items.
This code give 6 as output, cause sum(100,300,30,100,50,80) < limit = 850.
The desired loop would do this:
1st iteration: start from 0 until sum meet limit: [100,300,30,100,50,80,400,120,500,75,180] -> give 6
2nd iteration: start from the next(last index from 1st run +1) item and iterate over the rest: 400,120,500,75,180 -> give 2
3rd: iterate over 500,75,180 -> give 3
Number of widths = unknown
if width > limit -> break the code
Widths = [100,300,30,100,50,80,400,120,500,75,180]
def items(nums,limit):
sum=0
for i in range(0,len(nums)):
sum += nums[i]
if sum>limit-1:
return i
print (items(Widths,850))
I'd like to have output like this:
[6,2,3]
a return immediately exits out of the function. You need to store instead of returning, and go from there.
I have also pointed out some comments in code as well that should help.
Widths = [100,300,30,100,50,80,400,120,500,75,180]
def items(nums,limit):
acc = 0 #do not use sum as a variable name. it "shadows" or hides the builtin function with same name
length = 0
result = []
for num in nums: #You do not really need indexes here, so you can directly iterate on items in nums list.
acc += num
if acc >= limit: #greater than or equal to.
result.append(length)
acc = num
length = 1
else:
length += 1
result.append(length) #if you need the last length even if it does not add up.
return result
print (items(Widths,850))
#Output:
[6, 2, 3]

Min swaps to sort unordered consecutive integers

You are given an unordered array consisting of consecutive integers [1, 2, 3, ..., n] without any duplicates. You are allowed to swap any two elements. You need to find the minimum number of swaps required to sort the array in ascending order. The code what i have got ,timed out for a few test cases. Is there any way that we can optimise the code?
My code is as follows:
def minimumSwaps(arr):
lst=[ele+1 for ele in range(len(arr))]
cnt=0
for i in range(len(arr)):
if(lst[i]!=arr[i]):
k=arr.index(lst[i])
arr[i],arr[k]=arr[k],arr[i]
cnt=cnt+1
return cnt
You don't really need to generate a reference list lst because you should know that n in arr should be in the index n-1 to be ascending. Also, doing k=arr.index(lst[i]) requires O(n) time to search, and which is complete unnecessary. Here's my solution:
def minimumSwaps(arr):
total_swaps = 0
start = 0
while start < len(arr):
if arr[start] == start + 1:
start += 1
continue
arr[arr[start] - 1], arr[start] = arr[start], arr[arr[start] - 1]
total_swaps += 1
return total_swaps
If I guess it right, it's a question on Hackerrank, and this was my solution by the time passed the tests. :P
This algorithm starts at position one and swaps the items under the cursor directly into their correct positions. When the correct item is swapped into this position it increments the cursor to the next item and repeats the process.
This is more efficient than stepping through the array sequentially, swapping the correct item into each position, because you don't have to work out exactly where the correct item is located within the tail of the list.
I used the logic given in link here: https://www.youtube.com/watch?v=Def9kMtZCNA
def minimumSwaps(arr):
# element usually at element value-1 index value
# eg, 1 at index 0, 2 at index 1.....
counter=0
for i in range(len(arr)):
if arr[i] != i+1:
while arr[i] != i+1:
temp=arr[arr[i]-1]
arr[arr[i]-1]=arr[i]
arr[i]=temp
counter+=1
else:
continue
return counter

i want to find out the index of the elements in an array of duplicate elements

a=[2, 1, 3, 5, 3, 2]
def firstDuplicate(a):
for i in range(0,len(a)):
for j in range(i+1,len(a)):
while a[i]==a[j]:
num=[j]
break
print(num)
print(firstDuplicate(a))
The output should be coming as 4 and 5 but it's coming as 4 only
You can find the indices of all duplicates in an array in O(n) time and O(1) extra space with something like the following:
def get_duplicate_indices(arr):
inds = []
for i, val in enumerate(arr):
val = abs(val)
if arr[val] >= 0:
arr[val] = -arr[val]
else:
inds.append(i)
return inds
get_duplicate_indices(a)
[4, 5]
Note that this will modify the array in place! If you want to keep your input array un-modified, replace the first few lines in the above with:
def get_duplicate_indices(a):
arr = a.copy() # so we don't modify in place. Drawback is it's not O(n) extra space
inds = []
for i, val in enumerate(a):
# ...
Essentially this uses the sign of each element in the array as an indicator of whether a number has been seen before. If we come across a negative value, it means the number we reached has been seen before, so we append the number's index to our list of already-seen indices.
Note that this can run into trouble if the values in the array are larger than the length of the array, but in this case we just extend the working array to be the same length as whatever the maximum value is in the input. Easy peasy.
There are some things wrong with your code. The following will collect the indexes of every first duplicate:
def firstDuplicate(a):
num = [] # list to collect indexes of first dupes
for i in range(len(a)-1): # second to last is enough here
for j in range(i+1, len(a)):
if a[i]==a[j]: # while-loop made little sense
num.append(j) # grow list and do not override it
break # stop inner loop after first duplicate
print(num)
There are of course more performant algorithms to achieve this that are not quadratic.

Categories