Step 3 Lecture 1 : Solve problems on arrays easy¶
Find the Largest element in an array¶
Example 1:
Input: arr[] = {2,5,1,3,0};
Output: 5
Explanation: 5 is the largest element in the array.
Example2:
Input: arr[] = {8,10,5,7,9};
Output: 10
Explanation: 10 is the largest element in the array.
Approach 1 (Brute force - Iterative)¶
def largest_element(nums):
large = nums[0]
for num in nums:
large = max(large,num)
return large
print(largest_element([2,5,1,3,0]))
print(largest_element([8,10,5,7,9]))
5 10
complexity¶
O(N) : N = len(nums)
O(1)
Approach 2 (More Pythonic)¶
def largest_element(nums):
return max(nums)
print(largest_element([2,5,1,3,0]))
print(largest_element([8,10,5,7,9]))
5 10
Approach 3 (Brute force - recursive)¶
large(i) = max(large(i-1),nums[i-1])
large(1) = nums[0]
def largest_element(nums):
n = len(nums)
def large(n):
if n==1:
return nums[0]
return max(nums[n-1],large(n-1))
return large(n)
print(largest_element([2,5,1,3,0]))
print(largest_element([8,10,5,7,9]))
5 10
complexity¶
O(N) : N = len(nums)
O(N) : N = len(nums)
Find Second Smallest and Second Largest Element in an array¶
Example 1:
Input: [1,2,4,7,7,5]
Output: Second Smallest : 2
Second Largest : 5
Explanation: The elements are as follows 1,2,3,5,7,7 and hence second largest of these is 5 and second smallest is 2
Example 2:
Input: [1]
Output: Second Smallest : -1
Second Largest : -1
Explanation: Since there is only one element in the array, it is the largest and smallest element present in the array. There is no second largest or second smallest element present.
Approach 1 (Sorting)¶
- This approach only works if the element does not have duplicates
def second_elements(nums):
sorted_nums = sorted(nums)
n = len(nums)
return sorted_nums[1],sorted_nums[n-2]
print(second_elements([1,2,9,7,5]))
(2, 7)
complexity¶
O(N log N ) : N = len(nums)
O(1)
Approach 2 (Two pass)¶
- first pass : find the largest
- second pass : find the second
this works even with duplicates
def second_elements(nums):
smallest,largest = min(nums),max(nums)
second_small,second_large = float("inf"),float("-inf")
for num in nums[1:]:
if num > smallest and num < second_small:
second_small = num
if num < largest and num > second_large:
second_large = num
return second_small,second_large
print(second_elements([1,2,4,7,7,5]))
(2, 5)
Complexity¶
O(N) : N = len(nums)
O(1)
Approach 3 ( Single Pass)¶
- implement them separately
- second_smallest
- second_largest
def second_small(nums):
smallest,small = float("inf"),float("inf")
for num in nums:
if num < smallest:
smallest = num
elif num > smallest and num < small:
small = num
return small
def second_large(nums):
largest,large = -float("inf"),-float("inf")
for num in nums:
if num > largest:
largest = num
elif num < largest and num > large:
large = num
return large
print(second_small([1,2,4,7,7,5]))
print(second_large([1,2,4,7,7,5]))
2 5
Complexity¶
O(N) : N = len(nums)
O(1)
Check if an Array is Sorted¶
Given an array of size n, write a program to check if the given array is sorted in (ascending / Increasing / Non-decreasing) order or not. If the array is sorted then return True, Else return False.
{1,2,3,4,5}
True
{5,4,6,7,8}
False
Approach 1 (Brute Force)¶
- forward pass
def is_sorted(nums):
n = len(nums)
for i in range(1,n):
if nums[i] < nums[i-1]:
return False
return True
print(is_sorted([1,2,3,4,5]))
print(is_sorted([5,4,6,7,8]))
True False
Complexity¶
O(N) : N = len(nums)
O(1)
Check if the array is rotated sorted¶
[3,4,5,1,2]
True
[2,1,3,4]
False
[1,2,3]
True
Approach 1¶
- next_index(i) = (i+1) %n
- if rotation breaks more than 1 times ,then unsorted
e.g. [3,4,5,1,2] only 1 break i.e. 5 -> 1
def is_rotated(nums):
n = len(nums)
count =0
for i in range(n):
next_idx = (i+1) % n
if nums[i]>nums[next_idx]:
count +=1
return count <2
print(is_rotated([3,4,5,1,2]))
print(is_rotated([2,1,3,4]))
print(is_rotated([1,2,3]))
True False True
Complexity¶
O(N) : N= len(nums)
O(1)
Remove Duplicates in-place from Sorted Array¶
Example 1:
Input: arr[1,1,2,2,2,3,3]
Output: arr[1,2,3,_,_,_,_]
Explanation: Total number of unique elements are 3, i.e[1,2,3] and Therefore return 3 after assigning [1,2,3] in the beginning of the array.
Example 2:
Input: arr[1,1,1,2,2,3,3,3,3,4,4]
Output: arr[1,2,3,4,_,_,_,_,_,_,_]
Explanation: Total number of unique elements are 4, i.e[1,2,3,4] and Therefore return 4 after assigning [1,2,3,4] in the beginning of the array.
Approach 1 (Forward Pass with 2 ptrs)¶
left cursor = 0
right = 1 -> n-1
once nums[left] < nums[right] : left and right are relative correct
swap (left+1 , right)
if right = left +1 , swap(left+1, right) = swap(left+1, left+1) => unchanged
def remove_duplicates_sorted(nums):
write_pos = 1
n = len(nums)
for i in range(1,n):
if nums[i] != nums[i-1]:
nums[write_pos] = nums[i]
write_pos +=1
return nums
print(remove_duplicates_sorted([1,1,2,2,2,3,3]))
print(remove_duplicates_sorted([1,1,1,2,2,3,3,3,3,4,4]))
[1, 2, 3, 2, 2, 3, 3] [1, 2, 3, 4, 2, 3, 3, 3, 3, 4, 4]
complexity¶
O(N) : N = len(nums)
O(1)
Left Rotate the Array by One¶
Example 1:
Input: N = 5, array[] = {1,2,3,4,5}
Output: 2,3,4,5,1
Example 2:
Input: N = 1, array[] = {3}
Output: 3
approach 1¶
- save the first element
- nums[i] = nums[i+1]
- nums[end] = saved element
def rotate_left(nums):
n = len(nums)
if n==1:
return nums
first = nums[0]
for i in range(n-1):
nums[i] = nums[i+1]
nums[n-1] = first
return nums
print(rotate_left([1,2,3,4,5]))
print(rotate_left([3]))
[2, 3, 4, 5, 1] [3]
Complexity¶
O(N) : N= len(nums)
O(1)
Rotate array by K elements¶
Example 1:
Input: N = 7, array[] = {1,2,3,4,5,6,7} , k=2 , right
Output: 6 7 1 2 3 4 5
Example 2:
Input: N = 6, array[] = {3,7,8,9,10,11} , k=3 , left
Output: 9 10 11 3 7 8
Approach 1 (Extra array duplication)¶
- decrease k = k & n
- result[i] = nums[(i-k)%n]
def rotate_k(nums,k):
n = len(nums)
result = [0] * n
k = k %n
for i in range(n):
next_idx = (i - k + n)%n
result[i] = nums[next_idx]
return result
print(rotate_k([1,2,3,4,5,6,7],2))
print(rotate_k([3,7,8,9,10,11],3))
[6, 7, 1, 2, 3, 4, 5] [9, 10, 11, 3, 7, 8]
Complexity¶
O(N) : N = len(nums)
O(N) : N = len(nums)
Approach 2 (Use tripple reversal method)¶
- reverse n elements
- reverse first k elements
- reverse last n-k elements
e.g.
- 1,2,3,4,5,6,7
- 7,6,5,4,3,2,1 (reverse n elements)
- 6,7,5,4,3,2,1 (reverse first k elements )
- 6,7,1,2,3,4,5 (revese last n-k elements)
def rotate_k(nums,k):
def reverse(left,right):
while left < right:
nums[left],nums[right] = nums[right],nums[left]
left +=1
right -=1
n = len(nums)
k = k %n
reverse(0,n-1)
reverse(0,k-1)
reverse(k,n-1)
return nums
print(rotate_k([1,2,3,4,5,6,7],2))
print(rotate_k([3,7,8,9,10,11],3))
[6, 7, 1, 2, 3, 4, 5] [9, 10, 11, 3, 7, 8]
Complexity¶
O(N) : N = len(nums)
O(1)
Move all Zeros to the end of the array¶
Input: 1 ,0 ,2 ,3 ,0 ,4 ,0 ,1
Output: 1 ,2 ,3 ,4 ,1 ,0 ,0 ,0
Input: 1,2,0,1,0,4,0
Output: 1,2,1,4,0,0,0
Approach 1 (Relative order will not be preserved)¶
- left and right ptr
- swap when found
def move_zeros(nums):
n = len(nums)
left =0
right = n-1
while left < right:
if nums[left] !=0:
left +=1
elif nums[right] ==0:
right -=1
else:
nums[left],nums[right] = nums[right],nums[left]
left +=1
right -=1
return nums
print(move_zeros([1 ,0 ,2 ,3 ,0 ,4 ,0 ,1]))
print(move_zeros([1,2,0,1,0,4,0]))
[1, 1, 2, 3, 4, 0, 0, 0] [1, 2, 4, 1, 0, 0, 0]
Approach 2 (Preserve Relative Order without swapping)¶
- use write pos
- fill remaining 0 manually
def move_zeros(nums):
n = len(nums)
write_pos = 0
for i in range(n):
if nums[i] !=0:
nums[write_pos] = nums[i]
write_pos +=1
for i in range(write_pos,n):
nums[i] = 0
return nums
print(move_zeros([1 ,0 ,2 ,3 ,0 ,4 ,0 ,1]))
print(move_zeros([1,2,0,1,0,4,0]))
[1, 2, 3, 4, 1, 0, 0, 0] [1, 2, 1, 4, 0, 0, 0]
Approach 3 (Preserve the relative order)¶
- use swap as well as write_pos
- swap current non zero element with the write_pos
- no need for setting 0 in the end
def move_zeros(nums):
n = len(nums)
def swap(i,j):
nums[i],nums[j] = nums[j],nums[i]
write_pos = 0
for i in range(n):
if nums[i] !=0:
swap(i,write_pos)
write_pos +=1
return nums
print(move_zeros([1 ,0 ,2 ,3 ,0 ,4 ,0 ,1]))
print(move_zeros([1,2,0,1,0,4,0]))
[1, 2, 3, 4, 1, 0, 0, 0] [1, 2, 1, 4, 0, 0, 0]
Complexity¶
O(N) : N = len(nums)
O(1)
Linear Search¶
Input: arr[]= 1 2 3 4 5, num = 3
Output: 2
Input: arr[]= 5 4 3 2 1, num = 5
Output: 0
Approach 1 (Iterative)¶
def linear_search(nums,target):
for i in range(len(nums)):
if nums[i] == target:
return i
print(linear_search([1,2,3,4,5],3))
print(linear_search([5,4,3,2,1],5))
2 0
Complexity¶
O(N) : N = len(nums)
O(1)
Approach 2 (Recursive)¶
def linear_search(nums,target):
n = len(nums)
def search(pos):
if pos == n:
return -1
if nums[pos] == target:
return pos
return search(pos+1)
return search(0)
print(linear_search([1,2,3,4,5],3))
print(linear_search([5,4,3,2,1],5))
2 0
Find the missing number in an array¶
Given an integer N and an array of size N-1 containing N-1 numbers between 1 to N. Find the number(between 1 to N), that is not present in the given array.
Input Format: N = 5, array[] = {1,2,4,5}
Result: 3
Input Format: N = 3, array[] = {1,3}
Result: 2
Approach1 (Brute Force)¶
- linear search every element
def missing_num(nums):
n = len(nums)
def linear_search(target):
for num in nums:
if num == target:
return True
return False
for i in range(1,n+2):
if not linear_search(i):
return i
print(missing_num([1,2,4,5]))
print(missing_num([1,3]))
3 2
Complexity¶
O(N^2) : N = len(nums)
O(1)
Approach 2 (Use Set)¶
Instead of linear search , lets build a set
def missing_num(nums):
n = len(nums)
nums_set = set(nums)
for i in range(1,n+2):
if i not in nums_set:
return i
print(missing_num([1,2,4,5]))
print(missing_num([1,3]))
3 2
Complexity¶
O(N) : N = len(nums)
O(N) : N = len(nums)
Approach 3 (Sorting)¶
- First Sort the array
- Find the missing number
def missing_num(nums):
n = len(nums)
sorted_nums = sorted(nums)
for i in range(n):
if sorted_nums[i] != i+1:
return i+1
print(missing_num([1,2,4,5]))
print(missing_num([1,3]))
3 2
Complexity¶
O(N log N) : N = len(nums)
O(N)
Approach 4 (Maths)¶
- sum of first n numbers = n * (n +1) /2 = S1
- sum of nums = S2
- result = S1 - S2
def missing_num(nums):
n = len(nums)
s1 = (n+1) * (n+2) / 2
s2 =0
for num in nums:
s2 += num
return int(s1 - s2)
print(missing_num([1,2,4,5]))
print(missing_num([1,3]))
3 2
Complexity¶
O(N)
O(1)
Count Maximum Consecutive One's in the array¶
Input: prices = {1, 1, 0, 1, 1, 1}
Output: 3
Input: prices = {1, 0, 1, 1, 0, 1}
Output: 2
Approach1¶
- Keep track of current count
- result = max(count)
def max_ones(nums):
result = 0
count =0
for num in nums:
if num ==1:
count +=1
result = max(result,count)
else:
count =0
return result
print(max_ones([1, 1, 0, 1, 1, 1]))
print(max_ones([1, 0, 1, 1, 0, 1]))
3 2
Complexity¶
O(N) : N = len(nums)
O(1)
Union of Two Sorted Arrays¶
Given two sorted arrays, arr1, and arr2 of size n and m. Find the union of two sorted arrays. The union of two arrays can be defined as the common and distinct elements in the two arrays.NOTE: Elements in the union should be in ascending order.
Input:
n = 5,m = 5.
arr1[] = {1,2,3,4,5}
arr2[] = {2,3,4,4,5}
Output:
{1,2,3,4,5}
Input:
n = 10,m = 7.
arr1[] = {1,2,3,4,5,6,7,8,9,10}
arr2[] = {2,3,4,4,5,11,12}
Output: {1,2,3,4,5,6,7,8,9,10,11,12}
Approach 1 (Use Sets)¶
def union_sorted(nums1,nums2):
result = set()
for num in nums1:
result.add(num)
for num in nums2:
result.add(num)
return list(result)
print(union_sorted([1,2,3,4,5],[2,3,4,4,5]))
print(union_sorted([1,2,3,4,5,6,7,8,9,10],[2,3,4,4,5,11,12]))
[1, 2, 3, 4, 5] [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
Complexity¶
- N : len(nums1) , M = len(nums2)
- O(N) + O(M)
- O(N+M)
Approach 2 (More pythonic)¶
def union_sorted(nums1,nums2):
result = set()
result.update(nums1)
result.update(nums2)
return list(result)
print(union_sorted([1,2,3,4,5],[2,3,4,4,5]))
print(union_sorted([1,2,3,4,5,6,7,8,9,10],[2,3,4,4,5,11,12]))
[1, 2, 3, 4, 5] [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
Approach 3 (Two Ptrs)¶
def union_sorted(nums1,nums2):
union = []
ptr1 , ptr2 =0,0
m,n = len(nums1),len(nums2)
while ptr1 < m and ptr2 < n:
if nums1[ptr1] <= nums2[ptr2]:
if len(union) ==0 or union[-1] != nums1[ptr1]:
union.append(nums1[ptr1])
ptr1 +=1
else:
if len(union) ==0 or union[-1] != nums2[ptr2]:
union.append(nums2[ptr2])
ptr2 +=1
while ptr1 < m:
if len(union) ==0 or union[-1] != nums1[ptr1]:
union.append(nums1[ptr1])
ptr1 +=1
while ptr2 < n:
if len(union) ==0 or union[-1] != nums2[ptr2]:
union.append(nums2[ptr2])
ptr2 +=1
return union
print(union_sorted([1,2,3,4,5],[2,3,4,4,5]))
print(union_sorted([1,2,3,4,5,6,7,8,9,10],[2,3,4,4,5,11,12]))
[1, 2, 3, 4, 5] [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
Complexity¶
- N = len(nums1) and M = len(nums2)
- O(M + N)
- O(1)
Find the number that appears once, and the other numbers twice¶
Input Format: arr[] = {2,2,1}
Result: 1
Input Format: arr[] = {4,1,2,1,2}
Result: 4
Approach 1 (Freq Count)¶
from collections import Counter
def get_single_sum(nums):
freq_map = Counter(nums)
for num,count in freq_map.items():
if count ==1:
return num
return -1
print(get_single_sum([2,2,1]))
print(get_single_sum([4,1,2,1,2]))
1 4
COmplexity¶
- N = len(nums)
- O(N)
- O(N)
Approach 2(Sorting)¶
def get_single_sum(nums):
nums.sort()
n = len(nums)
for i in range(1,n-1):
if nums[i] != nums[i+1] and nums[i] != nums[i-1]:
return nums[i]
if nums[0] != nums[1]:
return nums[0]
return nums[-1]
print(get_single_sum([2,2,1]))
print(get_single_sum([4,1,2,1,2]))
1 4
COmplexity¶
- N = len(nums)
- O(N log N)
- O(1)
Approach 3 (Cleaner)¶
def get_single_sum(nums):
nums.sort()
# 0,0,1,2,2,3,3
for i in range(0,len(nums)-1,2):
if nums[i] != nums[i+1]:
return nums[i]
return nums[-1]
print(get_single_sum([2,2,1]))
print(get_single_sum([4,1,2,1,2]))
1 4
Approach 4 (Xor)¶
def get_single_sum(nums):
res = 0
for num in nums:
res ^= num
return res
print(get_single_sum([2,2,1]))
print(get_single_sum([4,1,2,1,2]))
1 4
Complexity¶
- N = len(nums)
- O(N)
- O(1)
Longest Subarray with given Sum K(Positives)¶
Input Format: N = 3, k = 5, array[] = {2,3,5}
Result: 2
Input Format: N = 5, k = 10, array[] = {2,3,5,1,9}
Result: 3
Approach 1(Brute Force)¶
def longest_subarray_length(nums,target):
n = len(nums)
result = 0
for i in range(n):
window_sum = 0
for j in range(i,n):
window_sum += nums[j]
if window_sum == target:
result = max(result,j-i+1)
return result
print(longest_subarray_length([2,3,5],5))
print(longest_subarray_length([2,3,5,1,9],10))
2 3
Complexity¶
- N = len(nums)
- O(N ^2)
- O(1)
Approach 2 (Prefix Sum)¶
def longest_subarray_length(nums,target):
result = 0
prefix_sum = 0
prefix_idx = {0:-1}
for i,num in enumerate(nums):
prefix_sum += num
# if prefix - sum exists , subarray exist
if (prefix_sum - target) in prefix_idx:
subarray_length = i - prefix_idx[prefix_sum - target]
result = max(result,subarray_length)
# store first occurese only as we want the longest length
if prefix_sum not in prefix_idx:
prefix_idx[prefix_sum] = i
return result
print(longest_subarray_length([2,3,5],5))
print(longest_subarray_length([2,3,5,1,9],10))
2 3
Complexity¶
- N = len(nums)
- O(N)
- O(N)
Approach 3 (Sliding Window)¶
def longest_subarray_length(nums,target):
result = 0
left = 0
window_sum = 0
for right in range(len(nums)):
window_sum += nums[right]
# shrink the window from left when window_sum is more
while left <= right and window_sum > target:
window_sum -= nums[left]
left += 1
if window_sum == target:
window_length = right - left + 1
result = max(result,window_length)
return result
print(longest_subarray_length([2,3,5],5))
print(longest_subarray_length([2,3,5,1,9],10))
2 3
Complexity¶
- N = len(nums)
- O(N)
- O(1)
Longest Subarray with sum K | [Postives and Negatives]¶
Input Format: N = 3, k = 5, array[] = {2,3,5}
Result: 2
Input Format: N = 3, k = 1, array[] = {-1, 1, 1}
Result: 3
Approach 1 (Brute Force)¶
def longest_sub_len(nums,target):
res = 0
n = len(nums)
for i in range(n):
window_sum = 0
for j in range(i,n):
window_sum += nums[j]
if window_sum == target:
window_len = j - i + 1
res = max(res,window_len)
return res
print(longest_sub_len([2,3,5],5))
print(longest_sub_len([-1,1,1],1))
2 3
Complexity¶
- N = len(nums)
- O(N ^ 2)
- O(1)
Approach 2 (Prefix Sum)¶
def longest_sub_len(nums,target):
res = 0
prefix_indices = {0:-1}
prefix_sum = 0
for i, num in enumerate(nums):
prefix_sum += num
if prefix_sum - target in prefix_indices:
window_len = i - prefix_indices[prefix_sum - target]
res = max(res,window_len)
if prefix_sum not in prefix_indices:
prefix_indices[prefix_sum] = i
return res
print(longest_sub_len([2,3,5],5))
print(longest_sub_len([-1,1,1],1))
2 3
Complexity¶
- N = len(nums)
- O(N)
- O(N)
We cannot use sliding window for mixed cases here as we don't know how to shrink window