- Notifications
You must be signed in to change notification settings - Fork0
License
Badhansen/InterviewCodeBook
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
- 1️⃣Arrays & Hashing
- 2️⃣Strings
- 3️⃣Sorting
- 7️⃣Trees & Graphs
- 8️⃣Recursion & Backtracking
- 9️⃣ Dynamic Programming (DP) (Knapsack, Memoization, State Transition)
- 🔟 Bit Manipulation (XOR Tricks, Counting Bits, Power of Two)
names= ['Badhan','Anik','Alamin']ages= [30,29,29]forname,ageinzip(names,ages):print(name,age)"""OutputBadhan 30Anik 29Alamin 29"""
Description:Lists in Python are resizable, meaning you can add elements to them using methods likeappend() orextend().
Example:
my_list= [1,2,3]my_list.append(4)# Adds a single element to the endprint(my_list)# Output: [1, 2, 3, 4]
Description:Lists can also be resized by removing elements using methods likeremove(),pop(), ordel.
Example:
my_list= [1,2,3,4]my_list.pop()# Removes and returns the last itemprint(my_list)# Output: [1, 2, 3]my_list.remove(2)# Removes the first occurrence of the valueprint(my_list)# Output: [1, 3]
Description:Lists can be concatenated using the+ operator or theextend() method.
Example:
list1= [1,2]list2= [3,4]concatenated_list=list1+list2# Using +print(concatenated_list)# Output: [1, 2, 3, 4]list1.extend(list2)# Using extend()print(list1)# Output: [1, 2, 3, 4]
Description:Lists can be initialized in various ways, including using list literals or comprehensions.
Example:
empty_list= []# Empty listlist_with_values= [1,2,3]# List with initial valueslist_from_range=list(range(5))# List from rangeprint(list_from_range)# Output: [0, 1, 2, 3, 4]
Description:Cloning a list can be done using slicing or thecopy() method.
Example:
original_list= [1,2,3]cloned_list=original_list[:]# Using slicingprint(cloned_list)# Output: [1, 2, 3]another_clone=original_list.copy()# Using copy()print(another_clone)# Output: [1, 2, 3]
Description:List comprehensions provide a concise way to create lists. They consist of brackets containing an expression followed by afor clause.
Example:
squares= [x**2forxinrange(5)]print(squares)# Output: [0, 1, 4, 9, 16]
list.pop(i)# O(n)list.insert(i,val)# O(n)
Description: 2D and 3D List Comprehension. You want to create an x m orl x n x m grids. Just follow the dimension in reverse order.
Example:
n=4# Number of rowsm=5# Number of columnsdefault_value=0# Default value for each cell# Create n x m 2D gridgrid_2d= [[default_valuefor_inrange(m)]for_inrange(n)]# Print the 2D gridforrowingrid_2d:print(row)"""Output[0, 0, 0, 0, 0][0, 0, 0, 0, 0][0, 0, 0, 0, 0][0, 0, 0, 0, 0]"""l=3# Number of layersn=4# Number of rowsm=5# Number of columnsdefault_value=0# Default value for each cell# Create l x n x m 3D gridgrid_3d= [[[default_valuefor_inrange(m)]for_inrange(n)]for_inrange(l)]# Print the 3D gridforlayeringrid_3d:print("Layer:")forrowinlayer:print(row)"""OutputLayer:[0, 0, 0, 0, 0][0, 0, 0, 0, 0][0, 0, 0, 0, 0][0, 0, 0, 0, 0]Layer:[0, 0, 0, 0, 0][0, 0, 0, 0, 0][0, 0, 0, 0, 0][0, 0, 0, 0, 0]Layer:[0, 0, 0, 0, 0][0, 0, 0, 0, 0][0, 0, 0, 0, 0]"""
Description:
Hashmaps and Hashsets are data structures that provide efficient storage and retrieval of data using a hash-based mechanism.
- Hashmaps (or dictionaries in Python) store key-value pairs, allowing quick access to values based on their keys.
- Hashsets store unique elements and are optimized for fast membership checks.
Hashmaps Example:
# Initialize a dictionarymy_dict= {'a':1,'b':2,'c':3}# Access Methodsvalue=my_dict.get('d',0)# O(1)keys=my_dict.keys()# O(1)values=my_dict.values()# O(1)items=my_dict.items()# O(1)# Using a for loopforkeyinmy_dict:print(key)# Using the dict.keys() methodforkeyinmy_dict.keys():print(key)# Using the dict.values() methodforvalueinmy_dict.values():print(value)# Using the dict.items() methodforkey,valueinmy_dict.items():print(key,value)# Modification Methodspopped_value=my_dict.pop('b',0)# O(1)popped_item=my_dict.popitem()# O(1), Removes and returns the last inserted itemmy_dict.update({'d':4,'e':5})# O(k), where k is the number of items being addedmy_dict.clear()# O(n)copy_dict=my_dict.copy()# O(n)value=my_dict.setdefault('z',30)# O(1)# Check Key Existenceif'x'inmy_dict:# O(1)print('Key exists')# Create a dictionary of squaressquares= {x:x*xforxinrange(6)}# O(n)# defaultdictfromcollectionsimportdefaultdict# Create a default dictionary with int as the default factory# The key will be the default value of int, which is 0.dict1=defaultdict(int)# Create a defaultdict with -1 as the default valuedict2=defaultdict(lambda:-1)default_dict=defaultdict(int)# O(1)default_dict['a']+=1# O(1)# CounterfromcollectionsimportCountercounter=Counter(['a','b','c','a','b','a'])# O(n)print(counter.most_common(2))# O(k log k), where k is the number of unique elements# Tuples as Keystuple_keys= {(1,2):'a', (3,4):'b'}# O(1) for access and insertiontuple_keys[(5,6)]='c'fromcollectionsimportCounter# Sample counter mapcounter_map=Counter({'a':5,'b':1,'c':3,'d':4})# Sorting using sorted() functionsorted_items=sorted(counter_map.items(),key=lambdax:x[1],reverse=True)# This will create a list not map# Printing sorted itemsprint(sorted_items)# [('a', 5), ('d', 4), ('c', 3), ('b', 1)]
| Operation | Time Complexity |
|---|---|
Access (get,keys, etc.) | O(1) |
| Insert/Update | O(1) |
Delete (pop,popitem) | O(1) |
| Copy | O(n) |
| Clear | O(n) |
| Comprehension | O(n) |
defaultdict Access | O(1) |
| Counter Initialization | O(n) |
Countermost_common | O(k log k) |
| Tuple Key Access | O(1) |
Hashsets Example:
# Sets= {1,2,3}s.clear()# O(1), Clear the sets= {1,2,3}s.add(4)# O(1)print("After add(4):",s)# {1, 2, 3, 4}# Remove element (throws KeyError if item is not present)s.remove(2)# O(1)print("After remove(2):",s)# {2, 3, 4}# Discard element (no error if item is not present)s.discard(5)# O(1)s.remove(3)# O(1)print("After discard(5) and remove(3):",s)# {2, 4}# Pop a random elementpopped_item=s.pop()# O(1), removes random item (since unordered)print(f"Popped item:{popped_item}")print("After pop():",s)# Set ComparisonsA= {1,2,3}B= {3,4,5}# Check if sets are disjoint (no common elements)print("A.isdisjoint(B):",A.isdisjoint(B))# False (O(min(len(A), len(B))))# Check if A is subset of B (A <= B)print("A.issubset(B):",A.issubset(B))# False (O(min(len(A), len(B))))# Check if A is superset of B (A >= B)print("A.issuperset(B):",A.issuperset(B))# False (O(min(len(A), len(B))))# Set Operations# Difference: Returns elements in A but not in Bprint("A.difference(B):",A.difference(B))# {1, 2} (O(len(A)))# Update A with difference of BA.difference_update(B)# O(len(B))print("After A.difference_update(B):",A)# Intersection: Returns elements in both A and BA= {1,2,3}print("A.intersection(B):",A.intersection(B))# {3} (O(min(len(A), len(B))))# Update A with intersection of A and BA.intersection_update(B)# O(min(len(A), len(B)))print("After A.intersection_update(B):",A)# Symmetric Difference: Returns elements in A or B but not bothA= {1,2,3}print("A.symmetric_difference(B):",A.symmetric_difference(B))# {1, 2, 4, 5} (O(len(A) + len(B)))# Update A with symmetric difference of A and BA.symmetric_difference_update(B)# O(len(A) + len(B))print("After A.symmetric_difference_update(B):",A)# Union OperationsA= {1,2,3}B= {3,4,5}# Union: Returns elements in A or B (without duplicates)print("A.union(B):",A.union(B))# {1, 2, 3, 4, 5} (O(len(A) + len(B)))# Update A with the union of A and BA.update(B)# O(len(B))print("After A.update(B):",A)
| Operation | Time Complexity |
|---|---|
add(item) | O(1) |
remove(item) | O(1) (throwsKeyError if item not found) |
discard(item) | O(1) |
pop() | O(1) |
isdisjoint(setB) | O(min(len(A), len(B))) |
issubset(setB) | O(min(len(A), len(B))) |
issuperset(setB) | O(min(len(A), len(B))) |
difference(setB) | O(len(A)) |
difference_update(setB) | O(len(B)) |
intersection(setB) | O(min(len(A), len(B))) |
intersection_update(setB) | O(min(len(A), len(B))) |
symmetric_difference(setB) | O(len(A) + len(B)) |
symmetric_difference_update(setB) | O(len(A) + len(B)) |
union(setB) | O(len(A) + len(B)) |
update(setB) | O(len(B)) |
Description:
In Python, a string is a sequence of characters enclosed within single quotes ('), double quotes ("), or triple quotes (''' or""").
Strings are immutable, meaning their content cannot be changed after creation. Python provides a rich set of methods and operations to manipulate and process strings efficiently.
Example:
fromcollectionsimportCounter# Example strings="hello world"print("Original string:",s)# Output: hello world# 1. Length of the stringprint("Length of string:",len(s))# O(1), Output: 11# 2. Strip whitespaces_padded=" hello world "print("Stripped:",s_padded.strip())# O(n), Output: hello worldprint("Left Stripped:",s_padded.lstrip())# O(n), Output: hello worldprint("Right Stripped:",s_padded.rstrip())# O(n), Output: hello world# 3. Split and joinfruits="apple,banana,cherry"fruit_list=fruits.split(",")# O(n)print("Split into list:",fruit_list)# Output: ['apple', 'banana', 'cherry']print("Joined with ', ':",", ".join(fruit_list))# O(n), Output: apple, banana, cherry# 4. Finding substringsprint("First occurrence of 'o':",s.find("o"))# O(n), Output: 4print("Last occurrence of 'o':",s.rfind("o"))# O(n), Output: 7print("Index of 'world':",s.index("world"))# O(n), Output: 6# 5. Replace substringsprint("Replace 'world' with 'Python':",s.replace("world","Python"))# O(n), Output: hello Python# 6. Count occurrencesprint("Count of 'l' in string:",s.count("l"))# O(n), Output: 3# 7. Startswith and endswithprint("Starts with 'hello':",s.startswith("hello"))# O(n), Output: Trueprint("Ends with 'world':",s.endswith("world"))# O(n), Output: True# 8. Check for alpha, digit, alphanumeric, and whitespacesample="abc123"print("Is alpha:",sample.isalpha())# O(n), Output: Falseprint("Is digit:","123".isdigit())# O(n), Output: Trueprint("Is alphanumeric:",sample.isalnum())# O(n), Output: Trueprint("Is whitespace:"," ".isspace())# O(n), Output: True# 9. Changing caseprint("Uppercase:",s.upper())# O(n), Output: HELLO WORLDprint("Lowercase:",s.lower())# O(n), Output: hello worldprint("Capitalized:",s.capitalize())# O(n), Output: Hello worldprint("Title case:",s.title())# O(n), Output: Hello World# 10. ASCII and Unicode conversionsprint("ASCII value of 'a':",ord('a'))# O(1), Output: 97print("Character for ASCII 97:",chr(97))# O(1), Output: a# 11. Checking substring existenceprint("'world' in string:","world"ins)# O(n), Output: True# Palindrome checkdefis_palindrome(st):st=st.lower()returnst==st[::-1]# O(n)print("Is 'Madam' a palindrome?",is_palindrome("Madam"))# Output: True# Character frequency countfrequency=Counter(s)# O(n)print("Character frequency:",frequency)# Output: Counter({'l': 3, 'o': 2, 'h': 1, 'e': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1})# Anagram checkdefis_anagram(s1,s2):returnsorted(s1)==sorted(s2)# O(n log n)print("Is 'listen' an anagram of 'silent'?",is_anagram("listen","silent"))# Output: True# Removing duplicatesdefremove_duplicates(st):return"".join(dict.fromkeys(st))# O(n)print("Remove duplicates from 'aabbcc':",remove_duplicates("aabbcc"))# Output: abc
| Operation | Time Complexity | Description |
|---|---|---|
len(s) | O(1) | Get the length of the string. |
strip(),lstrip(),rstrip() | O(n) | Remove leading/trailing whitespace. |
split(delimiter) | O(n) | Split the string into a list based on the delimiter. |
join(list) | O(n) | Join a list of strings into a single string. |
find(substring) | O(n) | Find the first occurrence of a substring. Returns-1 if not found. |
rfind(substring) | O(n) | Find the last occurrence of a substring. Returns-1 if not found. |
index(substring) | O(n) | Find the first occurrence of a substring. RaisesValueError if not found. |
replace(old, new) | O(n) | Replace all occurrences of a substring with another substring. |
count(substring) | O(n) | Count the occurrences of a substring in the string. |
startswith(prefix) | O(n) | Check if the string starts with the given prefix. |
endswith(suffix) | O(n) | Check if the string ends with the given suffix. |
isalpha() | O(n) | Check if all characters in the string are alphabetic. |
isdigit() | O(n) | Check if all characters in the string are digits. |
isalnum() | O(n) | Check if all characters in the string are alphanumeric. |
isspace() | O(n) | Check if all characters in the string are whitespace. |
upper(),lower() | O(n) | Convert the string to uppercase or lowercase. |
capitalize() | O(n) | Capitalize the first character of the string. |
title() | O(n) | Convert the string to title case. |
ord(char) | O(1) | Get the ASCII value of a character. |
chr(ascii_value) | O(1) | Get the character corresponding to an ASCII value. |
'substring' in string | O(n) | Check if a substring exists in the string. |
st[::-1] | O(n) | Reverse the string (used for palindrome check). |
Counter(string) | O(n) | Count the frequency of each character in the string. |
sorted(string) | O(n log n) | Sort the characters of the string (used for anagram check). |
dict.fromkeys(string) | O(n) | Remove duplicates from the string while preserving order. |
By default, the .sort() method sorts the elements in ascending order in-place. The return value of the .sort() method is None. By default, strings are sorted inlexicographical order.
elements= [5,3,6,2,1]elements.sort()print(elements)# [1, 2, 3, 5, 6]elements= ["grape","apple","banana","orange"]elements.sort()print(elements)# ['apple', 'banana', 'grape', 'orange']
defsort(key=None,reverse=False)->None:
- The
keyparameter allows us to customize the sorting order. - The
reverseparameter is a boolean value that determines whether the list should be sorted in descending order. By default, it is set toFalse.
elements= [5,3,6,2,1]elements.sort(None,True)# elements.sort(True)print(elements)# [6, 5, 3, 2, 1]
We can also specify a custom sorting order by using the key parameter in the .sort() method. The key parameter doesn't accept a value, instead, it accepts a function that returns a value to be used for sorting.
defget_word_length(word:str)->int:returnlen(word)words= ["apple","banana","kiwi","pear","watermelon","blueberry","cherry"]words.sort(key=get_word_length)print(words)# ['kiwi', 'pear', 'apple', 'banana', 'cherry', 'blueberry', 'watermelon']# Problem link: https://leetcode.com/problems/reorder-data-in-log-files/# AC, LC:BadhansenclassSolution:defsort_by_requirements(self,log:str)->tuple:identifier,content=log.split(" ",1)ifcontent[0].isdigit():return (1,"","")else:return (0,content,identifier)defreorderLogFiles(self,logs:List[str])->List[str]:logs.sort(key=self.sort_by_requirements)returnlogs
Defining a separate function just to pass it into the key parameter of the .sort() method can be cumbersome. We can use a lambda function to define a function in a single line and pass it directly to the .sort() method.
words= ["apple","banana","kiwi","pear","watermelon","blueberry","cherry"]words.sort(key=lambdaword:len(word))print(words)# ['kiwi', 'pear', 'apple', 'banana', 'cherry', 'blueberry', 'watermelon']
The lambda function lambda word: len(word) is equivalent to the function get_word_length we defined in the previous example. It takes a word as input and returns the length of the word.
To turn aCounter object (from Python'scollections module) into a sorted list, you have a couple of options depending on how you want the sorting to work. Here are common ways to convert aCounter to a sorted list:
fromcollectionsimportCounter# Example Counter objectcounter=Counter({'apple':3,'banana':2,'orange':5})# 1. Sort by count (descending order)sorted_by_count_desc=sorted(counter.items(),key=lambdax:x[1],reverse=True)print("Sorted by count (descending):",sorted_by_count_desc)# 2. Sort by count (ascending order)sorted_by_count_asc=sorted(counter.items(),key=lambdax:x[1])print("Sorted by count (ascending):",sorted_by_count_asc)# 3. Sort by key (alphabetical order)sorted_by_key=sorted(counter.items())print("Sorted by key:",sorted_by_key)
There is another way to sort a list in Python, using the sorted() function. The sorted() function returns a new list with the elements sorted in the specified order. The original list remains unchanged.
words= ["kiwi","pear","apple","banana","cherry","blueberry"]sorted_words=sorted(words)print(sorted_words)# ['apple', 'banana', 'blueberry', 'cherry', 'kiwi', 'pear']
The sorted() function takes the list as the first argument and returns a new list with the elements sorted in ascending order by default.
You can also specify the order using the reverse parameter:
numbers= [5,-3,2,-4,6,-2,4]sorted_numbers=sorted(numbers,reverse=True)print(sorted_numbers)# [6, 5, 4, 2, -2, -3, -4]
You can also pass a custom function to the key parameter to specify the sorting criteria.
numbers= [5,-3,2,-4,6,-2,4]sorted_numbers=sorted(numbers,key=abs)print(sorted_numbers)# [2, -2, -3, 4, -4, 5, 6]
For the most part, it's similar to the sort() method, but it returns a new list instead of modifying the original list.
Sort list using first element in increasing order if the first values are equal sort by second value by descending order.
profits= [1,2,3]capital= [0,1,1]pairs= [(profits[i],capital[i])foriinrange(len(profits))]# (profit, capital)pairs.sort(key=lambdap:(p[1],-p[0]))print(pairs)# [(1, 0), (3, 1), (2, 1)]
The time complexity of the .sort() method is 𝑂(𝑛𝑙𝑜𝑔𝑛), where n is the number of elements in the list. The space complexity is 𝑂(𝑛), where n is the number of elements in the list.
Note: Python uses the Timsort algorithm for sorting lists. Timsort is a hybrid sorting algorithm derived from merge sort and insertion sort.
# Problem Link: https://leetcode.com/problems/sort-an-array/# AC, LC: BadhansenclassSolution:defmergeSort(self,nums,l,r):ifl<r:m= (l+r)//2self.mergeSort(nums,l,m)self.mergeSort(nums,m+1,r)self.merge(nums,l,m,r)defmerge(self,nums,l,m,r):i=lj=m+1res= []whilei<=mandj<=r:ifnums[i]<=nums[j]:res.append(nums[i])i+=1else:res.append(nums[j])j+=1whilei<=m:res.append(nums[i])i+=1whilej<=r:res.append(nums[j])j+=1index=0forkinrange(l,r+1):nums[k]=res[index]index+=1defsortArray(self,nums:List[int])->List[int]:self.mergeSort(nums,0,len(nums)-1)returnnums
Description:A stack is a collection of elements that follows the Last In, First Out (LIFO) principle. Thepush operation adds an element to the top of the stack, while thepop operation removes the top element.
Example:
stack= []# Push elements onto the stackstack.append(1)# O(1)stack.append(2)stack.append(3)print(stack)# Output: [1, 2, 3]# Pop elements from the stacktop_element=stack.pop()# O(1)# Check the elementvalue=stack[-1]# O(1)print(value)# Output: 3print(top_element)# Output: 3print(stack)# Output: [1, 2]
Description:A queue is a collection of elements that follows the First In, First Out (FIFO) principle. Theenqueue operation adds an element to the end of the queue, while thedequeue operation removes the element from the front of the queue.
Example:
fromcollectionsimportdequequeue=deque()# Enqueue elementsqueue.append(1)# O(1)queue.append(2)queue.append(3)print(queue)# Output: deque([1, 2, 3])# Dequeue elementsfront_element=queue.popleft()# O(1)print(front_element)# Output: 1print(queue)# Output: deque([2, 3])
Description:A deque (double-ended queue) allows you to add or remove elements from both ends (front and back). This provides more flexibility than a regular queue or stack.
Example:
fromcollectionsimportdequedeque_obj=deque()# Add elements to both endsdeque_obj.append(1)# O(1), Add to the rightdeque_obj.appendleft(2)# O(1), Add to the leftprint(deque_obj)# Output: deque([2, 1])# Remove elements from both endsright_element=deque_obj.pop()# O(1)left_element=deque_obj.popleft()# O(1)print(right_element)# Output: 1print(left_element)# Output: 2print(deque_obj)# Output: deque([])# Create a dequedq=deque([10,20,30,40,50])# Access the first elementfirst_element=dq[0]# O(1)print("First element:",first_element)# Access the last elementlast_element=dq[-1]# O(1)print("Last element:",last_element)# Insert at a specific position (e.g., index 2)dq.insert(2,25)# O(n)print("After insert:",dq)# Access an element by index (not the ends)middle_element=dq[2]# O(n)print("Element at index 2:",middle_element)# Iterate through all elementsforelemindq:# O(n)print(elem,end=" ")
Time Complexity:
append(add to the right):O(1)appendleft(add to the left):O(1)pop(remove from the right):O(1)popleft(remove from the left):O(1)
In python we can traverse stacks, queues, and deques in Python:
- Stack Traversal:
- Stacks follow the Last In, First Out (LIFO) principle.
- Traversing a stack involves iterating over the elements, typically implemented as a list.
- Time Complexity:
O(n)
- Queue Traversal:
- Queues follow the First In, First Out (FIFO) principle.
- Traversing a queue involves iterating over the elements, often implemented using
collections.deque. - Time Complexity:
O(n)
- Deque Traversal:
- Deques (double-ended queues) allow elements to be added or removed from both ends.
- Traversing a deque involves iterating over the elements, also implemented using
collections.deque. - Time Complexity:
O(n)
Description:A Priority Queue is a data structure where elements are dequeued based on priority. Python implements it using theheapq module, which provides a min-heap by default.
Example:
importheapq# Min-Heap by default# Initialize an empty list to act as a heapheap= []# Push elements onto the heapheapq.heappush(heap,10)# O(log n)heapq.heappush(heap,5)# O(log n)heapq.heappush(heap,15)# O(log n)heapq.heappush(heap,3)# O(log n)print("Heap after pushing elements:",heap)# Output: Heap after pushing elements: [3, 5, 15, 10]# Pop the smallest element from the heapsmallest=heapq.heappop(heap)# O(log n)print("Smallest element:",smallest)# Output: Smallest element: 3print("Heap after popping the smallest element:",heap)# Output: Heap after popping the smallest element: [5, 10, 15]# Convert a list into a heapnums= [5,7,9,1,3]heapq.heapify(nums)# O(n)print("Heapified list:",nums)# Output: Heapified list: [1, 3, 9, 7, 5]# Push an element into the heapheapq.heappush(nums,4)# O(log n)print("Heap after pushing 4:",nums)# Output: Heap after pushing 4: [1, 3, 4, 7, 5, 9]# Pop the smallest element from the heapvalue=heapq.heappop(nums)# O(log n)print("Popped smallest element:",value)# Output: Popped smallest element: 1print("Heap after popping the smallest element:",nums)# Output: Heap after popping the smallest element: [3, 5, 4, 7, 9]
Description:Suppose I have a pairs of integers which represent (cost, profit) of an item.By default it is min heap so it will heapify based on first value then second value then third value.
Example:
importheapq# Initialize the list of tupleselements= [(3,300), (3,140), (3,140), (3,10), (5,55), (5,50), (10,200), (10,100)]# Convert the list to a heapheapq.heapify(elements)print("Heapified list:")whileelements:val=heapq.heappop(elements)print(val)'''Heapified list:(3, 10)(3, 140)(3, 140)(3, 300)(5, 50)(5, 55)(10, 100)(10, 200)'''
Now I want to create a heap: Low cost with high profit.
Example:
importheapqelements= [(3,300), (3,140), (3,140), (3,10), (5,55), (5,50), (10,200), (10,100)]heap= []forcost,profitinelements:heapq.heappush(heap, (cost,-profit))print("Heapified list:")whileheap:cost,profit=heapq.heappop(heap)print(cost,-profit)'''Heapified list:3 3003 1403 1403 105 555 5010 20010 100'''
Now I want to create a heap: High cost with high profit.
Example
importheapqelements= [(3,300), (3,140), (3,140), (3,10), (5,55), (5,50), (10,200), (10,100)]heap= []forcost,profitinelements:heapq.heappush(heap, (-cost,-profit))print("Heapified list:")whileheap:cost,profit=heapq.heappop(heap)print(-cost,-profit)'''Heapified list:10 20010 1005 555 503 3003 1403 1403 10'''
Description:A linked list is a linear collection of elements, where each element points to the next element in the sequence. Each element, often called a "node," contains two key pieces of information:
- The data value
- A reference (or link) to the next node in the sequence
Example
classNode:def__init__(self,data):self.data=dataself.next=NoneclassLinkedList:def__init__(self):self.head=Noneself.tail=Noneself.size=0defappend(self,data):"""Add element to the end of the list with O(1) time complexity"""new_node=Node(data)ifself.headisNone:# List is emptyself.head=new_nodeself.tail=new_nodeelse:# Add to the endself.tail.next=new_nodeself.tail=new_nodeself.size+=1defappendleft(self,data):"""Add element to the beginning of the list with O(1) time complexity"""new_node=Node(data)ifself.headisNone:# List is emptyself.head=new_nodeself.tail=new_nodeelse:# Add to the beginningnew_node.next=self.headself.head=new_nodeself.size+=1defdisplay(self):"""Display all elements in the linked list"""current=self.headwhilecurrent:print(current.data,end=" -> ")current=current.nextprint("None")defget_size(self):"""Return the number of elements in the list"""returnself.size
DescriptionA doubly linked list is a type of linked data structure in which each node contains three components:
- Data: The actual value or information being stored
- Next pointer: A reference to the next node in the sequence
- Previous pointer: A reference to the previous node in the sequence
Example
classNode:def__init__(self,data):self.data=dataself.prev=Noneself.next=NoneclassDoublyLinkedList:def__init__(self):self.head=Noneself.tail=Noneself.size=0# Add a node at the end of the listdefappend(self,data):new_node=Node(data)ifself.headisNone:# If the list is emptyself.head=self.tail=new_nodeelse:self.tail.next=new_node# Link the current tail to the new nodenew_node.prev=self.tail# Link the new node back to the current tailself.tail=new_node# Update the tail referenceself.size+=1# Add a node at the beginning of the listdefappendleft(self,data):new_node=Node(data)ifself.headisNone:# If the list is emptyself.head=self.tail=new_nodeelse:new_node.next=self.head# Link the new node to the current headself.head.prev=new_node# Link the current head back to the new nodeself.head=new_node# Update the head referenceself.size+=1# Delete the first node (from the head)defdelete_from_head(self):ifself.headisNone:# If the list is emptyreturnNonecurrent_data=self.head.data# Store the value of the head# If only one node existsifself.head==self.tail:self.head=self.tail=Noneelse:self.head=self.head.next# Move the head pointer forwardself.head.prev=None# Remove reference to the deleted nodeself.size-=1returncurrent_data# Delete the last node (from the tail)defdelete_from_tail(self):ifself.tailisNone:# If the list is emptyreturnNonecurrent_data=self.tail.data# Store the value of the tail# If only one node existsifself.head==self.tail:self.head=self.tail=Noneelse:self.tail=self.tail.prev# Move the tail pointer backwardself.tail.next=None# Remove reference to the deleted nodeself.size-=1returncurrent_data# Get the current size of the listdefget_size(self):returnself.size# Check if the list is emptydefis_empty(self):returnself.headisNone# Display the list in forward orderdefdisplay(self):ifself.is_empty():print("Empty list")returncurrent=self.headwhilecurrent:print(current.data,end=" <--> ")current=current.nextprint("None")
A tree organizes values hierarchically.
Each entry in the tree is called a node, and every node links to zero or more child nodes.
If you flip the picture upside down, it kind of looks like a tree. That's where the name comes from!
- Filesystems—files inside folders inside folders
- Comments—comments, replies to comments, replies to replies
- Family trees—parents, grandparents, children, and grandchildren
Leaf nodes are nodes that're on the bottom of the tree (more formally: nodes that have no children).
Each node in a tree has a depth: the number of links from the root to the node.
A tree's height is the number of links from its root to the furthest leaf. (That's the same as the maximum node depth.)
Visit the current node, then walk the left subtree, and finally walk the right subtree.
A pre-order traversal usually visits nodes in the same order as a DFS.
Walk the left subtree first, then visit the current node, and finally walk the right subtree.
Of all three traversal methods, this one is probably the most common. When walking a binary search tree, an in order traversal visits the nodes in sorted, ascending order.
Walk the left subtree, then the right subtree, and finally visit the current node.
This one's kind of rare ... but it shows up in some parsing algorithms, like Reverse Polish Notation.
A binary tree is a tree where every node has at most two children.
A full binary tree is a binary tree where every node has exactly 0 or 2 children.
A perfect binary tree doesn't have room for any more nodes—unless we increase the tree's height.
A complete binary tree is like a perfect binary tree missing a few nodes in the last level. Nodes are filled in from left to right.
Complete trees are the basis for heaps and priority queues.
A balanced binary tree is a tree whose height is small relative to the number of nodes it has. By small, we usually mean the height is O(log n), where n is the number of nodes.
Conceptually, a balanced tree "looks full," without any missing chunks or branches that end much earlier than other branches.
There are few different definitions of balanced depending on the context. One of the most common definition is that a tree is balanced if: (a) the heights of its left and right subtrees differ by at most 1, and (b) both subtrees are also balanced.
Similar definitions can be used for trees that have more than two children. For instance, a full ternary tree (with up to three children per node) is a tree where every node has zero or three children.
Example
classTreeNode:def__init__(self,value):self.value=valueself.left=Noneself.right=NoneclassSolution:defpreorder(self,root):ifrootisNone:returnprint(root.value,end=" ")self.preorder(root.left)self.preorder(root.right)definorder(self,root):ifrootisNone:returnself.inorder(root.left)print(root.value,end=" ")self.inorder(root.right)defpostorder(self,root):ifrootisNone:returnself.postorder(root.left)self.postorder(root.right)print(root.value,end=" ")
DescriptionA graph organizes items in an interconnected network.
Each item is a node (or vertex). Nodes are connected by edges
A graph is composed of nodes (or vertices) that are connected by edges.
- Representing links. Graphs are ideal for cases where you're working with things that connect to other things. Nodes and edges could, for example, respectively represent cities and highways, routers and ethernet cables, or Facebook users and their friendships.
- Weaknesses: Scaling challenges. Most graph algorithms are
O(n log n)or even slower. Depending on the size of your graph, running algorithms across your nodes may not be feasible.
In directed graphs, edges point from the node at one end to the node at the other end. In undirected graphs, the edges simply connect the nodes at each end.
A graph is cyclic if it has a cycle—an unbroken series of nodes with no repeating nodes or edges that connects back to itself. Graphs without cycles are acyclic.
If a graph is weighted, each edge has a "weight." The weight could, for example, represent the distance between two locations, or the cost or time it takes to travel between the locations.
A graph coloring is when you assign colors to each node in a graph. A legal coloring means no adjacent nodes have the same color:
There are a few different ways to store graphs. Let's take this graph as an example:
A list of all the edges in the graph:
graph= [[0,1], [1,2], [1,3], [2,3]]
Since node 3 has edges to nodes 1 and 2, [1, 3] and [2, 3] are in the edge list.
Sometimes it's helpful to pair our edge list with a list of all the nodes. For example, what if a node doesn't have any edges connected to it? It wouldn't show up in our edge list at all!
A list where the index represents the node and the value at that index is a list of the node's neighbors:
graph= [ [1], [0,2,3], [1,3], [1,2],]
Since node 3 has edges to nodes 1 and 2, graph[3] has the adjacency list [1, 2].
We could also use a dictionary where the keys represent the node and the values are the lists of neighbors.
graph= {0: [1],1: [0,2,3],2: [1,3],3: [1,2],}
This would be useful if the nodes were represented by strings, objects, or otherwise didn't map cleanly to list indices.
A matrix of 0s and 1s indicating whether node x connects to node y (0 means no, 1 means yes).
graph= [ [0,1,0,0], [1,0,1,1], [0,1,0,1], [0,1,1,0],]
Since node 3 has edges to nodes 1 and 2, graph[3][1] and graph[3][2] have value 1.
In a BFS, you first explore all the nodes one step away, then all the nodes two steps away, etc..
Breadth-first search is like throwing a stone in the center of a pond. The nodes you explore "ripple out" from the starting point.
Here's a sample tree, with the nodes labeled in the order they'd be visited in a BFS.
In a DFS, you go as deep as possible down one path before backing up and trying a different one.
Depth-first search is like walking through a corn maze. You explore one path, hit a dead end, and go back and try a different one.
Here's a how a DFS would traverse the same example tree:
- A BFS will find the shortest path between the starting point and any other reachable node. A depth-first search will not necessarily find the shortest path.
- Depth-first search on a binary tree generally requires less memory than breadth-first.
- Depth-first search can be easily implemented with recursion.
You can also use BFS and DFS on graphs.
Example
# Edge list representationedges= [ (0,1), (1,2), (1,3), (2,3)]# Example: Iterate through edgesforedgeinedges:print(f"Edge between{edge[0]} and{edge[1]}")# Adjacency list representationgraph= {0: [1],1: [0,2,3],2: [1,3],3: [1,2]}# Example: Iterate through neighbors of each nodefornode,neighborsingraph.items():print(f"Node{node} has neighbors{neighbors}")# Adjacency matrix representationgraph= [ [0,1,0,0],# Node 0 is connected to Node 1 [1,0,1,1],# Node 1 is connected to Nodes 0, 2, and 3 [0,1,0,1],# Node 2 is connected to Nodes 1 and 3 [0,1,1,0]# Node 3 is connected to Nodes 1 and 2]# Example: Print connectionsforiinrange(len(graph)):forjinrange(len(graph[i])):ifgraph[i][j]==1:print(f"Node{i} is connected to Node{j}")# Weighted graph using adjacency listgraph= {0: [(1,4)],# Node 0 is connected to Node 1 with weight 41: [(0,4), (2,3)],# Node 1 is connected to Nodes 0 and 2 with weights 4 and 32: [(1,3), (3,2)],# Node 2 is connected to Nodes 1 and 3 with weights 3 and 23: [(2,2)]# Node 3 is connected to Node 2 with weight 2}# Example: Print weighted edgesfornode,neighborsingraph.items():forneighbor,weightinneighbors:print(f"Edge from{node} to{neighbor} with weight{weight}")# Weighted graph using edge listedges= [ (0,1,4),# Edge from Node 0 to Node 1 with weight 4 (1,2,3),# Edge from Node 1 to Node 2 with weight 3 (2,3,2)# Edge from Node 2 to Node 3 with weight 2]# Example: Print weighted edgesforu,v,weightinedges:print(f"Edge from{u} to{v} with weight{weight}")# bfs implementation# Problem link: https://leetcode.com/problems/course-schedule-ii/# @Author: Badhan SenclassSolution:deffindOrder(self,numCourses:int,prerequisites:List[List[int]])->List[int]:graph=defaultdict(list)indegree= [0]*numCoursesforu,vinprerequisites:graph[v].append(u)indegree[u]+=1sources=deque([iforiinrange(numCourses)ifindegree[i]==0])res= []whilesources:src=sources.popleft()res.append(src)fordestingraph[src]:indegree[dest]-=1ifindegree[dest]==0:sources.append(dest)returnresiflen(res)==numCourseselse []"""Time and space complexity: O(v + e)"""classSolution:deffindOrder(self,numCourses:int,prerequisites:List[List[int]])->List[int]:graph=defaultdict(list)indegree= [0]*numCoursesforu,vinprerequisites:graph[v].append(u)indegree[u]+=1sources=deque([iforiinrange(numCourses)ifindegree[i]==0])res= []whilesources:size=len(sources)foriinrange(size):src=sources.popleft()res.append(src)fordestingraph[src]:indegree[dest]-=1ifindegree[dest]==0:sources.append(dest)returnresiflen(res)==numCourseselse []# dfs implementation using colors or numberclassSolution:deffindOrder(self,numCourses:int,prerequisites:List[List[int]])->List[int]:graph=defaultdict(set)foru,vinprerequisites:graph[v].add(u)res= []visited=defaultdict(lambda:0)defdfs(src):# Return false if the node is visited and viewed again before completionifvisited[src]==1:returnFalse# Return true if the node is completed processingifvisited[src]==2:returnTruevisited[src]=1fordestingraph[src]:ifnotdfs(dest):returnFalsevisited[src]=2# Mark as processedres.append(src)returnTrueforcinrange(numCourses):dfs(c)returnres[::-1]iflen(res)==numCourseselse []"""Time and space complexity: O(v + e)"""
1. Subset - I
Problem link: https://leetcode.com/problems/subsets/
Given an integer array nums of unique elements, return all possible subsets (the power set).
The solution set must not contain duplicate subsets. Return the solution in any order.
Example 1:Input: nums = [1,2,3]Output: [[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]Example 2:Input: nums = [0]Output: [[],[0]]Constraints:
1 <= nums.length <= 10-10 <= nums[i] <= 10- All the numbers of nums are unique.
Approach-1: Backtracking [Python Code]
classSolution:defbacktrack(self,start,nums,subset,res):res.append(subset[:])# res.append(subset.copy())foriinrange(start,len(nums)):subset.append(nums[i])self.backtrack(i+1,nums,subset,res)subset.pop()defsubsets(self,nums:List[int])->List[List[int]]:res= []self.backtrack(0,nums, [],res)returnres
Approach-2 : Bit Manipulation[Python Code]
classSolution:defsubsets(self,nums:List[int])->List[List[int]]:n=len(nums)total=1<<nresults= []foriinrange(total):res= []forjinrange(n):if (i&1<<j):res.append(nums[j])results.append(res)returnresults
Approach-3 : Pick & Not Pick [Python Code]
classSolution:defsolve(self,nums,i,ans,sub):ifi==len(nums):ans.append(sub[:])# ans.append(sub.copy()), Append a copy of the current subsetreturn# Pick the current elementsub.append(nums[i])self.solve(nums,i+1,ans,sub)sub.pop()# Backtrack# Don't pick the current elementself.solve(nums,i+1,ans,sub)defsubsets(self,nums):ans= []sub= []self.solve(nums,0,ans,sub)returnans
2. Subset - IIProblem link : https://leetcode.com/problems/subsets-ii/
Given an integer array nums that may contain duplicates, return all possible subsets (the power set).
The solution set must not contain duplicate subsets. Return the solution in any order.
Example 1:Input: nums = [1,2,2]Output: [[],[1],[1,2],[1,2,2],[2],[2,2]]Example 2:Input: nums = [0]Output: [[],[0]]Constraints:
1 <= nums.length <= 10-10 <= nums[i] <= 10
Approach - 1: Brute Force (Backtracking) [Python Code]
classSolution:defsubsetsWithDup(self,nums:List[int])->List[List[int]]:nums.sort()res=set()defdfs(i,subset):ifi>=len(nums):res.add(tuple(subset))returndfs(i+1,subset+ [nums[i]])dfs(i+1,subset)dfs(0, [])returnlist(res)# Time: O(N * 2^N)# Space: O(N)
Approach - 2: Optimized (Backtracking) [Python Code]
classSolution:defsubsetsWithDup(self,nums:List[int])->List[List[int]]:nums.sort()res= []defdfs(i,subset):ifi>=len(nums):res.append(subset)returndfs(i+1,subset+ [nums[i]])whilei+1<len(nums)andnums[i]==nums[i+1]:i+=1dfs(i+1,subset)dfs(0, [])returnres# Time: O(N * 2^N)# Space: O(N)
3. Permutation - I
problem link: https://leetcode.com/problems/permutations/
Given an array nums of distinct integers, return all the possible permutations. You can return the answer in any order.
Example 1:
Input: nums = [1,2,3]Output: [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]Example 2:
Input: nums = [0,1]Output: [[0,1],[1,0]]Example 3:
Input: nums = [1]Output: [[1]]Constraints:
1 <= nums.length <= 6-10 <= nums[i] <= 10All the integers of nums are unique.
Approach - 2: Optimized (Backtracking) [Python Code]
classSolution:defpermute(self,nums:List[int])->List[List[int]]:nums.sort()res= []defdfs(pos):ifpos==len(nums):# res.append(nums.copy())res.append(nums[:])returnforiinrange(pos,len(nums)):nums[i],nums[pos]=nums[pos],nums[i]dfs(pos+1)nums[i],nums[pos]=nums[pos],nums[i]dfs(0)returnres# Time Complexity: O(n! * n)# Space Complexity: O(n! * n) (for storing results) + O(n) (recursion stack)
About
Resources
License
Uh oh!
There was an error while loading.Please reload this page.