Report Python-276
Report Python-276
GROUP : C2
INDEX PAGE
SL. PROBLEM NAME LEETCODE PAGE NO FACULTY REMARKS
NO. PROBLEM NO SIGN.
1. Two Sum
1. Problem Statement
Find two numbers in a given array that add up to a specific target value and return their positions (indices).
2. Description
We are given an array of integers and an integer representing a target sum. We need to identify exactly two
numbers in the array that sum up to this target and return their indices. Key constraints: each input has exactly one
valid solution, we cannot use the same element twice, and the order of the returned indices does not matter. This
problem represents a fundamental search task where optimizing the lookup of complementary values is key.
3. Algorithm / Approach
To solve this efficiently, we can use a Hash Map (dictionary in Python) to keep track of the numbers we have seen
so far and their indices. 1. Initialize an empty dictionary. 2. Iterate through the array using both the index and the
current number. 3. For each number, calculate its “complement” (i.e., target - current number). 4. Check if this
complement already exists in our dictionary. 5. If it does, we have found our two numbers! Return the index of the
complement and the current index. 6. If it doesn’t, add the current number and its index to the dictionary and
continue.
4. Source Code
def twoSum(nums, target):
# Dictionary to store the value and its index
seen = {}
return []
5. Input
nums = [2, 7, 11, 15]
target = 9
6. Output
[0, 1]
1
7. Conclusion
This problem teaches the usefulness of Hash Maps for fast lookups. Instead of using a nested loop to check all
pairs (which takes O(n²) time), the Hash Map approach reduces the time complexity to O(n) and space complexity
to O(n) by trading memory for speed.
2. Description
Given a string, the goal is to find the longest substring where every character is unique. The substring must be
contiguous, meaning characters correspond to adjacent positions in the original string. It tests the ability to
dynamically track a sequence and shrink or restructure it when a duplicate is encountered.
3. Algorithm / Approach
This problem can be solved using the Sliding Window technique with a Hash Set or Hash Map. 1. Use two pointers
(left and right) to represent the boundaries of the current substring (window). 2. Use a dictionary to store the
character and its most recent index. 3. Expand the window by moving the right pointer. 4. If the character at right
is already in the dictionary and its index is inside the current window, shrink the window by moving the left pointer
to seen[character] + 1. 5. Update the character’s latest index in the dictionary. 6. Calculate the window length
(right - left + 1) and update the maximum length found so far.
4. Source Code
def lengthOfLongestSubstring(s):
seen = {}
max_len = 0
left = 0
return max_len
5. Input
s = "abcabcbb"
2
6. Output
3 # (The substring is "abc")
7. Conclusion
The sliding window technique combined with a Hash Map is incredibly effective for substring problems. Tracking
the latest index of characters allows us to jump the left pointer directly, making the solution highly optimized. The
time complexity is O(n) and the space complexity is O(min(n, m)) where m is the character set size.
2. Description
You are given an integer array height representing heights of walls. The distance between two bars is the
difference of their indices. You need to calculate the maximum area of water that can be trapped between any two
walls. Since water spills over the shorter wall, the height of the container is determined by the shorter of the two
walls.
3. Algorithm / Approach
We use the Two Pointers approach. 1. Place one pointer at the beginning (left = 0) and one at the end (right =
len(height) - 1). 2. Calculate the area formed by the two lines: Area = width * height, where width = right
- left and height = min(height[left], height[right]). 3. Keep track of the maximum area seen so far. 4.
Move the pointer pointing to the shorter line inward. Why? Because the width is decreasing, so the only way to
possibly get a larger area is to find a taller line. 5. Repeat until the two pointers meet.
4. Source Code
def maxArea(height):
left, right = 0, len(height) - 1
max_water = 0
return max_water
3
5. Input
height = [1,8,6,2,5,4,8,3,7]
6. Output
49
7. Conclusion
This problem showcases a greedy two-pointer strategy. By smartly deciding which wall to discard (the shorter one),
we avoid checking all possible pairs, reducing the time complexity from O(n²) to O(n). The space complexity is O(1).
2. Description
Given an integer array nums sorted in non-decreasing order, the task is to modify the array directly so the first k
elements contain the unique numbers, maintaining their relative order. You cannot use extra space for another
array; the operation must be done in-place. The function should return k.
3. Algorithm / Approach
Since the array is sorted, duplicates will be adjacent. We can use the Two Pointers technique. 1. Use a pointer
insert_pos (starting at 1) to track where the next unique element should be placed. 2. Iterate through the array
starting from the second element (index 1) with another pointer i. 3. Compare the current element nums[i] with
the previous element nums[i - 1]. 4. If they are different, it means we found a new unique element. We place it at
nums[insert_pos] and increment insert_pos. 5. At the end of the loop, insert_pos will hold the count of unique
elements and the array will be properly modified up to that index.
4. Source Code
def removeDuplicates(nums):
if not nums:
return 0
return insert_pos
5. Input
nums = [0,0,1,1,1,2,2,3,3,4]
4
6. Output
5 # The modified nums array will be [0,1,2,3,4,...]
7. Conclusion
This demonstrates modifying arrays in-place using two pointers: one for reading and one for writing. Relying on the
sorted nature of the input makes it remarkably easy to spot duplicates. Time complexity is O(n) and space
complexity is O(1).
5. Valid Parentheses
1. Problem Statement
Check if a string consisting purely of bracket characters ((), {}, []) is closed in the correct order.
2. Description
We are given a string s containing only brackets. A string is considered valid if open brackets are closed by the
same type of brackets, and they are closed in the exact correct order. This is a classic parsing problem essential
for compilers checking code syntax.
3. Algorithm / Approach
A Stack data structure is ideal for this since we want to process the matching properties in a Last-In, First-Out
(LIFO) manner. 1. Create an empty stack (a list in Python) and a hash map linking each closing bracket to its
corresponding opening bracket. 2. Iterate over each character in the string. 3. If it is an opening bracket, push it
onto the stack. 4. If it is a closing bracket, check if the stack is empty. If it is, the string is invalid. 5. If the stack is
not empty, pop the top element. If the popped bracket does not match the required opening bracket for the
current closing character, it is invalid. 6. After processing all characters, the string is valid only if the stack is empty
(meaning all brackets were properly closed).
4. Source Code
def isValid(s):
stack = []
# Map closing brackets to their corresponding opening brackets
mapping = {')': '(', '}': '{', ']': '['}
for char in s:
# If it's a closing bracket
if char in mapping:
# Pop the top element if stack is not empty, else assign a dummy value
top_element = [Link]() if stack else '#'
# If the popped element doesn't match the corresponding opening bracket, return False
if mapping[char] != top_element:
return False
else:
# It's an opening bracket, purely push to the stack
[Link](char)
5
5. Input
s = "([{}])"
6. Output
True
7. Conclusion
The stack abstract data type is a natural fit for problems involving nested structures. Using a dictionary for bracket
mappings makes the code extensible and clean. Time complexity is O(n) and space complexity is O(n).
2. Description
You are given two sorted arrays, nums1 and nums2. nums1 has enough extra space (filled with zeros) at the end to
accommodate nums2. The goal is to merge nums2 directly inside nums1 in sorted, non-decreasing order without
using additional array allocations. nums1 has m active elements, and nums2 has n elements.
3. Algorithm / Approach
To avoid overwriting elements in nums1 that haven’t been processed yet, we utilize a Reverse Three-Pointer
strategy. 1. Point p1 to the last valid element of nums1 (m - 1). 2. Point p2 to the last element of nums2 (n - 1). 3.
Point p to the very last available slot in nums1 (m + n - 1). 4. Compare nums1[p1] and nums2[p2]. Place the larger
element at nums1[p] and decrement p and the pointer of the array we picked the element from. 5. If nums2
elements are leftover after p1 finishes, copy them over into the remaining slots.
4. Source Code
def merge(nums1, m, nums2, n):
# Initialize pointers for nums1, nums2, and the merged insertion position
p1 = m - 1
p2 = n - 1
p = m + n - 1
6
5. Input
nums1 = [1,2,3,0,0,0]
m = 3
nums2 = [2,5,6]
n = 3
6. Output
# nums1 is modified in-place to:
[1, 2, 2, 3, 5, 6]
7. Conclusion
Filling a pre-allocated array starting from the back is a brilliant trick to do in-place sorting without shifting data
continuously. Time complexity is strictly O(m + n) with O(1) space complexity.
7. Valid Palindrome
1. Problem Statement
Determine if a given string is a palindrome after removing all non-alphanumeric characters and ignoring
uppercase/lowercase differences.
2. Description
A palindrome reads the same forwards and backwards. In this problem, we are given a string that might contain
spaces, punctuation marks, and mixed case letters. We must first “clean” the string conceptually by looking only
at alphanumeric characters natively case-insensitively, and then verify if the resulting string is symmetrical.
3. Algorithm / Approach
We can use a Two Pointers method starting from opposite ends of the string to save space and time. 1. Initialize
left pointer to the first character and right pointer to the last character. 2. Move the left pointer continuously
forward if it points to a non-alphanumeric character. 3. Move the right pointer continuously backward if it points
to a non-alphanumeric character. 4. Once both point to valid characters, compare them (converted to lowercase).
If they don’t match, return False. 5. If they match, move both pointers inward. 6. Continue this until left >= right.
If the loop finishes without issues, return True.
4. Source Code
def isPalindrome(s):
# Initialize two pointers
left, right = 0, len(s) - 1
# Compare characters
if s[left].lower() != s[right].lower():
7
return False
return True
5. Input
s = "A man, a plan, a canal: Panama"
6. Output
True
7. Conclusion
Two Pointers is highly efficient for validating symmetry because it evaluates conditions simultaneously from both
edges. This prevents us from allocating additional memory for a cleaned version of the string. Time complexity is
O(n) and space complexity is O(1).
2. Description
Given an input string s that may contain leading/trailing spaces or multiple spaces between words, we want to
return a string of the words reversed. A word is defined as a sequence of non-space characters. The returned string
must only have a single space separating the words, with no leading or trailing spaces.
3. Algorithm / Approach
Python provides built-in methods that make this process straightforward. 1. Use the split() function to divide the
string into a list of words. This built-in function naturally ignores extra spaces between words. 2. Reverse the list of
words. We can do this using the slice syntax [::-1] or the reversed() function. 3. Join the reversed list back into a
single string using ' '.join().
4. Source Code
def reverseWords(s):
# split() automatically removes leading/trailing and multiple spaces
words = [Link]()
5. Input
s = " hello world "
8
6. Output
"world hello"
7. Conclusion
While writing this from scratch (like in C) requires multiple reversals to do it in-place, Python’s string manipulation
methods abstract the heavy lifting. The time complexity is O(n) to parse strings, and the space complexity is O(n)
to hold the array of words.
6. Min Stack
1. Problem Statement
Design a stack that supports standard push, pop, and top operations, but also provides an operation to retrieve the
minimum element in constant time.
2. Description
The challenge is managing the current minimum value without having to search the entire stack each time it is
requested. The getMin function is expected to return the minimal element on the stack immediately in O(1) time.
All other stack operations must operate in O(1) time as well.
3. Algorithm / Approach
The solution involves using a standard stack conceptually but keeping track of minimum values alongside real
data. 1. Initialize a list stack. Instead of just storing the values directly, we’ll store tuples (or pairs):
(current_value, current_minimum). 2. For push(val): if the stack is empty, the current_minimum is val. If not,
the current_minimum is the minimum of val and the minimum of the previous top element. We append (val,
min(val, previous_min)) to the stack. 3. For pop(): simply pop the top pair from the stack list. 4. For top():
return the first item of the pair at the top of the stack. 5. For getMin(): return the second item of the pair at the top
of the stack.
4. Source Code
class MinStack:
def init (self):
# We will store tuples of (value, minimum_at_this_level)
[Link] = []
9
def getMin(self) -> int:
if [Link]:
return [Link][-1][1]
5. Input
minStack = MinStack()
[Link](-2)
[Link](0)
[Link](-3)
[Link]()
[Link]()
[Link]()
[Link]()
6. Output
# Sequential outputs:
# getMin() -> -3
# pop() -> None
# top() -> 0
# getMin() -> -2
7. Conclusion
Storing the state or “history” of minimums synchronized with elements at every stage is an elegant way to maintain
an O(1) retrieval time. Time complexity for all operations is O(1), while space complexity is O(n) due to storing pairs
instead of just elements.
2. Description
Given an integer array nums, we need to return True if any value appears at least twice in the array, and return False
if every element is distinct. This is a basic frequency/existence check common in database and validation
algorithms.
3. Algorithm / Approach
Using a Hash Set provides the most optimal time complexity. 1. Initialize an empty Set. Set structures inherently
map unique keys and prohibit duplicates. 2. Iterate through each number in nums. 3. If the number is already in the
set, we’ve found a duplicate! Return True. 4. If not, add the number to the set. 5. If the loop ends without finding
duplicates, it means all elements are distinct, so return False. (Alternatively, simply compare the length of the list
to the length of the list converted to a set: len(set(nums)) != len(nums)).
4. Source Code
def containsDuplicate(nums):
# A set to keep track of seen numbers
seen = set()
10
for num in nums:
# Duplicate detected immediately
if num in seen:
return True
[Link](num)
return False
5. Input
nums = [1, 2, 3, 1]
6. Output
True
7. Conclusion
Using a Hash Set restricts duplicate insertion, which makes finding redundant elements O(1) lookup time. The
entire array check runs in O(n) time. The space complexity is O(n) since the set may grow to the size of the array if
all elements are unique.
2. Description
A standard queue processes elements in a FIFO manner, similar to a line at a grocery store. A stack, however,
works as LIFO. The challenge is to combine two stacks (s1 and s2) to mimic the behavior of a queue.
3. Algorithm / Approach
We use two stacks: an in_stack to handle pushes, and an out_stack to handle pops and peeks. 1. Push: Always
push newly inserted elements to the in_stack. 2. Pop/Peek: Since the oldest elements are at the bottom of the
in_stack, we must move them. If out_stack is empty, pop all items from in_stack and push them into out_stack.
3. Then, simply pop or peek from the out_stack. 4. Empty: The queue is only empty if both stacks are empty.
4. Source Code
class MyQueue:
def init (self):
self.in_stack = []
self.out_stack = []
11
def peek(self) -> int:
self.move_elements()
return self.out_stack[-1]
def move_elements(self):
if not self.out_stack:
while self.in_stack:
self.out_stack.append(self.in_stack.pop())
5. Input
q = MyQueue()
[Link](1)
[Link](2)
[Link]()
[Link]()
[Link]()
6. Output
# peek() -> 1
# pop() -> 1
# empty() -> False
7. Conclusion
Reversing a LIFO structure twice yields a FIFO order. Amortized time complexity for pop and peek is O(1). Space
complexity is O(n).
2. Description
Given an array nums sorted in ascending order and a target element, we need to return the target’s index if it exists,
or -1 if it doesn’t. Algorithm must run in O(log n) time.
3. Algorithm / Approach
1. Define two pointers: left at the start and right at the end.
2. Calculate the middle index mid.
3. Compare the item at mid to the target.
4. If it’s a match, return mid.
5. If the target is greater, update left = mid + 1.
6. If the target is smaller, update right = mid - 1.
7. Repeat until left passes right. Return -1 if not found.
12
4. Source Code
def search(nums, target):
left, right = 0, len(nums) - 1
if nums[mid] == target:
return mid
elif nums[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1
5. Input
nums = [-1,0,3,5,9,12]
target = 9
6. Output
4
7. Conclusion
Binary search is a cornerstone concept illustrating Divide and Conquer. Time complexity is O(log n), and space
complexity is O(1).
2. Description
You are given an API isBadVersion(version) that checks if a version is bad. All versions after a bad version are
also bad. Find the first bad version minimizing API calls.
3. Algorithm / Approach
1. Initialize markers: left = 1 and right = n.
2. Compute mid. Ǫuery isBadVersion(mid).
3. If it is bad, mid might be the first bad version. Move the upper limit: right = mid.
4. If mid is not bad, move the lower limit: left = mid + 1.
5. Terminate when left meets right. left will end on the first bad version.
4. Source Code
# def isBadVersion(version: int) -> bool:
def firstBadVersion(n):
left = 1
right = n
13
while left < right:
mid = left + (right - left) // 2
if isBadVersion(mid):
right = mid
else:
left = mid + 1
return left
5. Input
n = 5
# bad = 4
6. Output
4
7. Conclusion
An application of binary search looking for a Boolean boundary. Time complexity is O(log n) and space complexity
O(1).
2. Description
Given an array of characters, reverse the element order without generating a secondary array. Modify the list
sequentially using its original memory.
3. Algorithm / Approach
1. Place a pointer left at the 0th index, and right at the last index.
2. Swap the character at left with the character at right.
3. Move left forward (+1) and right backwards (-1).
4. Exit the loop when left meets or passes right.
4. Source Code
def reverseString(s):
left, right = 0, len(s) - 1
5. Input
s = ["h","e","l","l","o"]
14
6. Output
["o","l","l","e","h"]
7. Conclusion
Array modification with two pointers on the ends converging toward the middle is an O(n) time mechanism utilizing
an O(1) overhead footprint.
2. Description
Swap only the vowels (a, e, i, o, u) present in the string. Both uppercase and lowercase variants matter.
3. Algorithm / Approach
1. Convert string to a list of characters.
2. Start left at the beginning and right at the end.
3. Move left forward until it hits a vowel, and right backward until it hits a vowel.
4. Swap elements at left and right.
5. Move both pointers inward and repeat until they meet.
6. Reconstruct the list into a string and return.
4. Source Code
def reverseVowels(s):
vowels = set("aeiouAEIOU")
s_list = list(s)
left, right = 0, len(s_list) - 1
return "".join(s_list)
5. Input
s = "leetcode"
6. Output
"leotcede"
15
7. Conclusion
Using a set to evaluate character typings alongside two pointers maintains maximum efficiency. Time complexity
stays O(n) with space at O(n) handling string immutability.
2. Description
Given the head of a singly linked list, return the halfway point structure. If the list has an even amount of
components, return the second middle node.
3. Algorithm / Approach
1. Initiate two pointers, slow and fast, both starting at the head.
2. fast moves twice as fast as slow (two nodes per iteration).
3. slow traverses one node per iteration.
4. When fast reaches the end of the list, slow will be exactly at the midway point.
4. Source Code
# class ListNode:
# def init (self, val=0, next=None):
# [Link] = val
# [Link] = next
def middleNode(head):
slow = head
fast = head
return slow
5. Input
head = [1, 2, 3, 4, 5]
6. Output
[3, 4, 5]
7. Conclusion
Calculations boil down to a single iteration path without relying on total lengths, providing strictly O(n) runtimes
and O(1) memory mapping.
16
17. Reverse Linked List
1. Problem Statement
Reverse a singly linked list completely and supply the new head.
2. Description
Restructure the next pointers within the nodes so that the list flows backwards. Return the node that was
previously the tail.
3. Algorithm / Approach
1. Use variables: prev (None), curr (head).
2. For each node, temporarily save the next node.
3. Bind the current node’s pointer backward to prev.
4. Shift markers: prev becomes curr, and curr slides into the saved next node.
5. prev will eventually represent the new head.
4. Source Code
# class ListNode:
# def init (self, val=0, next=None):
# [Link] = val
# [Link] = next
def reverseList(head):
prev = None
curr = head
while curr:
next_temp = [Link]
[Link] = prev
prev = curr
curr = next_temp
return prev
5. Input
head = [1, 2, 3, 4, 5]
6. Output
[5, 4, 3, 2, 1]
7. Conclusion
Incrementally reversing pointers handles linking robustly in O(n) time and O(1) space logic.
17
18. Linked List Cycle
1. Problem Statement
Detect if a continuous linked list contains an internal circular loop.
2. Description
Determine if a singly linked list has a cycle, meaning the tail points to a node previously visited instead of None.
Return True or False.
3. Algorithm / Approach
1. Use Floyd’s Cycle-Finding Algorithm. Place slow and fast pointers at the head.
2. Advance slow by 1, and fast by 2.
3. If no cycle exists, fast encounters None.
4. If a loop wraps round, fast will recursively loop and collide with slow. Check if their references match.
4. Source Code
# class ListNode:
# def init (self, x):
# [Link] = x
# [Link] = None
def hasCycle(head):
slow = head
fast = head
if slow == fast:
return True
return False
5. Input
head = [3, 2, 0, -4]
pos = 1 # -4 points to 2
6. Output
True
7. Conclusion
Mathematical tracking of variable speeds avoids massive object mapping tables. Time complexity O(n) and
Memory complexity strictly O(1).
18
16. Remove Nth Node From End of List
1. Problem Statement
Remove the nth node from the end of a linked list and recover the updated list’s head.
2. Description
Complete the node deletion counting from the tail threshold in exactly 1-pass since linked lists traverse solely
forward natively.
3. Algorithm / Approach
1. Instantiate a “dummy” node ahead of the head.
2. Initialize first and second pointers at dummy.
3. Propel first ahead structurally exactly 𝑛 + 1 instances.
4. Move both pointers at equal paces until first surpasses the chain end.
5. The pre-calculated spacing drops second exactly prior to the target node. [Link] =
[Link].
4. Source Code
# class ListNode:
# def init (self, val=0, next=None):
# [Link] = val
# [Link] = next
[Link] = [Link]
return [Link]
5. Input
head = [1, 2, 3, 4, 5]
n = 2
6. Output
[1, 2, 3, 5]
7. Conclusion
Enabling fixed separation windows eliminates secondary counting logic. Runtime O(n) combined cleanly with O(1)
memory constants.
19
20. Intersection of Two Linked Lists
1. Problem Statement
Locate the precise node where two distinct one-directional linked branches merge together.
2. Description
Given isolated list bases headA and headB, deliver the node reference where intersection transpires physically in
memory. Unmerged outputs must present empty None.
3. Algorithm / Approach
1. Create ptrA starting at headA mapping beside ptrB starting at headB.
2. Simultaneously iterate steps.
3. When either hits None, forcefully restart it from the opposite list’s base.
4. By swapping paths, both pointers travel effectively length(A)+length(B). Therefore, they align concurrently
matching intersections.
4. Source Code
# class ListNode:
# def init (self, x):
# [Link] = x
# [Link] = None
ptrA = headA
ptrB = headB
return ptrA
5. Input
# List A: 4 -> 1 \
# -> 8 -> 4 -> 5
# List B: 5 -> 6 /
6. Output
# Node with value 8
7. Conclusion
Symmetrical trajectory offsets perfectly match unequal length branches mathematically guaranteeing collision
points. Time scales O(N+M) using O(1) memory.
20
21. Maximum Average Subarray I
1. Problem Statement
Find a contiguous subarray of exactly length k that has the maximum average value, and return this maximum
average.
2. Description
Given an integer array nums and an integer k, we need to find the k-length continuous slice of the array whose
elements sum up to the highest possible value. Because the length k is constant, finding the maximum sum
directly translates to finding the maximum average.
3. Algorithm / Approach
This problem is perfectly suited for the Fixed Sliding Window technique. 1. Compute the sum of the first k
elements to form our initial window. 2. Initialize max_sum with this initial sum. 3. Slide the window one element to
the right at a time across the rest of the array. 4. To update the sum in O(1) time, add the new element entering the
window from the right, and subtract the old element leaving the window from the left. 5. Continuously update
max_sum. 6. Finally, return max_sum / k as the maximum average.
4. Source Code
def findMaxAverage(nums, k):
# Calculate sum of the first `k` elements
current_sum = sum(nums[:k])
max_sum = current_sum
return max_sum / k
5. Input
nums = [1,12,-5,-6,50,3]
k = 4
6. Output
12.75 # The subarray is [12, -5, -6, 50], sum = 51, avg = 12.75
7. Conclusion
The Fixed Sliding Window transforms an O(N*K) brute-force summation into an O(N) optimized pass. Space
complexity remains O(1).
21
22. Maximum Number of Vowels in a Substring of Given Length
1. Problem Statement
Find the maximum number of vowel letters present in any substring of a specific length k.
2. Description
Given a string s and an integer k, evaluate every k-length contiguous block to see which one contains the highest
count of vowels (‘a’, ‘e’, ‘i’, ‘o’, ‘u’). Return that maximum count.
3. Algorithm / Approach
We apply the Fixed Sliding Window algorithm similarly to the maximum average subarray. 1. Establish a set of
vowels for rapid O(1) condition checking. 2. Form the first window of size k and count the exact number of vowels
inside it. 3. Record this initial count as our max_vowels. 4. Slide the window forward by doing two things: - If the
new character entering the window is a vowel, increment the count. - If the character left behind outside the
window was a vowel, decrement the count. 5. Maximize the count tracking variable dynamically.
4. Source Code
def maxVowels(s, k):
vowels = {'a', 'e', 'i', 'o', 'u'}
current_vowels = 0
max_vowel_count = current_vowels
return max_vowel_count
5. Input
s = "abciiidef"
k = 3
6. Output
3 # Substring "iii" contains 3 vowels
7. Conclusion
By carrying forward state evaluations instead of recalculating counts, time complexity becomes strictly O(N).
Memory allocation is O(1) since our vowel lookup set holds exactly 5 constant characters.
22
23. Fruit Into Baskets
1. Problem Statement
Find the longest contiguous stretch of trees that contains no more than two different types of fruit.
2. Description
Given an array fruits representing fruit types, you have only two baskets, and each basket can hold an infinite
amount of a single type of fruit. You must collect fruits contiguously. Thus, the problem translates to: Find the
length of the longest subarray with at most two distinct elements.
3. Algorithm / Approach
This utilizes a Dynamic Sliding Window aided by a Hash Map. 1. Use left and right pointers to represent the
basket collection bounds. 2. Use a dictionary basket to track the frequency of each fruit type in the current
window. 3. Expand right. If a new fruit exceeds our limit of 2 distinct types, we must shrink the window from the
left. 4. To shrink, decrement the frequency of fruits[left]. If a fruit count reaches 0, delete it from the
dictionary. 5. Record the maximum size of the valid window (right - left + 1).
4. Source Code
def totalFruit(fruits):
basket = {}
max_fruits = 0
left = 0
return max_fruits
5. Input
fruits = [1,2,3,2,2]
6. Output
4 # Subarray is [2,3,2,2]
7. Conclusion
Hash Maps paired with a flexible window beautifully resolve constraint-based subarray tracking. While the while
loops intuitively suggest slower bounds, every fruit is visited strictly twice, providing O(N) execution time. Space
complexity evaluates to O(1) as the dictionary caps at 3 elements.
23
24. Sliding Window Maximum
1. Problem Statement
Find the maximum element within every sliding window of size k moving from left to right across an array.
2. Description
For an array nums and window length k, you can only see k numbers horizontally at a time. The window slides +1
continuously. You must return an array compiling every maximum value observable in that window per step.
3. Algorithm / Approach
Extracting maxes per frame without hitting O(NK) requires a Monotonic Deque (Double-Ended Ǫueue). 1. The
queue will store only array indices*, keeping the actual array values monotonically decreasing. 2. For each
element nums[i], remove indices from the back of the queue if their corresponding values are smaller than
nums[i]. (They can never be the maximum because nums[i] is both bigger and will stay in the window longer). 3.
Append index i to the back. 4. Remove the front index if it has slid out of the window (front <= i - k). 5. After
hitting length k, record the front of the queue, as it consistently represents the highest value.
4. Source Code
from collections import deque
for i in range(len(nums)):
# Remove elements out of window boundary
if q and q[0] == i - k:
[Link]()
[Link](i)
return result
5. Input
nums = [1,3,-1,-3,5,3,6,7]
k = 3
6. Output
[3, 3, 5, 5, 6, 7]
24
7. Conclusion
Monotonically decreasing deques provide the perfect buffer system filtering obsolete values while actively
ordering active ones. Items are aggressively popped keeping constraints tight. Runtime evaluates heavily
optimized at O(N). Space complexity relies on O(K) holding the active window bounds.
2. Description
Koko wants to eat all piles of bananas within h hours. She decides her eating speed k. Every hour she chooses a
pile and eats k bananas. If the pile has fewer than k bananas, she eats them and rests for the remainder of the
hour. What is her minimum necessary speed?
3. Algorithm / Approach
This represents Binary Search on Answer Space. 1. Minimum speed left is 1. Maximum speed right is
max(piles). 2. Binary search the optimal k speed. Compute a mid speed. 3. Calculate the required hours to finish
all piles mapping [Link](pile / mid). 4. If total hours required > h, she’s eating too slowly. Boost speed:
left = mid + 1. 5. If total hours required <= h, she can finish, but we want the minimum speed. Attempt lower
bounds: right = mid. 6. Terminate resolving left as the optimal valid speed.
4. Source Code
import math
if hours_taken > h:
# We took too long, eat faster
left = mid_speed + 1
else:
# We made it! But can we eat slower?
right = mid_speed
return left
5. Input
piles = [3,6,7,11]
h = 8
25
6. Output
4
7. Conclusion
Instead of strictly hunting arrays, searching algorithmic outcome potential spaces proves phenomenally powerful.
Runtime scales O(N log M) where N matches piles counting and M equals maximum pile limits. Space checks in
exclusively at O(1).
2. Description
You receive an array nums mapping red, white, and blue using identifiers 0, 1, and 2 respectively. Group elements
physically sorted scaling 0 to 2 optimally requiring 1 solitary pass.
3. Algorithm / Approach
Apply the Dutch National Flag Algorithm via three tracking bounds. 1. Track trackers: low = 0 (boundary for 0s),
mid = 0 (iterator), high = len(nums) - 1 (boundary for 2s). 2. Sweep mid forward evaluating indices until
breaking past high. 3. If nums[mid] == 0: Swap logic positions tying nums[low] and nums[mid]. Elevate both low
and mid. 4. If nums[mid] == 1: Position is already correctly placed centrally. Just push mid ahead. 5. If nums[mid]
== 2: Swap logic bridging nums[mid] backwards alongside nums[high]. Downgrade high, keeping mid frozen so the
newly swapped-in element can be verified next iteration.
4. Source Code
def sortColors(nums):
low, mid = 0, 0
high = len(nums) - 1
5. Input
nums = [2,0,2,1,1,0]
26
6. Output
[0,0,1,1,2,2]
7. Conclusion
Multi-pointer anchoring isolates partitions effectively pushing numbers cleanly directly via concurrent sorting
swaps. Delivering true O(N) evaluation using O(1) in-place overheads perfectly replicates optimal low-level sorting
parameters.
2. Description
In a valid BST, the left subtree contains only values smaller than the parent node, and the right subtree exclusively
houses figures fundamentally greater. Both subtrees independently must also recursively represent true BST
formats. A standalone DFS check verifies legality.
3. Algorithm / Approach
Validation succeeds leveraging Recursive Depth-First Search Configuration Bounds. 1. Create a sub-function
establishing parameters feeding current node mappings, absolute lower boundary mappings, and upper boundary
limitations. 2. Initialize starting with unlimited bounding (-infinity, +infinity). 3. If traversing reaches the end
node (None), structurally validate returning True. 4. If current values break outside lower/upper limitations, fail out
providing False. 5. Recurse down branching into the left child tightly restricting the upper threshold limit strictly to
current node value. 6. Recurse heavily into the right child elevating the root lower restriction to current node
value.
4. Source Code
# class TreeNode:
# def init (self, val=0, left=None, right=None):
# [Link] = val
# [Link] = left
# [Link] = right
def isValidBST(root):
def validate(node, low=-float('inf'), high=float('inf')):
# Empty nodes are valid by default
if not node:
return True
return validate(root)
27
5. Input
# Tree maps: [2, 1, 3] (2 at root, 1 left, 3 right)
6. Output
True
7. Conclusion
Tracking constraint parameters iteratively across lower branching enforces complete validity ensuring deeply
nested nodes comply strictly with master parent inheritance rules. Calculates O(N) scaling traversing structures
via O(N) internal memory framing limits tracking structural deepness.
2. Description
Provided a root of a structural binary tree, extract row listings grouping siblings equally aligned geometrically.
Represent formats inside 2D-Arrays listing levels progressively. Assures horizontal tracking rather than deep
delving mechanisms.
3. Algorithm / Approach
Operate horizontal scanning effectively via Breadth-First Search (BFS) combined with Ǫueue mechanics. 1.
Setup a list queue holding root structures. Initializations fail if tree references equal None. 2. Extract the amount of
length existing natively within the current queue representing level size parameters. 3. Iteratively loop removing
structures identically bound resolving the precise current size amount previously tracked. 4. Record their inherent
values internally per level. 5. Identify left and right branching structures adding them heavily sequentially queuing
backwards. 6. Push level results pushing master responses arrays.
4. Source Code
from collections import deque
# class TreeNode:
# def init (self, val=0, left=None, right=None):
# [Link] = val
# [Link] = left
# [Link] = right
def levelOrder(root):
if not root:
return []
result = []
queue = deque([root])
while queue:
level_size = len(queue)
current_level = []
28
# Process all nodes currently in the queue (one level)
for _ in range(level_size):
node = [Link]()
current_level.append([Link])
return result
5. Input
# Tree maps: [3, 9, 20, null, null, 15, 7]
6. Output
[[3], [9, 20], [15, 7]]
7. Conclusion
Level grouping isolates row architectures dynamically capturing dimensions uniformly. Leveraging a FIFO queue
enforces breadth iterations effectively without risking recursion depths. Traversing scales efficiently locking inside
O(N) intervals while queue holding takes O(N) constraints spanning bottom layers.
2. Description
An island encompasses isolated horizontal or vertical “1” cells bounded securely strictly inside native water
constraints or array boundaries. Diagnosing configurations demands complete map navigation ensuring
connected blocks merge grouping into a solitary island entity count definitively.
3. Algorithm / Approach
Implement intensive Depth-First Search (DFS) Traversal tracking constraints. 1. Navigate evaluating grid matrix
points exhaustively counting nodes. 2. If identifying unvisited land (‘1’), increase maximum island counts. 3.
Deploy DFS logic sweeping completely traversing out capturing contiguous formatting. 4. Convert visited lands
securely assigning strings formatting (‘0’) preventing infinite iteration redundancies. 5. DFS systematically tests Up,
Down, Left, Right directional bounds sweeping arrays correctly identifying structural land blocks iteratively
grouping matches.
29
4. Source Code
def numIslands(grid):
if not grid:
return 0
rows = len(grid)
cols = len(grid[0])
island_count = 0
for r in range(rows):
for c in range(cols):
# We found an unvisited part of a new island
if grid[r][c] == "1":
island_count += 1
dfs(r, c) # Sink all connected tiles of this island
return island_count
5. Input
grid = [
["1","1","0","0","0"],
["1","1","0","0","0"],
["0","0","1","0","0"],
["0","0","0","1","1"]
]
6. Output
3
7. Conclusion
“Sinking” land natively effectively functions mapping visited metrics heavily bypassing external matrices
significantly saving overhead. Operations span matrix size strictly establishing O(rows * cols) temporal
constraints alongside equally sized worst-case implicit memory recursions.
30
30. Rotting Oranges
1. Problem Statement
Gauge matrix orange crop infections analyzing time progression. Calculate completely how many exact minutes
expire decaying entire configurations given specific infectious rules.
2. Description
The grid maps 0 (empty), 1 (fresh), and 2 (rotten). Each sequential minute, fresh oranges orthogonally located
neighboring rotting units infect deteriorating recursively. Trace formats verifying maximum bounds or evaluating
structurally blocked units guaranteeing impossible infections tracking isolated layouts (return -1).
3. Algorithm / Approach
Execute synchronous grid sweeping via Multi-Source Breadth-First Search.
1. Formulate counting fresh oranges verifying layout bases and map initially rotting oranges pushing strictly into
tracking queues.
2. Traverse dynamically tracking minute counts per iteration layers pulling queued items safely identifying
coordinate positioning lengths.
3. Validate directional formatting coordinates spanning Up, Down, Left, and Right locations organically inside
matrix lengths mutating ’1’s heavily converting correctly updating variables tracking fresh losses efficiently back
queuing components.
4. When tracking ceases completely verify configurations determining if fresh_oranges == 0. Return elapsed
minutes safely.
4. Source Code
from collections import deque
def orangesRotting(grid):
rows = len(grid)
cols = len(grid[0])
queue = deque()
fresh_oranges = 0
# Step 1: Collect all initial rotten oranges and count fresh ones
for r in range(rows):
for c in range(cols):
if grid[r][c] == 2:
[Link]((r, c))
elif grid[r][c] == 1:
fresh_oranges += 1
minutes = 0
directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
31
# Process everything currently in the queue (one minute pass)
for _ in range(len(queue)):
r, c = [Link]()
5. Input
grid = [[2,1,1],[1,1,0],[0,1,1]]
6. Output
4
7. Conclusion
Deploying multi-source layouts effectively establishes radiating wavefronts tracking real-world physical spreads.
Time progresses sequentially resolving metrics bounding completely. Traversals evaluate cleanly executing within
limits capping tightly into O(rows * cols) constraints simultaneously mirroring equivalent space bounds.
SUBMITTED BY:
NAME: SANKALP JIBAN PRADHAN
SEM: 6th
GROUP: C2
BRANCH: CSE(AIML)
REG: 2301020276
32