Swapping array indices based on indices in python - python

I am trying to set up cyclic sort, where the range of numbers are known ahead of time
def cyclic_sort(nums):
# TODO: Write your code here
i = 0
while i < len(nums):
while nums[i] - 1 != i:
nums[i], nums[nums[i] - 1] = nums[nums[i] - 1], nums[i]
i += 1
return nums
print(cyclic_sort([2, 1, 3]))
However the code just hangs however when I refactor to the below the code runs
def cyclic_sort(nums):
# TODO: Write your code here
i = 0
while i < len(nums):
while nums[i] - 1 != i:
other = nums[i] - 1
nums[i], nums[nums[i] - 1] = nums[nums[i] - 1], nums[i]
i += 1
return nums
print(cyclic_sort([2, 1, 3]))
Can someone help me understand what is happening?

nums[i] gets reassigned first, so when nums[nums[i] - 1] = ... is evaluated, it is taking the new value of nums[i], which in this case is 1.
So you get nums[0] = 1, and then nums[1-1] = 2, in your example.
You are setting the value of the current element to the new value you want to swap with, and then setting the element at the position of the value of the swapped element to the current value.
Your code is equivalent to:
x, y = nums[nums[i] - 1], nums[i]
nums[i] = x #nums[i] is set to value of element you want to swap
nums[nums[i] - 1] = y #nums[(value at swapped element) - 1] = (current elements original value)
You also don't need the while loop, which doesn't do anything useful, because you already know which position the number should be in based on the value, so you only need to check it once per position.
Swap order of assignments, since nums[i] doesn't get affected by changing the value of nums[nums[i] - 1].
def cyclic_sort(nums):
# TODO: Write your code here
i = 0
while i < len(nums):
if nums[i] - 1 != i:
nums[nums[i] - 1], nums[i] = nums[i], nums[nums[i] - 1]
i += 1
return nums
print(cyclic_sort([2, 1, 3]))

Related

what does this statement do? `nums[i] *= nums[i - 1] or 1`

While going through solutions of maximum product subarray at leetcode, I found an intriguing solution
def maxProduct(self, nums: List[int]) -> int:
B = nums[::-1]
for i in range(1, len(nums)):
nums[i] *= nums[i - 1] or 1
B[i] *= B[i - 1] or 1
# print(nums)
# print(B)
return max(nums + B)
I can't figure out how this code works, specifically what does or 1 mean in the expression
To understand this nums[i] *= nums[i - 1] or 1 You have to understand two things:
Operator Precedence (refer this Python Operator Precedence)
Shorthand operator
Let's break it down:
We have this syntax for shorthand operator:
a = a + 10 means a += 10
So, var = var * value menas var *= value(or statement)
Here, or keyword execute first. So if value B[i - 1] in current execution is zero(Boolean value False) then it will execute 0 or 1 and return 1.
This way, it can prevent 0 from multiplying all over the list item.
So,
nums[i] *= nums[i - 1] or 1
is equivalent to,
nums[i] = nums[i] * (nums[i - 1] or 1)

How can I move zeroes to the end of a list?

I have an array of integers like:
nums = [0, 1, 0, 3, 12]
I want to move all '0's to the end of it, while maintaining the relative order of the non-zero elements. So the desired output is [1, 3, 12, 0, 0].
I made the following attempts:
temp = 0
for i in range(len(nums)):
if nums[i] == 0:
nums.pop(i)
temp += 1
print(temp)
In this code, I got an error saying that nums[i] has an index out of range. Why? len(nums) == 5, so the i values should all be valid.
nums = [0,1,0,3,12]
nums.sort()
temp = 0
for i in range(len(nums)-1):
if nums[i] == 0:
print(nums[i])
temp +=1
nums.pop(i)
print(nums)
for _ in range(temp):
nums.append(0)
print(nums)
With this code, the first print gives an output of [0, 1, 3, 12], so in the final result, not all of the zeroes are moved. Why were they not all popped by the first loop?
I think one way you can get the desired output is to separate the nums list into two list that doesn't contain zeros and one does using list comprehension and concat the two list together
nums = [0,1,0,3,12]
new_list = [n for n in nums if n != 0] + [n for n in nums if n == 0]
Edit: per #DanielHao's suggestion, you can also use sorted key lambda with lambda x: not x which will then interprets zeroes as 1 and non-zeroes as 0 when sorting the list
nums = [0,1,0,3,12]
nums[:] = sorted(nums, key=lambda x: not x)
nums = [0, 1, 0, 3, 12]
temp =0
new_nums=[]
for x in range(0,len(nums)):
if(nums[x]==0):
temp +=1
else:
new_nums.append(nums[x])
while(temp != 0):
new_nums.append(0)
temp -=1
print(new_nums)
This code work efficiently to produce desired output.
Q1) When you pop out the item, the length of nums is decreased. For loops will not adjust the range being iterated over, so in this case range(len(nums)) will always be range(5) as this was the original length. This means that nums[4] is called, which results in an index error because the item that used to be at this index has moved backwards in the list due to the removal of prior values.
A solution is to use a while loop, as the condition i < len(nums) will be checked at every iteration. You will have to manually increment i.
Q2) If the indices of values in the list decreases as items are popped, some values will be skipped.
A solution in tandem with a while loop is to only increment i if the condition nums[i] == 0 is not met.
nums = [0,1,0,3,12]
nums.sort()
temp = 0
i = 0
while i < len(nums):
if nums[i] == 0:
print(nums[i])
temp += 1
nums.pop(i)
else:
i += 1
print(nums)
for _ in range(temp):
nums.append(0)
print(nums)
There are definitely easier ways to solve the same problem, as shown in other solutions, but I hope this way is easy to understand.
1] You are popping the list element so the list length changes hence getting out of index error
2] Code:
nums = [0,1,0,3,12]
# Get count of 0's
count = nums.count(0)
# List without 0's
nums = [ i for i in nums if i!=0 ]
# Add 0's to the end
[nums.append(0) for i in range(count)]
print(nums)

Swapping values in a list by index

I came across an issue swapping values in a list by using the indices as placeholders to keep track of which positive integers appeared.
The question is here (from Leetcode)
Given an unsorted integer array nums, find the smallest missing positive integer.
You must implement an algorithm that runs in O(n) time
and uses constant extra space.
Input: nums = [3,4,-1,1]
Output: 2
Example 3
My initial approach was:
for i in range(len(arr)):
while arr[i] > 0 and len(arr) > arr[i] and i != arr[i]-1 and arr[i] != arr[arr[i]-1]:
print('swapping to index ' + str(i) + ' value ' + str(arr[arr[i] - 1]))
print('swapping to index ' + str(arr[i]-1) + ' value ' + str(arr[i]))
arr[i], arr[arr[i] - 1] = arr[arr[i] - 1], arr[i]
print(arr)
But this outputs:
swapping to index 0 value -1
swapping to index 2 value 3
[-1, 4, 3, 1]
swapping to index 3 value -1
swapping to index 0 value 1
**[-1, 4, 1, -1]** (??)
Whereas if I set a variable j = arr[i] - 1 and run the same code
for i in range(len(arr)):
while arr[i] > 0 and len(arr) > arr[i] and i != arr[i]-1 and arr[i] != arr[arr[i]-1]:
j = arr[i] - 1
print('swapping to index ' + str(i) + ' value ' + str(arr[j]))
print('swapping to index ' + str(j) + ' value ' + str(arr[i]))
arr[i], arr[j] = arr[j], arr[i]
print(arr)
This swaps correctly
swapping to index 0 value -1
swapping to index 2 value 3
[-1, 4, 3, 1]
swapping to index 3 value -1
swapping to index 0 value 1
[1, 4, 3, -1] (as intended)
Could someone explain this to me?
When you swap values
arr[i], arr[arr[i] - 1] = arr[arr[i] - 1], arr[i]
then it runs something like
variable = arr[arr[i] - 1], arr[i]
arr[i], arr[arr[i] - 1] = variable
but in second line it still has to assign values one by one and first it will assign
arr[i] = ...
and later
arr[arr[i] - 1] = ...
but when it assign second value then arr[i] will already have new value instead of old value - and this makes problem.
When you use j = arr[i] - 1 and
arr[j] = ...
then j has all time the old value - ant then it works correctly.

Leetcode 3Sum: why is result duplicated?

I am trying the 15. 3Sum code challenge on LeetCode:
Given an integer array nums, return all the triplets [nums[i], nums[j], nums[k]] such that i != j, i != k, and j != k, and nums[i] + nums[j] + nums[k] == 0.
Notice that the solution set must not contain duplicate triplets.
Example 1:
Input: nums = [-1,0,1,2,-1,-4]
Output: [[-1,-1,2],[-1,0,1]]
Here is my attempt:
class Solution(object):
def threeSum(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
if len(nums) < 3:
return []
nums.sort()
ret_val = []
for i in range(len(nums) - 2):
if i - 1 >= 0 and nums[i - 1] == nums[i]:
while nums[i - 1] == nums[i] and i < len(nums) - 2:
i += 1
if nums[i - 1] == nums[i]:
break
j = i + 1
k = len(nums) - 1
# This is our target sum
target = -nums[i]
while j < k:
if j - 1 != i and nums[j - 1] == nums[j]:
while nums[j - 1] == nums[j] and j < k:
j += 1
elif k + 1 != len(nums) and nums[k + 1] == nums[k]:
while nums[k + 1] == nums[k] and j < k:
k -= 1
else:
if nums[j] + nums[k] == target:
ret_val.append([nums[i], nums[j], nums[k]])
j += 1
k -= 1
elif nums[j] + nums[k] < target:
j += 1
else:
k -= 1
return ret_val
There is apparently a bug in my code that I couldn't figure out, even after running the Python Debugger. My code gives the wrong result when I use the input of [-11, 1, -12, -12, 10]. It creates an unnecessary duplicate in the answer. I've never seen this happen before, but Python seems to run the for loop one too many times.
The expected output should be [[-11, 1, 10]], but it instead gives [[-11, 1, 10], [-11, 1, 10]]. Another interesting thing I discovered is that by removing either one or both instances of -12, it ends up giving the correct answer of [[-11, 1, 10]].
What is the problem in my code?
The reason is that your outer loop will sometimes increment i (the loop variable). This is not good, because that incremented i value is scheduled to be i's value in a next iteration of that loop, and so you will have two iterations happening with the same value for i.
Note how Python's for i in range iteration does not take into account any increment you apply to i in one of the iterations. The full range of values will be iterated, no matter what you do with i in the mean time. This is different from more traditional for loop constructs that you find in other programming languages:
for (int i = 0; i < len(nums) - 2; i++)
...as in that case the loop's i++ will just act on whatever value i has at that moment, taking into account any change you made to i during an iteration. Again, this is not how it works with an iterator such as Python's range function returns.
The solution is to just exit the current iteration when you would have wanted to increment i, since you know that the next iteration will have that incremented value for i anyhow.
So change this piece of code:
if i - 1 >= 0 and nums[i - 1] == nums[i]:
while nums[i - 1] == nums[i] and i < len(nums) - 2:
i += 1
if nums[i - 1] == nums[i]:
break
to this:
if i - 1 >= 0 and nums[i - 1] == nums[i]:
continue
That will solve the issue.

Python array slice not working as expected

I'm trying to write a program for a given array and a value, to remove all instances of that value in place and return the new length.
Example:
Given input array nums = [3,2,2,3], val = 3
It should return length = 2, with the first two elements of nums being 2.
Here is my code:
Code 1:
def removeElement(self, nums, val):
"""
:type nums: List[int]
:type val: int
:rtype: int
"""
i = 0
j = len(nums) - 1
while i <= j:
while i <= j and nums[j] != val:
j -= 1
while i <= j and nums[i] == val:
i += 1
if i <= j:
nums[i], nums[j] = nums[j], nums[i]
return len(nums[i:])
This returns the array slice in reverse order.
Input:
[3,2,2,3]
3
Output: [3,3]
Expected: [2,2]
However, if I make slight modifications at the end of the code 1, it gives me the correct output:
nums[:] = nums[i:]
return len(nums[i:])
Code 2:
def removeElement(self, nums, val):
"""
:type nums: List[int]
:type val: int
:rtype: int
"""
i = 0
j = len(nums) - 1
while i <= j:
while i <= j and nums[j] != val:
j -= 1
while i <= j and nums[i] == val:
i += 1
if i <= j:
nums[i], nums[j] = nums[j], nums[i]
nums[:] = nums[i:]
return len(nums)
I cant figure out why my code 1 doesnt work. Could someone help me understand why slice doesnt work as expected?
This would do what you intend ("... remove all instances of that value in place and return the new length"):
def remove_element(nums, val):
nums[:] = [x for x in nums if x != val]
return len(nums)
Test:
nums = [3, 2, 2, 3]
val = 3
print(remove_element(nums, val))
print(nums)
Output:
2
[2, 2]
Your first example works.
When you slice a new list is created. So in your first code sample you are creating a new list at the end containing the correct result, but never returning it.
In your second code example you are assigning the newly created list to the original list and are hence able to access the final result.

Categories

Resources