CC Index
CC Index
aboratory Man
FACULTY OF ENGINEERING AND TECHNOLOGY
BACHELOR OF TECHNOLOGY
COMPETITIVE CODING
(303105259)
IV SEMESTER
Computer Science & Engineering Department
Laboratory Manual
Session 2024-25
CERTIFICATE
Mr./Ms....................................................................................................
Head Of Department:...........................................
Problem Statement:
Design a MinStack data structure that supports the following operations:
1. Push: Add an element to the top of the stack.
2. Pop: Remove the element from the top of the stack.
3. Top: Retrieve the top element of the stack without removing it.
4. GetMin: Retrieve the minimum element in the stack.
5. Display: Display all elements in the stack.
6. Overflow: Check if the stack is full (if a capacity is defined).
7. Underflow: Check if the stack is empty.
The stack should have a fixed capacity N, and it should handle overflow and underflow
conditions appropriately.
Algorithms:
1. Initialize the Stack:
o Create two stacks: one for storing the actual elements (stack) and another for
storing the minimum elements (min_stack).
o Define the capacity N of the stack.
2. Push Operation:
o Check if the stack is full (overflow condition). If full, display an error
message.
o Push the element onto the stack.
o If the min_stack is empty or the new element is less than or equal to the top of
the min_stack, push the element onto the min_stack.
CODE:
class MinStack:
def __init__(self, capacity):
self.capacity = capacity
self.stack = []
self.min_stack = []
def is_overflow(self):
return len(self.stack) == self.capacity
def is_underflow(self):
return len(self.stack) == 0
def pop(self):
if self.is_underflow():
print("Stack Underflow: Cannot pop element. Stack is empty.")
return None
popped_value = self.stack.pop()
if popped_value == self.min_stack[-1]:
self.min_stack.pop()
print(f"Popped {popped_value} from the stack.")
return popped_value
def top(self):
if self.is_underflow():
print("Stack is empty.")
return None
return self.stack[-1]
def get_min(self):
if self.is_underflow():
print("Stack is empty.")
def display(self):
if self.is_underflow():
print("Stack is empty.")
else:
print("Stack elements:", self.stack)
# Example usage
if __name__ == "__main__":
capacity = 5
min_stack = MinStack(capacity)
min_stack.push(3)
min_stack.push(5)
min_stack.push(2)
min_stack.push(1)
min_stack.push(4)
min_stack.pop()
min_stack.pop()
min_stack.push(0)
min_stack.display() # Output: Stack elements: [3, 5, 2, 0]
print("Minimum element:", min_stack.get_min()) # Output: Minimum element: 0
OUTPUT:
AIM: Write a program to deal with real-world situations where Stack data
structure is widely used Evaluation of expression: Stacks are used to evaluate
expressions, especially in languages that use postfix or prefix notation.
Operators and operands are pushed onto the stack, and operations are performed
based on the LIFO principle.
Problem Statement:
In many real-world applications, such as calculators or compilers, expressions need to be
evaluated. One common way to evaluate expressions is by using the stack data structure,
especially for expressions written in postfix (Reverse Polish Notation) or prefix notation. The
stack's Last-In-First-Out (LIFO) property makes it ideal for this purpose.
Your task is to write a Python program that evaluates a postfix expression using a stack. The
program should handle basic arithmetic operations like addition (+), subtraction (-),
multiplication (*), and division (/).
Algorithm:
1. Initialize an empty stack to store operands.
2. Iterate through each character in the postfix expression:
o If the character is an operand (number), push it onto the stack.
o If the character is an operator (+, -, *, /):
▪ Pop the top two elements from the stack.
▪ Perform the operation on the two popped elements.
▪ Push the result back onto the stack.
3. After processing all characters, the final result will be the only element left in the
stack.
4. Return the final result.
def evaluate_prefix(expression):
stack = []
for char in reversed(expression.split()):
if char.isdigit():
stack.append(int(char))
else:
a = stack.pop()
OUTPUT:
AIM: Write a program for finding NGE NEXT GREATER ELEMENT from an
array.
Problem Statement:
Given an array of integers, the Next Greater Element (NGE) for each element is the first
greater element to its right. If no such element exists, the NGE is considered to be -1. Write a
program to find the NGE for each element in the array.
Algorithm;
1. Initialize a stack and a result array.
2. Iterate through the array from the end to the start.
3. For each element:
o Pop elements from the stack until the top of the stack is greater than the
current element or the stack is empty.
o If the stack is empty, the NGE for the current element is -1.
o Otherwise, the NGE is the top element of the stack.
o Push the current element onto the stack.
4. Reverse the result array to match the original order of the input array.
5. Return the result array.
OUTPUT:
Problem Statement:
Design a circular queue data structure that supports the following operations:
1. Enqueue: Insert an element into the queue. If the queue is full, return an error or
indicate that the operation cannot be performed.
2. Dequeue: Remove an element from the queue. If the queue is empty, return an error
or indicate that the operation cannot be performed.
3. Front: Get the front item from the queue. If the queue is empty, return an error or
indicate that the operation cannot be performed.
4. Rear: Get the last item from the queue. If the queue is empty, return an error or
indicate that the operation cannot be performed.
The circular queue should efficiently utilize the space and handle wrap-around conditions.
Algorithm:
1. Initialization:
o Use a list of size k to represent the circular queue.
o Initialize two pointers, front and rear, to -1 to indicate an empty queue.
o Track the current size of the queue using a variable size.
2. Enqueue:
o Check if the queue is full (size == k). If full, return an error.
o If the queue is empty (front == -1), set front and rear to 0.
o Otherwise, increment rear circularly using (rear + 1) % k.
o Insert the element at the rear position.
o Increment the size.
3. Dequeue:
o Check if the queue is empty (size == 0). If empty, return an error.
CODE:
from collections import deque
class CircularQueue :
def __init__(self, k) :
self.queue = deque(maxlen = k)
self.size = k
def dequeue(self) :
if not self.queue:
return "Queue is empty!"
value = self.queue.popleft()
def display(self):
return list(self.queue)
k=3
cq = CircularQueue(k)
print(cq.enqueue(10))
print(cq.enqueue(20))
print(cq.enqueue(30))
print(cq.enqueue(40))
print(cq.display())
print(cq.dequeue())
print(cq.display())
print(cq.enqueue(40))
print(cq.display())
OUTPUT:
AIM: Write a Program for an infix expression and convert it to postfix notation.
Use a queue to implement the Shunting Yard Algorithm for expression
conversion.
Problem Statement:
Write a Python program that takes an infix expression as input and converts it to postfix
notation using the Shunting Yard Algorithm. The Shunting Yard Algorithm should be
implemented using a queue to manage the output and a stack to manage the operators.
Algorithm:
1. Initialize:
o A stack to hold operators.
o A queue to hold the output (postfix expression).
o A dictionary to define operator precedence.
2. Tokenize the Input:
o Split the input string into tokens (numbers, operators, parentheses).
3. Process Each Token:
o If the token is a number, add it to the output queue.
o If the token is an operator:
▪ While there is an operator on the top of the stack with higher or equal
precedence, pop it and add it to the output queue.
▪ Push the current operator onto the stack.
o If the token is a left parenthesis (, push it onto the stack.
o If the token is a right parenthesis ):
▪ Pop from the stack and add to the output queue until a left parenthesis
is encountered.
▪ Pop the left parenthesis from the stack and discard it.
CODE:
from collections import deque
class ShuntingYard:
def __init__(self):
self.precedence = {'+': 1, '-': 1, '*': 2, '/': 2, '^': 3}
self.operators = set(['+', '-', '*', '/', '^'])
self.parenthesis = set(['(', ')'])
while stack:
queue.append(stack.pop())
return ' '.join(queue)
# Example usage
if __name__ == "__main__":
shunting_yard = ShuntingYard()
infix_expression = "3 + 4 * 2 / ( 1 - 5 ) ^ 2 ^ 3"
postfix_expression = shunting_yard.infix_to_postfix(infix_expression)
print("Infix Expression:", infix_expression)
print("Postfix Expression:", postfix_expression)
OUTPUT:
AIM: Write a Program for finding the Product of the three largest Distinct
Elements. Use a Priority Queue to efficiently find and remove the largest
elements.
Problem Statement:
Given an array of integers, find the product of the three largest distinct elements in the array.
If there are fewer than three distinct elements, return -1. Use a priority queue (heap) to
efficiently find and remove the largest elements.
Algorithm:
1. Input: An array of integers.
2. Output: The product of the three largest distinct elements or -1 if there are fewer than
three distinct elements.
3. Steps:
o Use a max-heap (priority queue) to efficiently find and remove the largest
elements.
o Insert all elements into the heap.
o Extract the top three distinct elements from the heap.
o If fewer than three distinct elements are found, return -1.
o Otherwise, return the product of the three largest distinct elements.
CODE:
class PriorityQueue :
def __init__(self) :
self.array = []
def is_empty(self) :
return not self.array
OUTPUT:
Problem Statement:
Given two sorted linked lists, merge them into a single sorted linked list. The new linked list
should be created by splicing together the nodes of the first two lists.
Algorithm:
1. Initialize Pointers: Start with two pointers, one for each linked list, and a dummy
node to build the merged list.
2. Compare Nodes: Compare the values of the nodes pointed to by the two pointers.
3. Append Smaller Node: Append the smaller node to the merged list and move the
corresponding pointer to the next node.
4. Repeat: Continue the process until one of the lists is exhausted.
5. Append Remaining Nodes: Append the remaining nodes from the non-exhausted list
to the merged list.
6. Return Merged List: Return the merged list, starting from the node after the dummy
node.
CODE:
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
if l1:
current.next = l1
else:
current.next = l2
return dummy.next
def createLinkedList(values):
dummy = ListNode()
current = dummy
for val in values:
current.next = ListNode(val)
current = current.next
return dummy.next
def printLinkedList(head):
current = head
while current:
print(current.val, end=" -> ")
current = current.next
# Example usage
if __name__ == "__main__":
# Create two sorted linked lists
l1 = createLinkedList([1, 3, 5])
l2 = createLinkedList([2, 4, 6])
OUTPUT:
AIM: Write a Program to find the Merge point of two linked lists(sorted).
Problem Statement:
Given two sorted linked lists, find the merge point where the two lists merge into a single
linked list. The merge point is the first node where the two lists share the same node. If the
two lists do not merge, return None.
Algorithm:
1. Initialize Pointers: Start with two pointers, one for each linked list (let's call
them p1 and p2).
2. Traverse the Lists: Traverse both linked lists simultaneously:
o If the current node in p1 is smaller than the current node in p2, move p1 to the
next node.
o If the current node in p2 is smaller than the current node in p1, move p2 to the
next node.
o If the current nodes in p1 and p2 are equal, that node is the merge point.
3. Handle Unequal Lengths: If one list is longer than the other, continue traversing the
longer list until the pointers meet.
4. Return the Merge Point: If a merge point is found, return the node. If the pointers
reach the end of both lists without finding a merge point, return None.
CODE:
class ListNode:
def __init__(self, value=0, next=None):
self.value = value
self.next = next
while p1 != p2:
p1 = p1.next if p1 else head2
p2 = p2.next if p2 else head1
return p1
def create_linked_list(values):
if not values:
return None
head = ListNode(values[0])
current = head
for value in values[1:]:
current.next = ListNode(value)
current = current.next
return head
# Example usage:
# Create two linked lists that merge at some point
# List 1: 1 -> 3 -> 5 -> 7 -> 9
# List 2: 2 -> 4 -> 7 -> 9
# The merge point is the node with value 7
OUTPUT:
Problem Statement:
Given a singly linked list, swap every two adjacent nodes and return the head of the modified
list. You must solve the problem without modifying the values in the list's nodes (i.e., only
nodes themselves may be changed.)
Algorithm:
1. Initialize Pointers:
o Use a dummy node to simplify edge cases (e.g., swapping the first two nodes).
o Use prev to keep track of the node before the pair to be swapped.
o Use curr to point to the first node of the pair.
2. Swap Nodes:
o While curr and curr.next are not None:
▪ Identify the two nodes to be swapped: first and second.
▪ Update prev.next to point to second.
▪ Update first.next to point to second.next.
▪ Update second.next to point to first.
▪ Move prev to first and curr to first.next.
3. Return the Modified List:
o The new head of the list is dummy.next.
CODE:
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
# Swapping
prev.next = second
first.next = second.next
second.next = first
return dummy.next
print("Original List:")
printList(head)
OUTPUT:
PRACTICAL – 10
Problem Statement:
Given a binary tree, write a function isValidBST to determine if it is a valid Binary Search
Tree (BST). A BST is valid if:
1. The left subtree of a node contains only nodes with values less than the node's value.
2. The right subtree of a node contains only nodes with values greater than the node's
value.
3. Both the left and right subtrees must also be valid BSTs.
Algorithm:
1. Define a helper function:
o The helper function will recursively check each node in the tree.
o It will take three parameters: the current node, the minimum allowed value
(min_val), and the maximum allowed value (max_val).
2. Base Case:
o If the current node is None, return True (an empty tree is a valid BST).
3. Check Current Node:
o If the current node's value is less than or equal to min_val or greater than or
equal to max_val, return False (it violates BST rules).
4. Recursive Case:
o Recursively check the left subtree, updating the max_val to the current node's
value.
o Recursively check the right subtree, updating the min_val to the current node's
value.
5. Return Result:
o If both subtrees are valid, return True.
OUTPUT:
Problem Statement:
Build a Binary Search Tree (BST):
Given a list of integers, write a program to construct a Binary Search Tree (BST) from the
list. The BST should satisfy the following properties:
1. The left subtree of a node contains only nodes with keys less than the node's key.
2. The right subtree of a node contains only nodes with keys greater than the node's key.
3. Both the left and right subtrees must also be binary search trees.
Algorithm:
1. Define the Node Structure:
o Each node in the BST will have a value, a left child, and a right child.
2. Insertion into BST:
o Start with the root node. If the tree is empty, the first element becomes the
root.
o For each subsequent element, compare it with the current node:
▪ If the element is less than the current node's value, move to the left
subtree.
▪ If the element is greater than the current node's value, move to the right
subtree.
o Repeat the process until you find an empty spot (where the new node can be
inserted).
3. In-order Traversal (Optional):
o To verify the BST, perform an in-order traversal. The output should be a sorted
list of elements.
# Example usage
if __name__ == "__main__":
elements = [50, 30, 20, 40, 70, 60, 80]
root = build_bst(elements)
OUTPUT:
Problem Statement:
Given a binary tree, determine its maximum depth. The maximum depth is the number of
nodes along the longest path from the root node down to the farthest leaf node. A leaf node is
a node with no children.
Algorithm:
1. Base Case: If the tree is empty (i.e., the root is None), return 0.
2. Recursive Case:
o Calculate the depth of the left subtree.
o Calculate the depth of the right subtree.
o The depth of the current node is the maximum of the depths of the left and
right subtrees, plus 1 (for the current node).
3. Return: The depth of the tree.
CODE:
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
# The depth of the current node is the maximum of the left and right depths, plus 1
return max(left_depth, right_depth) + 1
# Example usage:
# Constructing a binary tree:
# 3
# /\
# 9 20
# / \
# 15 7
root = TreeNode(3)
root.left = TreeNode(9)
root.right = TreeNode(20)
root.right.left = TreeNode(15)
root.right.right = TreeNode(7)
OUTPUT:
AIM: Write a Program to Understand and implement Tree traversals i.e. Pre-
Order Post-Order, In-Order.
Problem Statement:
Implement a binary tree and perform the following tree traversals:
1. Pre-Order Traversal: Visit the root node, then traverse the left subtree, and finally
traverse the right subtree.
2. In-Order Traversal: Traverse the left subtree, visit the root node, and then traverse
the right subtree.
3. Post-Order Traversal: Traverse the left subtree, traverse the right subtree, and finally
visit the root node.
Algorithms:
1. Pre-Order Traversal:
o Visit the root node.
o Recursively perform a pre-order traversal of the left subtree.
o Recursively perform a pre-order traversal of the right subtree.
2. In-Order Traversal:
o Recursively perform an in-order traversal of the left subtree.
o Visit the root node.
o Recursively perform an in-order traversal of the right subtree.
3. Post-Order Traversal:
o Recursively perform a post-order traversal of the left subtree.
o Recursively perform a post-order traversal of the right subtree.
o Visit the root node.
# Pre-Order Traversal
def pre_order_traversal(root):
if root:
print(root.val, end=" ") # Visit the root
pre_order_traversal(root.left) # Traverse the left subtree
pre_order_traversal(root.right) # Traverse the right subtree
# In-Order Traversal
def in_order_traversal(root):
if root:
in_order_traversal(root.left) # Traverse the left subtree
print(root.val, end=" ") # Visit the root
in_order_traversal(root.right) # Traverse the right subtree
# Post-Order Traversal
def post_order_traversal(root):
if root:
post_order_traversal(root.left) # Traverse the left subtree
post_order_traversal(root.right) # Traverse the right subtree
print(root.val, end=" ") # Visit the root
print("Pre-Order Traversal:")
pre_order_traversal(root)
print("\n")
print("In-Order Traversal:")
in_order_traversal(root)
print("\n")
print("Post-Order Traversal:")
post_order_traversal(root)
print("\n")
OUTPUT:
Problem Statement:
Given a Binary Search Tree (BST), perform a Boundary Traversal of the tree. Boundary
Traversal involves traversing the boundary nodes of the tree in the following order:
1. Left Boundary: Traverse the left boundary from the root to the leftmost leaf
(excluding the leaf if it has a right subtree).
2. Leaf Nodes: Traverse all the leaf nodes from left to right.
3. Right Boundary: Traverse the right boundary from the rightmost leaf to the root
(excluding the leaf if it has a left subtree).
The traversal should output the nodes in the correct order.
Algorithm:
1. Left Boundary Traversal:
o Start from the root and move to the left child until a leaf node is reached.
o If a node has no left child, move to the right child.
o Exclude the leaf node if it has a right subtree.
2. Leaf Nodes Traversal:
o Perform an inorder traversal and collect all leaf nodes.
3. Right Boundary Traversal:
o Start from the root and move to the right child until a leaf node is reached.
o If a node has no right child, move to the left child.
o Exclude the leaf node if it has a left subtree.
o Reverse the collected nodes to get the correct order.
4. Combine Results:
o Combine the left boundary, leaf nodes, and right boundary to get the final
result.
def is_leaf(node):
"""Check if a node is a leaf node."""
return node.left is None and node.right is None
def boundary_traversal(root):
"""Perform boundary traversal of the BST."""
if root is None:
return []
result = []
return result
# Example usage
if __name__ == "__main__":
# Construct a BST
root = TreeNode(20)
root.left = TreeNode(8)
root.left.left = TreeNode(4)
root.left.right = TreeNode(12)
root.left.right.left = TreeNode(10)
root.left.right.right = TreeNode(14)
root.right = TreeNode(22)
root.right.right = TreeNode(25)
OUTPUT:
Problem Statement:
Given a binary tree and two nodes p and q, find the lowest common ancestor (LCA) of the
two nodes. The lowest common ancestor is the lowest node in the tree that has
both p and q as descendants (where we allow a node to be a descendant of itself).
Algorithm:
1. Base Case: If the current node is None, return None.
2. Recursive Case:
o If the current node is either p or q, return the current node.
o Recursively find the LCA in the left subtree.
o Recursively find the LCA in the right subtree.
3. Decision:
o If both the left and right subtrees return a non-None value, it
means p and q are found in different subtrees, so the current node is the LCA.
o If only one subtree returns a non-None value, return that value (either p or q is
found in that subtree).
o If both subtrees return None, return None.
CODE:
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
# Example usage:
# Constructing the binary tree
# 3
# /\
# 5 1
# /\/\
# 6 20 8
# /\
# 7 4
# Find LCA
lca = lowestCommonAncestor(root, p, q)
print(f"LCA of {p.val} and {q.val} is {lca.val}") # Output: LCA of 5 and 4 is 5
OUTPUT:
Problem Statement:
Given two binary trees, write a program to determine if they are mirrors of each other. Two
binary trees are considered mirrors if they are symmetric in structure, and the corresponding
nodes have the same values but are mirrored in position.
Algorithm:
1. Base Case: If both trees are empty (None), they are mirrors of each other.
2. Recursive Case:
o Check if the current nodes of both trees have the same value.
o Recursively check if the left subtree of the first tree is a mirror of the right
subtree of the second tree.
o Recursively check if the right subtree of the first tree is a mirror of the left
subtree of the second tree.
3. If any of the above conditions fail, the trees are not mirrors.
CODE:
class Node:
def _init_(self,value):
self.value = value
self.left = self.right = None
tree1 = Node(1)
tree1.left = Node(2)
tree1.right = Node(3)
tree2 = Node(1)
tree2.left = Node(3)
tree2.right = Node(2)
if is_mirrored(tree1, tree2):
print("Mirror Tree")
else:
print("Not mirrored tree")
OUTPUT:
Problem Statement:
Design and implement a basic hash function in Python that can be used to store and retrieve
key-value pairs. The hash function should map keys to indices in a fixed-size array (hash
table). Demonstrate the usage of this hash function by storing and retrieving key-value pairs.
Algorithm:
1. Define a Hash Function:
o The hash function will take a key as input and return an integer representing
the index in the hash table.
o A simple hash function can be implemented by summing the ASCII values of
the characters in the key and then taking the modulo with the size of the hash
table.
2. Create a Hash Table:
o The hash table will be a list of a fixed size, where each index can store a key-
value pair.
3. Insert Key-Value Pair:
o Use the hash function to determine the index for the key.
o Store the key-value pair at the computed index in the hash table.
4. Retrieve Value by Key:
o Use the hash function to determine the index for the key.
o Retrieve the value stored at the computed index in the hash table.
5. Handle Collisions:
o For simplicity, this implementation will not handle collisions. In a real-world
scenario, techniques like chaining or open addressing would be used.
CODE:
class HashTable:
def __init__(self):
self.table = {}
hash_table = HashTable()
hash_table.insert("name", "Kirtan")
hash_table.insert("age", 20)
print("Name:", hash_table.retrieve("name"))
print("Age:", hash_table.retrieve("age"))
OUTPUT:
Problem Statement:
Implement a hash table using separate chaining for collision handling. The hash table should
support the following operations:
1. Insertion: Insert a key-value pair into the hash table. If the key already exists, update
the value.
2. Deletion: Remove a key-value pair from the hash table based on the key.
3. Search: Retrieve the value associated with a given key from the hash table.
The hash table should handle collisions using separate chaining, where each bucket in the
hash table is a linked list of key-value pairs.
Algorithm:
1. Hash Function:
o Use a simple hash function, such as hash(key) % size, where size is the
number of buckets in the hash table.
2. Insertion:
o Compute the hash value for the key.
o Locate the bucket corresponding to the hash value.
o If the bucket is empty, create a new linked list node with the key-value pair
and add it to the bucket.
o If the bucket is not empty, traverse the linked list to check if the key already
exists. If it does, update the value. If not, append the new key-value pair to the
end of the linked list.
3. Deletion:
o Compute the hash value for the key.
o Locate the bucket corresponding to the hash value.
o Traverse the linked list to find the key. If found, remove the node from the
linked list.
4. Search:
Name: KIRTAN C. PARSANA
Enrollment No. : 2303051050377 Division: 4A2
Faculty of Engineering & Technology
Subject Name: Competitive Coding
Subject Code: 303105259
B.Tech. CSE Year 2 Semester 4
o Compute the hash value for the key.
o Locate the bucket corresponding to the hash value.
o Traverse the linked list to find the key. If found, return the associated value. If
not found, return None.
CODE:
class HashTable:
def __init__(self, size):
self.size = size
self.table = [[] for _ in range(size)]
def __str__(self):
return str(self.table)
# Example usage
if __name__ == "__main__":
ht = HashTable(10)
ht.insert("apple", 5)
ht.insert("banana", 10)
ht.insert("orange", 15)
ht.delete("banana")
print("Hash Table after deleting 'banana':")
print(ht)
ht.insert("apple", 20)
print("Hash Table after updating 'apple':")
print(ht)
OUTPUT:
Problem Statement:
Given an array of integers nums and an integer target, return the indices of the two numbers
such that they add up to the target. You may assume that each input would have exactly one
solution, and you may not use the same element twice. You can return the answer in any
order.
Algorithm:
1. Initialize a Hash Map: Create an empty hash map (dictionary in Python) to store the
numbers and their indices.
2. Iterate through the array: Loop through the array nums using an index i.
3. Calculate the complement: For each number nums[i], calculate the complement
as complement = target - nums[i].
4. Check if the complement exists in the hash map:
o If it exists, return the current index i and the index stored in the hash map for
the complement.
o If it does not exist, store the current number nums[i] and its index i in the hash
map.
5. Return the result: If no solution is found, return an empty list or handle it as per
requirements.
CODE:
def two_sum(nums, target):
hashmap = {}
OUTPUT:
Problem Statement:
A Trie (pronounced "try") is a tree-like data structure used to store a dynamic set of strings. It
is particularly useful for operations like searching, inserting, and removing strings efficiently.
Each node in a Trie represents a character of a string, and the path from the root to a node
represents a prefix of one or more strings.
Your task is to implement a Trie data structure in Python with the following operations:
1. Insert: Insert a string into the Trie.
2. Search: Check if a string exists in the Trie.
3. Remove: Remove a string from the Trie.
Algorithm:
1. Trie Node Structure:
o Each node in the Trie will have:
▪ A dictionary to store child nodes (key: character, value: child node).
▪ A boolean flag is_end_of_word to indicate if the node represents the
end of a word.
2. Insert Operation:
o Start from the root node.
o For each character in the string, check if it exists in the current node's children.
o If it doesn't exist, create a new node and add it to the children.
o Move to the child node and repeat the process for the next character.
o After processing all characters, mark the last node as the end of the word.
3. Search Operation:
o Start from the root node.
o For each character in the string, check if it exists in the current node's children.
o If it doesn't exist, return False.
o Move to the child node and repeat the process for the next character.
CODE:
class TrieNode:
def __init__(self):
self.children = {}
self.is_end_of_word = False
class Trie:
def __init__(self):
self.root = TrieNode()
char = word[depth]
if char not in node.children:
return False
if should_delete_child:
del node.children[char]
return len(node.children) == 0
return False
_remove(self.root, word, 0)
trie.remove("app")
print(trie.search("app")) # Output: False
print(trie.search("apple")) # Output: True
OUTPUT:
Problem Statement:
Huffman coding is a lossless data compression algorithm. The idea is to assign variable-
length codes to input characters, with shorter codes assigned to more frequent characters. The
algorithm builds a binary tree (Huffman tree) based on the frequency of characters and
generates codes by traversing the tree. The goal is to implement Huffman coding to compress
and decompress a given input string.
Algorithm:
1. Frequency Calculation: Calculate the frequency of each character in the input string.
2. Priority Queue: Create a priority queue (min-heap) where the node with the lowest
frequency has the highest priority.
3. Build Huffman Tree:
o Extract two nodes with the lowest frequency from the priority queue.
o Create a new internal node with a frequency equal to the sum of the two nodes'
frequencies.
o Add the new node back to the priority queue.
o Repeat until there is only one node left in the queue, which becomes the root
of the Huffman tree.
4. Generate Codes: Traverse the Huffman tree to assign binary codes to each character.
5. Encode: Use the generated codes to encode the input string.
6. Decode: Use the Huffman tree to decode the encoded string back to the original
string.
CODE:
import heapq
from collections import defaultdict, Counter
class HuffmanNode:
def __init__(self, char, freq):
Name: KIRTAN C. PARSANA
Enrollment No. : 2303051050377 Division: 4A2
Faculty of Engineering & Technology
Subject Name: Competitive Coding
Subject Code: 303105259
B.Tech. CSE Year 2 Semester 4
self.char = char
self.freq = freq
self.left = None
self.right = None
def build_huffman_tree(freq_map):
priority_queue = [HuffmanNode(char, freq) for char, freq in freq_map.items()]
heapq.heapify(priority_queue)
return heapq.heappop(priority_queue)
def huffman_encode(data):
freq_map = Counter(data)
root = build_huffman_tree(freq_map)
codes = {}
generate_codes(root, "", codes)
return "".join(decoded_data)
# Encoding
encoded_data, huffman_tree = huffman_encode(input_data)
print(f"Encoded Data: {encoded_data}")
# Decoding
decoded_data = huffman_decode(encoded_data, huffman_tree)
print(f"Decoded Data: {decoded_data}")
# Verify
assert input_data == decoded_data, "Decoded data does not match the original!"
OUTPUT:
Problem Statement:
Given a string, find the number of distinct substrings in the string. A substring is a contiguous
sequence of characters within a string. For example, the string "abc" has the following
substrings: "a", "b", "c", "ab", "bc", and "abc". The total number of distinct substrings is 6.
Algorithm:
1. Initialize a set to store unique substrings: A set is used because it automatically
handles duplicates.
2. Iterate over the string: Use nested loops to generate all possible substrings.
o The outer loop runs from the start of the string to the end.
o The inner loop runs from the current position of the outer loop to the end of
the string.
3. Generate substrings: For each pair of indices (i, j), extract the substring from index i
to j+1.
4. Store substrings in the set: Add each generated substring to the set.
5. Count the number of unique substrings: The size of the set at the end of the
iteration will be the number of distinct substrings.
6. Return the count: Output the number of distinct substrings.
OUTPUT:
Problem Statement
Given a tree data structure, where each node contains a word, write a program to find the
total number of words in the tree. The tree can be represented as a collection of nodes, where
each node has a value (the word) and a list of children nodes.
Algorithm
1. Define the Tree Node Structure:
o Each node should have a value (the word) and a list of children nodes.
2. Traverse the Tree:
o Use a Depth-First Search (DFS) or Breadth-First Search (BFS) approach to
traverse the tree.
o For each node visited, increment a counter to keep track of the number of
words.
3. Return the Count:
o After traversing the entire tree, return the total count of words.
CODE:
class TreeNode:
def __init__(self, value):
self.value = value
self.children = []
def count_words_in_tree(root):
if not root:
return 0
return count
# Example Usage
if __name__ == "__main__":
# Create a sample tree
root = TreeNode("Hello")
child1 = TreeNode("World")
child2 = TreeNode("Python")
child3 = TreeNode("Programming")
root.children.append(child1)
root.children.append(child2)
child2.children.append(child3)
OUTPUT:
Problem Statement:
Given a binary tree, print the left view of the tree. The left view of a binary tree contains all
nodes that are visible when the tree is viewed from the left side. The leftmost node at each
level is included in the left view.
Algorithm:
1. If the tree is empty, return an empty list.
2. Use a queue to perform a level-order traversal of the tree.
3. For each level, store the first node encountered in the left view list.
4. Continue traversing the tree level by level until all nodes are visited.
5. Print or return the left view list.
CODE:
class Node:
def __init__(self, value):
self.value = value
self.left = self.right = None
if level == len(view):
print(root.value, end=" ")
view.append(root.value)
view = []
root = Node(1)
root.left = Node(2)
root.right = Node(3)
root.left.left = Node(4)
root.left.right = Node(5)
root.left.right.right = Node(6)
left_view(root)
OUTPUT:
Problem Statement:
Given a binary tree, write a program to perform a Level Order Traversal (also known as
Breadth-First Traversal). This traversal visits all nodes at each depth level before moving
on to the next level.
Algorithm:
1. If the tree is empty, return.
2. Initialize a queue and enqueue the root node.
3. While the queue is not empty:
o Dequeue a node from the front.
o Process the dequeued node (print or store its value).
o If the dequeued node has a left child, enqueue it.
o If the dequeued node has a right child, enqueue it.
4. Repeat until all nodes have been processed.
CODE:
from collections import deque
class Node:
def __init__(self, value):
self.value = value
self.left = None
self.right = None
def level_order_traversal(root):
if not root:
return
queue = deque([root])
while queue:
current = queue.popleft()
print(current.value, end=" ") # Process the current node
if current.left:
queue.append(current.left)
if current.right:
queue.append(current.right)
if __name__ == "__main__":
root = Node(1)
root.left = Node(2)
root.right = Node(3)
root.left.left = Node(4)
root.left.right = Node(5)
root.right.left = Node(6)
root.right.right = Node(7)
OUTPUT: