Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 59 additions & 23 deletions Exercise_1.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,60 @@
"""
Time Complexity:
- push: O(1)
- pop: O(1)
- peek: O(1)
- size: O(1)

Space Complexity:
- O(n) where n = capacity
- Here capacity is fixed at 10000, so space complexity is O(1) in practical terms.

Simple fixed-capacity stack implementation.
"""

class myStack:
#Please read sample.java file before starting.
#Kindly include Time and Space complexity at top of each file
def __init__(self):

def isEmpty(self):

def push(self, item):

def pop(self):


def peek(self):

def size(self):

def show(self):


s = myStack()
s.push('1')
s.push('2')
print(s.pop())
print(s.show())
# Please read sample.java file before starting.
# Kindly include Time and Space complexity at top of each file
def __init__(self, capacity=10000):
self.capacity = capacity
self.stack = [None] * capacity
self.top = -1

def isFull(self):
return self.top == self.capacity - 1

def isEmpty(self):
return self.top == -1

def push(self, item):
if self.isFull():
raise OverflowError("Stack is full. Stack Overflow")
self.top += 1
self.stack[self.top] = item

def pop(self):
if self.isEmpty():
raise IndexError("Stack is empty. Stack Underflow")
item = self.stack[self.top]
self.stack[self.top] = None
self.top -= 1
return item

def peek(self):
if self.isEmpty():
raise IndexError("Stack is empty.")
return self.stack[self.top]

def size(self):
return self.top + 1

def show(self):
return [self.stack[i] for i in range(self.top + 1)]


if __name__ == "__main__":
s = myStack()
s.push('1')
s.push('2')
print(s.pop())
print(s.show())
42 changes: 40 additions & 2 deletions Exercise_2.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,54 @@
"""
1. Push
Time complexity: O(1) - constant number of pointer updates
regardless of stack size.

Space complexity: O(1) - one new node is allocated per call;
does not depend on the current size of the stack.

2. Pop
Time complexity: O(1) - constant number of pointer updates
regardless of stack size.

Space complexity: O(1) - one new node is allocated per call;
does not depend on the current size of the stack.
"""
class Node:
def __init__(self, data):
self.data = data
self.next = None

class Stack:
def __init__(self):
self.head = None
self.size = 0

def push(self, data):

def push(self, data):
"""
Approach: Create a new node, point its `next` at the
current head (the old top of the stack), then make the
new node the head. This inserts at the front of the
linked list, which is O(1) since no traversal is needed.
"""
new_node = Node(data)
new_node.next = self.head
self.head = new_node
self.size += 1

def pop(self):

"""
Approach: Check if the stack is empty. If not, remove the head node
(the top of the stack) and return its data. Update the head to point
to the next node in the list.
"""
if self.head is None:
return None
data = self.head.data
self.head = self.head.next
self.size -= 1
return data

a_stack = Stack()
while True:
#Give input as string if getting an EOF error. Give input like "push 10" or "pop"
Expand Down
150 changes: 147 additions & 3 deletions Exercise_3.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,29 @@
"""
Time complexity and space complexity for the following operations on a singly-linked list:

1. Append
Time complexity: O(n) - we may need to traverse the entire list to find the end.
Space complexity: O(1) - we only allocate a new node, which does not depend on the current size of the list.

2. Find
Time complexity: O(n) - we may need to traverse the entire list to find the key.
Space complexity: O(1) - we do not allocate any additional space that depends on the size of the list.

3. Remove
Time complexity: O(n) - we may need to traverse the entire list to find the key to remove.
Space complexity: O(1) - we do not allocate any additional space that depends on the size of the list.

"""


class ListNode:
"""
A node in a singly-linked list.
"""
def __init__(self, data=None, next=None):

self.data = data
self.next = next

class SinglyLinkedList:
def __init__(self):
"""
Expand All @@ -16,17 +36,141 @@ def append(self, data):
"""
Insert a new element at the end of the list.
Takes O(n) time.
"""

Approach: Create a new node with the given data.
If the list is empty, set the head to this new node.
Otherwise, traverse to the end of the list and
set the next pointer of the last node to the new node.

"""

new_node = ListNode(data) # was: Node(data)
if self.head is None:
self.head = new_node
else:
current = self.head
while current.next:
current = current.next
current.next = new_node

def find(self, key):
"""
Search for the first element with `data` matching
`key`. Return the element or `None` if not found.
Takes O(n) time.

Approach: Start from the head and traverse the list,
checking each node's data against the key.
If a match is found, return that node.
If the end of the list is reached without finding a match,
return None.
"""

current = self.head
while current:
if current.data == key:
return current
current = current.next
return None

def remove(self, key):
"""
Remove the first occurrence of `key` in the list.
Takes O(n) time.

Approach: Traverse the list while keeping track of the
previous node. If the current node's data matches the key,
update the previous node's next pointer to skip the current node.
If the node to remove is the head, update the head to the next node.
"""
current = self.head
previous = None
while current:
if current.data == key:
if previous is None:
self.head = current.next
else:
previous.next = current.next
return
previous = current
current = current.next

def run_tests():
# --- Test append ---
ll = SinglyLinkedList()
ll.append(10)
ll.append(20)
ll.append(30)

values = []
current = ll.head
while current:
values.append(current.data)
current = current.next
assert values == [10, 20, 30], f"append failed: {values}"
print("append: OK ->", values)

# --- Test find (existing) ---
node = ll.find(20)
assert node is not None and node.data == 20, "find failed to locate existing key"
print("find existing: OK -> found", node.data)

# --- Test find (missing) ---
node = ll.find(999)
assert node is None, "find should return None for missing key"
print("find missing: OK -> None")

# --- Test remove head ---
ll.remove(10)
values = []
current = ll.head
while current:
values.append(current.data)
current = current.next
assert values == [20, 30], f"remove head failed: {values}"
print("remove head: OK ->", values)

# --- Test remove middle/tail ---
ll.append(40)
ll.remove(30) # now middle-ish
values = []
current = ll.head
while current:
values.append(current.data)
current = current.next
assert values == [20, 40], f"remove middle failed: {values}"
print("remove middle: OK ->", values)

# --- Test remove tail ---
ll.remove(40)
values = []
current = ll.head
while current:
values.append(current.data)
current = current.next
assert values == [20], f"remove tail failed: {values}"
print("remove tail: OK ->", values)

# --- Test remove nonexistent key (should not raise, no-op) ---
ll.remove(999)
values = []
current = ll.head
while current:
values.append(current.data)
current = current.next
assert values == [20], f"remove nonexistent changed list: {values}"
print("remove nonexistent: OK -> no change", values)

# --- Test remove last remaining element ---
ll.remove(20)
assert ll.head is None, "list should be empty after removing last element"
print("remove last element: OK -> list is empty")

# --- Test operations on empty list ---
assert ll.find(1) is None, "find on empty list should return None"
ll.remove(1) # should not raise
print("empty list operations: OK -> no crash")

print("\nAll tests passed!")


run_tests()