diff --git a/04_LinkedList/Check if a LinkedList is palindrome or not..py b/04_LinkedList/Check if a LinkedList is palindrome or not..py new file mode 100644 index 00000000..2e4eebdd --- /dev/null +++ b/04_LinkedList/Check if a LinkedList is palindrome or not..py @@ -0,0 +1,43 @@ +# https://leetcode.com/problems/palindrome-linked-list/ + +''' +Go to the middle node and reverse the right-side linkedlist. +Then take 2 pointers one from start and another from middle and check +equality of value. +''' + +class Solution: + def isPalindrome(self, head: Optional[ListNode]) -> bool: + if not head or not head.next: return True + + # Find Middle + slow = head + fast = head.next + while fast and fast.next: + slow = slow.next + fast = fast.next.next + # slow is just the previous node of middle as we need to reverse the Linkedlist also + + # Reverse LinkedList right-side of middle + pre = slow + cur = pre.next + nex = cur.next + + while nex: + cur.next = nex.next + nex.next = pre.next + pre.next = nex + nex = cur.next + + # start checking equality of values + left = head + right = slow.next + while left and right: + if left.val != right.val: return False + left = left.next + right = right.next + + return True + +# Time: O(N) +# Space: O(1) \ No newline at end of file diff --git a/04_LinkedList/Find intersection point of 2 LinkedList.py b/04_LinkedList/Find intersection point of 2 LinkedList.py new file mode 100644 index 00000000..4b60d6d1 --- /dev/null +++ b/04_LinkedList/Find intersection point of 2 LinkedList.py @@ -0,0 +1,21 @@ +# https://leetcode.com/problems/intersection-of-two-linked-lists/ + +class Solution: + def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]: + a = headA + b = headB + loopCount = 0 # if no intersection + + while True: + if a == b: return a + + a = a.next + b = b.next + + if not a: a = headB; loopCount += 1 + if not b: b = headA + + if loopCount > 1: return None + +# Time: O(n + m) +# Space: O(1) \ No newline at end of file diff --git a/04_LinkedList/Flattening of a LinkedList.py b/04_LinkedList/Flattening of a LinkedList.py new file mode 100644 index 00000000..1e77fec1 --- /dev/null +++ b/04_LinkedList/Flattening of a LinkedList.py @@ -0,0 +1,63 @@ +# https://practice.geeksforgeeks.org/problems/flattening-a-linked-list/1 + +''' +Select 2 bottom directed linklist at a time and use the concept of "21. Merge Two Sorted Lists" +on them. Start traversing from the begining. Assign 'a' linkedList wirh smaller head and +'b' to the larger head. Change main head to a. At the end we will have the sorted Linkedlist with only bottom pointer. + +Input: +a b c => take 'a' at smaller head +5 -> 10 -> 19 -> 28 +| | | | +7 20 22 35 +| | | +8 50 40 +| | +30 45 + +Output: 5-> 7-> 8- > 10 -> 19-> 20-> 22-> 28-> 30-> 35-> 40-> 45-> 50 bottom pointer + +''' + + +''' +class Node: + def __init__(self, d): + self.data=d + self.next=None + self.bottom=None +''' + +def flatten(head): + while head: + if not head.next: return head + + if head.data <= head.next.data: + a = head + b = head.next + c = b.next + else: + a = head.next + b = head + c = a.next + head = head.next + + while a: + if b and a.bottom and a.bottom.data > b.data: + tmp = a.bottom + a.bottom = b + b = tmp + elif not a.bottom: + a.bottom = b + b = None + elif not a.bottom and not b: + a.bottom = c + break + a = a.bottom + head.next = c + + return head + + +# Time Complexity: O(N*N*M) +# Auxiliary Space: O(1) \ No newline at end of file diff --git "a/10_Trie/02. Implement Trie \342\200\223 2 (Prefix Tree).py" "b/10_Trie/02. Implement Trie \342\200\223 2 (Prefix Tree).py" new file mode 100644 index 00000000..60bcb1d5 --- /dev/null +++ "b/10_Trie/02. Implement Trie \342\200\223 2 (Prefix Tree).py" @@ -0,0 +1,63 @@ +# https://www.codingninjas.com/codestudio/problems/implement-trie_1387095 + +# https://youtu.be/ict1UawpXMM?t=359 + +class TrieNode: + def __init__(self): + self.children = {} + self.endOfWordCount = 0 + self.prefixOfWordCount = 0 + +class Trie: + def __init__(self): + self.root = TrieNode() + + def insert(self, word): + cur = self.root + for c in word: + if c not in cur.children: + cur.children[c] = TrieNode() + cur = cur.children[c] + cur.prefixOfWordCount += 1 + cur.endOfWordCount += 1 + + def countWordsEqualTo(self, word): + cur = self.root + for c in word: + if c not in cur.children: + return 0 + cur = cur.children[c] + return cur.endOfWordCount + + def countWordsStartingWith(self, word): + cur = self.root + for c in word: + if c not in cur.children: + return 0 + cur = cur.children[c] + return cur.prefixOfWordCount + + def erase(self, word): # Dele te function + cur = self.root + toBeDeleted = None + for c in word: # as it a delete function so word is present in trie so we don't need to check if key 'c' present in children hashmap or not. + cur = cur.children[c] + cur.prefixOfWordCount -= 1 + if toBeDeleted: + toBeDeleted = None + if cur.prefixOfWordCount == 0: + toBeDeleted = cur + + if toBeDeleted: + toBeDeleted = None + cur.endOfWordCount -= 1 + + + # It will also work if we don't delete the node only decrease the counts + def erase(self, word): # Dele te function + cur = self.root + for c in word: # as it a delete function so word is present in trie so we don't need to check if key 'c' present in children hashmap or not. + cur = cur.children[c] + cur.prefixOfWordCount -= 1 + + cur.endOfWordCount -= 1 \ No newline at end of file diff --git a/10_Trie/03. Maximum XOR of Two Numbers in an Array.py b/10_Trie/03. Maximum XOR of Two Numbers in an Array.py new file mode 100644 index 00000000..653cd780 --- /dev/null +++ b/10_Trie/03. Maximum XOR of Two Numbers in an Array.py @@ -0,0 +1,62 @@ +# https://leetcode.com/problems/maximum-xor-of-two-numbers-in-an-array/ + +''' +Insert all the elements of nums in a trie with bit values in respective position. +where every node can have 2 children either with 0 key or 1 key. + +As XOR is a inequality detector so we try to maximize the inequality between num and node. +So that the XOR of num and value of node will give the max value. + +So we do the following steps While traversing the num from 31'th bit position to 0'th bit position: + +If the current bit of num is 1 then we try to move the cur pointer towards the child with 0 key. +And if the current bit of num is 0 then we try to move the cur pointer towards the child with 1 key. +''' + +class TrieNode: + def __init__(self): + self.children = {} + self.val = -1 # used to store the value of entire number at the end of trie node + +class Trie: + def __init__(self): + self.root = TrieNode() # object of trienode class + + def addNum(self, num): + cur = self.root # every time start from root + for i in range(31, -1, -1): + bit = 1 if num & (1 << i) else 0 # bit value i'th position of num + if bit not in cur.children: + cur.children[bit] = TrieNode() + cur = cur.children[bit] + cur.val = num # storing the value of entire num at the end of trie node + + +class Solution: + def findMaximumXOR(self, nums: List[int]) -> int: + trie = Trie() # creating object of Trie class + for num in nums: # adding all num to the trie structure + trie.addNum(num) + + res = 0 + for num in nums: + cur = trie.root # every time start cur pointer from root + for i in range(31, -1, -1): + bit = 1 if num & (1 << i) else 0 # bit value of i'th position of num + if bit == 1: # try to move towards opposite key ie. 0 + if 0 in cur.children: # opposit key 0 exist then defenetly go towards the child 0 + cur = cur.children[0] + else: # opposit key 0 not exist so we have only option to go towards what we have ie. 1 + cur = cur.children[1] + else: # bit == 0 # try to move towards opposite key ie. 1 + if 1 in cur.children: # opposit key 1 exist then defenetly go towards the child 1 + cur = cur.children[1] + else: # opposit key 1 not exist so we have only option to go towards what we have ie. 0 + cur = cur.children[0] + # as we tried to maximize the inequality between cur.val and num so XOR of them will give max value + res = max(res, cur.val ^ num) + + return res + + + diff --git a/10_Trie/04. Number of Distinct Substrings in a String.py b/10_Trie/04. Number of Distinct Substrings in a String.py new file mode 100644 index 00000000..72696f85 --- /dev/null +++ b/10_Trie/04. Number of Distinct Substrings in a String.py @@ -0,0 +1,29 @@ +# https://www.codingninjas.com/codestudio/problems/count-distinct-substrings_985292 +# https://youtu.be/RV0QeTyHZxo +class TrieNode: + def __init__(self): + self.children = {} + +class Trie: + def __init__(self): + self.root = TrieNode() + def solve(self, s): + res = 0 + for i in range(len(s)): + cur = self.root + for j in range(i, len(s)): + if s[j] not in cur.children: + cur.children[s[j]] = TrieNode() + res += 1 + cur = cur.children[s[j]] + return res + 1 # +1 for empty substring "" + +def countDistinctSubstrings(s): + trie = Trie() + return trie.solve(s) + + +# Time: O(N^2) +# Space: It is hard to predict spcace taken tries. It depends on the distinct elements of s. But as we are using only necessary keys in trie hashmap not all 26 keys so at max space can be N^2 +# Space: in worst case O(N^2) + \ No newline at end of file diff --git a/11_Binary-Search/Aggressive Cows - Advanced Binary Search.py b/11_Binary-Search/Aggressive Cows - Advanced Binary Search.py new file mode 100644 index 00000000..daf13b9e --- /dev/null +++ b/11_Binary-Search/Aggressive Cows - Advanced Binary Search.py @@ -0,0 +1,37 @@ +# https://www.codingninjas.com/codestudio/problems/aggressive-cows_1082559?leftPanelTab=1 +# https://youtu.be/YTTdLgyqOLY?t=2626 +''' +Same binary search approach like 'Minimum Page Allocation Problem'. +In the isValid function we need to check if with the current distance 'mid' +we can place all the cows in the stalls or not. If we can place all the cows then +try to increase the mid(ie. increase the low). If we can not place all cows then +that distsnce(ie. mid) we need to decrease the distance etween cows ie. dicrease the +mid. +''' +def aggressiveCows(stalls, k): + def isValid(mid): + count = 1 # count of cows + lastPosition = stalls[0] # position where we recently placed the cow + for i in range(1, len(stalls)): + if stalls[i] - lastPosition >= mid: # checking if we can place cow in this i'th position + count += 1 + if count == k: return True + lastPosition = stalls[i] + return False + + stalls.sort() # as in isValid() function we need the distance in increasing order + ans = 0 + low = 0 + high = max(stalls) + while low <= high: + mid = low + (high - low) // 2 + if isValid(mid): # check if we can increase the distance or not + ans = max(ans, mid) + low = mid + 1 + else: + high = mid - 1 + + return ans + +# Time: n * log(max(stalls)) +# Space: O(1) \ No newline at end of file diff --git a/11_Binary-Search/Allocate Minimum Number Of Pages.py b/11_Binary-Search/Allocate Minimum Number Of Pages.py new file mode 100644 index 00000000..aabe59df --- /dev/null +++ b/11_Binary-Search/Allocate Minimum Number Of Pages.py @@ -0,0 +1,77 @@ +# https://practice.geeksforgeeks.org/problems/allocate-minimum-number-of-pages0937/1 +# https://www.youtube.com/watch?v=2JSQIhPcHQg + +''' +A = [12,34,67,90]; M = 2 + +I will implement Binary Search with left pointer l and right pointer r. +As I know max(A) must be given to a student so l = max(A); you can start from l = 0 also +l = max(A) = 90 +r = sum(A) = 203 + +90----------------------------------------------------------------146--------------------------------------------203 +l mid r +mid = 146 isValid so ans = mid = 146; I will try to decrease mid => r = mid - 1 = 145 + +90-------------------------------------117------------------------145 +l mid r +mid = 117 isValid so ans = mid = 117; I will try to decrease mid => r = mid - 1 = 116 + +90--------------103--------------------116 +l mid r +mid = 103 NOT isValid so I will try to increase mid => l = mid + 1 = 104 + + 104-------110----------116 + l mid r +mid = 110 NOT isValid so I will try to increase mid => l = mid + 1 = 111 + + 111----113---116 + l mid r +mid = 113 isValid so ans = mid = 113; I will try to decrease mid => r = mid - 1 = 112 + + 111----112 + l=mid r +mid = 111 NOT isValid so I will try to increase mid => l = mid + 1 = 112 + + 112 + l=mid=r +mid = 112 NOT isValid so I will try to increase mid => l = mid + 1 = 113 +Now l = 113; r = 112 => l > r => Break Loop + +''' + +class Solution: + def findPages(self,A, N, M): + l = max(A); r = sum(A); ans = -1 + + if len(A) < M: return -1 # Number of books can not be lesser than number of students as we have to give atleast 1 book to a student + + def isValid(A, M, mid): + pageSum = 0 # sum of pages of A that can be allocated to one student + requiredStudents = 1 # Number of students required if mid is the max capacity of student + + for pages in A: + pageSum += pages + if pageSum > mid: # sum of pages allocated to one student exceed max capacity of the student + requiredStudents += 1 # We need one more student + pageSum = pages # start calculating sum of pages that can be allocated to next student + + if requiredStudents > M: return False + else: return True + + + while l <= r: + mid = l + (r - l) // 2 + + if isValid(A, M, mid): + ans = mid # Updating answer to current mid as current mid is the most optimized(least) ans till now + r = mid - 1 # I will try to decrease mid + else: + l = mid + 1 # current mid NOT isValid so I will try to increase mid + + + return ans # Most Optimized ans is stored here + + +# Time: O(log(n)) +# Space: O(1) \ No newline at end of file diff --git a/11_Binary-Search/EKO - Max Height of cut to get atleast M amount of wood.py b/11_Binary-Search/EKO - Max Height of cut to get atleast M amount of wood.py new file mode 100644 index 00000000..7c92779e --- /dev/null +++ b/11_Binary-Search/EKO - Max Height of cut to get atleast M amount of wood.py @@ -0,0 +1,53 @@ +# https://www.spoj.com/problems/EKO/ + +n = int(str[0]) +M = int(str[1]) + +arr = map(int, input().split()) + +def isValid(arr, M, mid): + amount = 0 + for height in arr: + if height > mid: + amount += height - mid + return amount >= M + +low = 0 +high = max(arr) + +while low <= high: + mid = low + (high - low) // 2 + if isValid(arr, M, mid): + low = mid + 1 + else: + high = mid - 1 + +print(low) + + +# Time: O(n * log(max(arr))) +# Space: O(1) + + + + +def chopped_off(heights, t): + return sum(max(i-t, 0) for i in heights) + + +def search_optimal_height(heights, threshold): + lo = min(heights) - threshold + hi = max(heights) + while lo <= hi: + mid = (hi + lo)//2 + if chopped_off(heights, mid) >= threshold: + lo = mid+1 + else: + hi = mid - 1 + return hi + + +n, m = map(int, raw_input().split()) +heights = [int(i) for i in raw_input().split()] +ans = search_optimal_height(heights, m) +print(ans) \ No newline at end of file diff --git a/11_Binary-Search/K-th element of two sorted Arrays.py b/11_Binary-Search/K-th element of two sorted Arrays.py new file mode 100644 index 00000000..6402d223 --- /dev/null +++ b/11_Binary-Search/K-th element of two sorted Arrays.py @@ -0,0 +1,35 @@ +# https://practice.geeksforgeeks.org/problems/k-th-element-of-two-sorted-array1317/1 +# https://youtu.be/nv7F4PiLUzo + +class Solution: + def kthElement(self, nums1, nums2, n, m, k): + n1 = len(nums1) + n2 = len(nums2) + if n1 > n2: return self.kthElement(nums2, nums1, n, m, k) + + low = max(0, k-n1); high = min(k, n1) + INT_MIN = -2**31; INT_MAX = 2**31 + + while low <= high: + cut1 = (low + high) // 2 + cut2 = k - cut1 + + left1 = nums1[cut1-1] if cut1 > 0 else INT_MIN + right1 = nums1[cut1] if cut1 < n1 else INT_MAX + + left2 = nums2[cut2-1] if cut2 > 0 else INT_MIN + right2 = nums2[cut2] if cut2 < n2 else INT_MAX + + if left1 <= right2 and left2 <= right1: + return max(left1, left2) + + elif left1 > right2: + high = cut1 - 1 + else: + low = cut1 + 1 + + return 1 + + +# Time: O(log(min(n, m))) +# Space: O(1) \ No newline at end of file diff --git a/11_Binary-Search/Median of Row Wise Sorted Matrix.py b/11_Binary-Search/Median of Row Wise Sorted Matrix.py new file mode 100644 index 00000000..aa2b0608 --- /dev/null +++ b/11_Binary-Search/Median of Row Wise Sorted Matrix.py @@ -0,0 +1,50 @@ +# https://www.interviewbit.com/problems/matrix-median/ +# https://youtu.be/ZN1Ra1pG45E +# https://youtu.be/63fPPOdIr2c + +''' +assigning low = min of matrix and high = max of matrix +we run binary search and try to fing if mid is at middle index of n*m//2 or not. +as rows are sorted so we can use binary search to find right most occurance of the mid to +find the left side elements count. +''' + +class Solution: + def findMedian(self, matrix): + + def countSmallerThanEqualToMid(row, target): # like last occurrence index of an element in sorted array + l = 0; r = len(row)-1 + while l <= r: + mid = l + (r - l) // 2 + if row[mid] <= target: + l = mid + 1 + else: + r = mid - 1 + return l + + low = 2**31; high = -2**31 + for row in matrix: + low = min(low, row[0]) + high = max(high, row[-1]) + + # if we make an sorted array of all elements of matrix then position of median will be middle index of (n*m)//2 + medianPosition = (len(matrix) * len(matrix[0])) // 2 + + while low <= high: + mid = low + (high - low) // 2 + leftCount = 0 + for row in matrix: + leftCount += countSmallerThanEqualToMid(row, mid) + if leftCount <= medianPosition: + low = mid + 1 + else: + high = mid - 1 + + return low + + +# (high can be maximum of 2**32 and inside that binary search we are counting for each row and inside each row we are using binary search again) +# if n = len(matrix) and m = len(matrix[0]) + +# Time: log(2^32) * n * log(m) = 32 * n * log(m) +# space: O(1) \ No newline at end of file diff --git a/11_Binary-Search/Median of Two Sorted Arrays.py b/11_Binary-Search/Median of Two Sorted Arrays.py new file mode 100644 index 00000000..d768a2e0 --- /dev/null +++ b/11_Binary-Search/Median of Two Sorted Arrays.py @@ -0,0 +1,56 @@ +# https://leetcode.com/problems/median-of-two-sorted-arrays/ + +# https://www.youtube.com/watch?v=NTop3VTjmxk +class Solution: + def findMedianSortedArrays(self, nums1, nums2): + n1 = len(nums1); n2 = len(nums2) + if n1 > n2: return self.findMedianSortedArrays(nums2, nums1) # WE SHALL DO BINARY SEARCH ON THE SMALLER ARRAY, NUMS1 + + INT_MIN, INT_MAX = -2**64, 2**64 # SETUP INT_MIN AND INT_MAX FOR EMPTY LEFT / RIGHT PARTITION + low = 0; high = n1 # pointers for BINARY SEARCH ON THE SMALLER ARRAY NUMS1 + + while low <= high: + + # GET THE PARITIONS POINTS OF BOTH ARRAYS + cut1 = (low + high) // 2 # partition of nums1 + cut2 = (n1 + n2 + 1) // 2 - cut1 # partition of nums2 + + # GET THE 4 BOUNDARY NUMBERS + left1 = nums1[cut1-1] if cut1 > 0 else INT_MIN # left1 is the left partition of cut1 + right1 = nums1[cut1] if cut1 < n1 else INT_MAX # right1 is the right partition of cut1 + + left2 = nums2[cut2-1] if cut2 > 0 else INT_MIN # left2 is the left partition of cut2 + right2 = nums2[cut2] if cut2 < n2 else INT_MAX # right2 is the right partition of cut2 + + # CORRECT PARTITION FOUND + if left1 <= right2 and left2 <= right1: # Got the Answer => Median + if (n1 + n2) % 2 == 0: + return (max(left1, left2) + min(right1, right2)) / 2 + else: + return max(left1, left2) + + # MOVE cut1 (mid of binary search) LEFTWARDS + elif left1 > right2: + high = cut1 - 1 + + # MOVE cut1 (mid of binary search) RIGHTWARDS + else: + low = cut1 + 1 + + return 0.0 # For both empty arrays + +''' +Time Complexity: O(log(n1)) +Space Complexity: O(1) +''' + + + +''' +Input: +[7,12,14,15] +[1,2,3,4,9,11] + +Output: +8.00000 +''' diff --git a/11_Binary-Search/Painters Partition Problem.py b/11_Binary-Search/Painters Partition Problem.py new file mode 100644 index 00000000..290aba54 --- /dev/null +++ b/11_Binary-Search/Painters Partition Problem.py @@ -0,0 +1,31 @@ +# https://practice.geeksforgeeks.org/problems/the-painters-partition-problem1535/1/# + +# SAME AS MINIMUM NUMBER OF PAGE ALLOCATION PROBLEM using Binary Search + +class Solution: + def minTime (self, arr, n, k): + + def isValid(mid): + count = 1 + s = 0 + for i in arr: + s += i + if s > mid: + count += 1 + s = i + return count <= k + + low = min(arr) + high = sum(arr) + while low <= high: + mid = low + (high - low) // 2 + if isValid(mid): + high = mid - 1 + else: + low = mid + 1 + + return low + +# the sum(arr) can at max 2^32 so max high = 2^32 +# Time: O(log(2^32) * n) = 32 * n +# Space: O(1) diff --git a/11_Binary-Search/Search in Rotated Sorted Array.py b/11_Binary-Search/Search in Rotated Sorted Array.py new file mode 100644 index 00000000..17a31d4e --- /dev/null +++ b/11_Binary-Search/Search in Rotated Sorted Array.py @@ -0,0 +1,30 @@ +# https://leetcode.com/problems/search-in-rotated-sorted-array/ +''' +Even the array is rotated sorted if we can use binary search in it, as one sided of the mid will be +sorted and other sider unsorted. if target not present in sorted side then it must be in the unsorted +part. change l and r pointer likewise. +''' +class Solution: + def search(self, nums: List[int], target: int) -> int: + l = 0; r = len(nums)-1 + + while l <= r: + mid = (l+r) // 2 + + if nums[mid] == target: return mid + + elif nums[l] <= nums[mid]: # Left subarray of mid is sorted + if nums[l] <= target < nums[mid]: # target present at Left subarray of mid + r = mid-1 + else: # target at right subarray of mid + l = mid+1 + else: # right subarray of mid is sorted + if nums[mid] < target <= nums[r]: # target is present at right subarray of mid + l = mid+1 + else: # target at left subarray of mid + r = mid - 1 + + return -1 + +# Time: O(log(n)) +# Space: O(1) \ No newline at end of file diff --git a/11_Binary-Search/Single Element in a Sorted Array.py b/11_Binary-Search/Single Element in a Sorted Array.py new file mode 100644 index 00000000..52c6dec2 --- /dev/null +++ b/11_Binary-Search/Single Element in a Sorted Array.py @@ -0,0 +1,38 @@ +# https://leetcode.com/problems/single-element-in-a-sorted-array/ +''' +If every element in the sorted array were to appear exactly twice, they would occur in pairs at indices i, i+1 for all even i. + +Equivalently, nums[i] = nums[i+1] and nums[i+1] != nums[i+2] for all even i. + +When we insert the unique element into this list, the indices of all the pairs following it will be shifted by one, negating the above relationship. +''' + +class Solution: + def singleNonDuplicate(self, nums: List[int]) -> int: + if len(nums) == 1: return nums[0] + r = len(nums) + l = 0 + + while l <= r: + m = (l + r) // 2 + if m == 0 or m == len(nums)-1: return nums[m] + + if m % 2 != 0: + if nums[m-1] != nums[m] != nums[m+1]: + return nums[m] + elif nums[m-1] != nums[m]: + r = m - 1 + else: + l = m + 1 + else: + if nums[m-1] != nums[m] != nums[m+1]: + return nums[m] + elif nums[m] != nums[m+1]: + r = m - 1 + else: + l = m + 1 + + return nums[m] + +# Time: O(log(n)) +# Space: O(n) diff --git a/11_Binary-Search/The N-th root of any number.py b/11_Binary-Search/The N-th root of any number.py new file mode 100644 index 00000000..4ba02a47 --- /dev/null +++ b/11_Binary-Search/The N-th root of any number.py @@ -0,0 +1,22 @@ +# https://practice.geeksforgeeks.org/problems/find-nth-root-of-m5843/1/ + +# https://youtu.be/WjpswYrS2nY + +class Solution: + def NthRoot(self, n, num): + r = num + l = 1 + + while l <= r: + mid = (l + r) // 2 + if mid ** n < num: + l = mid + 1 + elif mid ** n > num: + r = mid - 1 + else: + return mid + + return -1 + +# Time: O(log(num)) +# Space: O(1) \ No newline at end of file diff --git a/12_Backtracking/Rat in a Maze Problem - I.py b/12_Backtracking/Rat in a Maze Problem - I.py new file mode 100644 index 00000000..7a0a055f --- /dev/null +++ b/12_Backtracking/Rat in a Maze Problem - I.py @@ -0,0 +1,41 @@ +# https://practice.geeksforgeeks.org/problems/rat-in-a-maze-problem/1# +''' +We keep a track of elements and check for all 4 directions +''' +class Solution: + def findPath(self, m, n): + if m[0][0] == 0 or m[-1][-1] == 0: return [] + res = [] + vis = [[False]*n for i in range(n)] # to keep track of visited cells + + def solve(r, c, path): + if r == n-1 and c == n-1 and m[r][c] == 1: + res.append(path) + return + # Up + if r-1 >= 0 and m[r-1][c] == 1 and vis[r-1][c] == False: + vis[r][c] = True + solve(r-1, c, path + 'U') + vis[r][c] = False + # Down + if r+1 < n and m[r+1][c] == 1 and vis[r+1][c] == False: + vis[r][c] = True + solve(r+1, c, path + 'D') + vis[r][c] = False + # Left + if c-1 >= 0 and m[r][c-1] == 1 and vis[r][c-1] == False: + vis[r][c] = True + solve(r, c-1, path + 'L') + vis[r][c] = False + # Right + if c+1 < n and m[r][c+1] == 1 and vis[r][c+1] == False: + vis[r][c] = True + solve(r, c+1, path + 'R') + vis[r][c] = False + + solve(0, 0, '') + return sorted(res) + +# Time: O(N*N) +# Space: O(N*N) + diff --git a/12_Backtracking/Word Break.py b/12_Backtracking/Word Break.py new file mode 100644 index 00000000..7449ff1d --- /dev/null +++ b/12_Backtracking/Word Break.py @@ -0,0 +1,42 @@ +# https://leetcode.com/problems/word-break/ +# https://youtu.be/Sx9NNgInc3A + +# Recursion +class Solution: + def wordBreak(self, s: str, wordDict: List[str]) -> bool: + self.res = False + def solve(s): + if not s: + self.res = True + return + for i in range(1, len(s)+1): + if s[:i] in wordDict: + solve(s[i:]) + + solve(s) + return self.res +# Time: O(N^2) +# Space: O(1) + + + + + +# Dynamic Programming +class Solution: + def wordBreak(self, s: str, wordDict: List[str]) -> bool: + n = len(s) + dp = [False]*(n+1) + dp[n] = True + + for i in range(n-1, -1, -1): + for word in wordDict: + if i + len(word) <= n and s[i:i+len(word)] == word: + dp[i] = dp[i+len(word)] + if dp[i] == True: + break + + return dp[0] + +# Time: O(N^2) +# Space: O(N) diff --git a/15_Greedy/Fractional Knapsack.py b/15_Greedy/Fractional Knapsack.py new file mode 100644 index 00000000..f98271cf --- /dev/null +++ b/15_Greedy/Fractional Knapsack.py @@ -0,0 +1,24 @@ +# https://leetcode.com/problems/maximum-units-on-a-truck/ +''' +As we need to find maximum counts so that sort the array in decreasing order so that higher +count per box remain first. Then keep adding and reducing the value of given size. +''' + +class Solution: + def maximumUnits(self, boxTypes: List[List[int]], truckSize: int) -> int: + boxTypes.sort(key = lambda x:x[1], reverse = True) + res = 0 + i = 0 + while i < len(boxTypes): + if truckSize > boxTypes[i][0]: + res += boxTypes[i][0] * boxTypes[i][1] + truckSize -= boxTypes[i][0] + else: + res += truckSize * boxTypes[i][1] + break + i += 1 + + return res + +# Time: O(N log(N)) +# Space: O(1) \ No newline at end of file diff --git a/15_Greedy/Job Sequencing Problem .py b/15_Greedy/Job Sequencing Problem .py new file mode 100644 index 00000000..7dfbb4c2 --- /dev/null +++ b/15_Greedy/Job Sequencing Problem .py @@ -0,0 +1,42 @@ +# Theory: https://youtu.be/zPtI8q9gvX8 +# Approach: https://youtu.be/LjPx4wQaRIs + +''' +Input: +N = 4 +Jobs = {(1,4,20),(2,1,10),(3,1,40),(4,1,30)} +Output: +2 60 + +Input: +N = 5 +Jobs = {(1,2,100),(2,1,19),(3,2,27),(4,1,25),(5,1,15)} +Output: +2 127 +''' + +class Solution: + def JobScheduling(self,jobs,n): + # Sort based on the profit in decending order + jobs.sort(key = lambda x:x[2]) + + maxDeadline = 0 + for job in jobs: + maxDeadline = max(maxDeadline, job[1]) + + jobDone = [False] * (maxDeadline + 1) + + jobsCount = 0 + totalProfit = 0 + for job in jobs: + for i in range(job[1], 0, -1): + if jobDone[i] == False: + jobsCount += 1 + totalProfit += job[2] + jobDone[i] == True + break + + return jobsCount, totalProfit + +# Time: O(N log(N)) + O(N * M) # where N = len(jobs); M = maxDeadline +# Space: O(M) \ No newline at end of file diff --git a/15_Greedy/Number of Coins.py b/15_Greedy/Number of Coins.py new file mode 100644 index 00000000..68cbbac9 --- /dev/null +++ b/15_Greedy/Number of Coins.py @@ -0,0 +1,41 @@ +# https://leetcode.com/problems/coin-change/ +# https://practice.geeksforgeeks.org/problems/number-of-coins1824/1/ +# https://www.youtube.com/watch?v=I-l6PBeERuc&list=PL_z_8CaSLPWekqhdCPmFohncHwz8TY2Go&index=16 + +import sys +class Solution: + def coinChange(self, coins: List[int], amount: int) -> int: + # As we have infinite supply of coins so this question is a variation of Unbounded Knapsack + dp = [[sys.maxsize] * (amount + 1) for i in range(len(coins) + 1)] # placing max value as we want to find minimum + # in Python 3 as sys.maxsize is max value of integer + + # Initializing 0th row = infinite = float("inf"); as for array coins of size zero it will take infinite coins to make amount j + for j in range(amount + 1): + dp[0][j] = sys.maxsize + + # Initializing 0th column from dp[1][0] to dp[len(coins)][0] as 0 + # as for array coins of size >= 1; to make amount 0 don't need to take any coin so dp[i][0] = 0 for 1 <= i <= len(coins) + for i in range(1, len(coins)+1): + dp[i][0] = 0 + +# *** IN THIS QUESTION WE HAVE TO INITIALIZE 1st ROW ALSO *** + # in 1st row check if amount j is multiple of coins[0] or not + # if multiple put number of coins[0] required to make amount j in dp[1][j] + for j in range(1, amount + 1): + if j % coins[0] == 0: # multiple or not + dp[1][j] = j // coins[0] # number of coins[0] required to make amount j + + # change remaing dp + for i in range(2, len(coins) + 1): + for j in range(1, amount + 1): + if coins[i - 1] <= j: + dp[i][j] = min(1 + dp[i][j - coins[i-1]], dp[i-1][j]) + else: + dp[i][j] = dp[i - 1][j] + + ans = dp[-1][-1] + return ans if ans != sys.maxsize else -1 + + +# Time Complexity = O((len(coins)+1) * (amount+1)) +# Space Complexity = O((len(coins)+1) * (amount+1)) \ No newline at end of file diff --git a/18_Array/Count Reverse Pairs.py b/18_Array/Count Reverse Pairs.py new file mode 100644 index 00000000..d602ea6f --- /dev/null +++ b/18_Array/Count Reverse Pairs.py @@ -0,0 +1,54 @@ +# https://leetcode.com/problems/reverse-pairs/ +# https://youtu.be/S6rsAlj_iB4 +''' +use the merge sort concept of Inversion of Array +https://github.com/SamirPaul1/DSAlgo/blob/main/30-Days-SDE-Sheet-Practice/02.%20Day%202%20Arrays%20Part-II/Inversion%20of%20Array%20-using%20Merge%20Sort.py +''' +class Solution: + def __init__(self): + self.count = 0 + + def reversePairs(self, nums: List[int]) -> int: + self.mergeSort(nums) + return self.count + + def mergeSort(self, nums): + if len(nums) > 1: + # calculate mid + mid = len(nums) // 2 + # divide the input array in to right and left + left = nums[:mid] + right = nums[mid:] + + self.mergeSort(left) + self.mergeSort(right) + + # the tricky part - updating the count of number of possible pairs + j = 0 + for i in range(len(left)): + while j < len(right) and left[i] > 2 * right[j]: + j += 1 + self.count += j + + # merge two sorted array + i = j = k = 0 + while i < len(left) and j < len(right): + if left[i] <= right[j]: + nums[k] = left[i] + k += 1 + i += 1 + else: + nums[k] = right[j] + k += 1 + j += 1 + while i < len(left): + nums[k] = left[i] + k += 1 + i += 1 + while j < len(right): + nums[k] = right[j] + k += 1 + j += 1 + +# Time: O(n log(n)) +# Space: O(n) \ No newline at end of file diff --git a/18_Array/x raised to the power n without using built-in function.py b/18_Array/x raised to the power n without using built-in function.py new file mode 100644 index 00000000..14315e17 --- /dev/null +++ b/18_Array/x raised to the power n without using built-in function.py @@ -0,0 +1,22 @@ +# https://leetcode.com/problems/powx-n/ +# Using Binary Exponentiation + +class Solution: + def myPow(self, x: float, n: int) -> float: + res = 1 + p = n + if p < 0: p *= -1 + + while p: + if p % 2 == 0: + x = x*x + p /= 2 + else: + res *= x + p -= 1 + + if n < 0: return 1 / res + return res + +# Time: O(log(n)) # as get devided each time +# Space: O(1) \ No newline at end of file diff --git a/23_Recursion/01. Subset Sums.py b/23_Recursion/01. Subset Sums.py new file mode 100644 index 00000000..e8b2c689 --- /dev/null +++ b/23_Recursion/01. Subset Sums.py @@ -0,0 +1,22 @@ +# https://practice.geeksforgeeks.org/problems/subset-sums2234/1# + +''' +Make the recursion TREE of the problem. one chile including the current element +and other child Not including the current element +''' +class Solution: + def subsetSums(self, arr, N): + res = [] + def solve(i, s): + if i == len(arr): + res.append(s) + return + # make 2 calls for 2 childs of the recursion tree + solve(i+1, s + arr[i]) # Include the current element + solve(i+1, s) # NOT include the current element + + solve(0, 0) + return res + +# Time: O(2 ^ N) # as for each element we are calling 2 different calls +# Space: O(N) \ No newline at end of file diff --git a/23_Recursion/02. Subsets II.py b/23_Recursion/02. Subsets II.py new file mode 100644 index 00000000..cfaff1b2 --- /dev/null +++ b/23_Recursion/02. Subsets II.py @@ -0,0 +1,25 @@ +# https://leetcode.com/problems/subsets-ii/ + +# Use the same concept of 01. Subset Sums + +class Solution: + def subsetsWithDup(self, nums: List[int]) -> List[List[int]]: + nums.sort() + res = [] + + def dfs(i, subset): + if i >= len(nums): + res.append(subset) + return + + dfs(i+1, subset + [nums[i]]) + while i + 1 < len(nums) and nums[i] == nums[i+1]: + i += 1 + dfs(i+1, subset) + + dfs(0, []) + return res + + +# Time: O(2^n) +# Space: O(2^n) # as we are storeing subsets in path array in each calls \ No newline at end of file diff --git a/23_Recursion/03. Combination Sum.py b/23_Recursion/03. Combination Sum.py new file mode 100644 index 00000000..54079fe8 --- /dev/null +++ b/23_Recursion/03. Combination Sum.py @@ -0,0 +1,26 @@ +# https://leetcode.com/problems/combination-sum/ +''' +At each index we have 2 options. Either keep the current element or skip the current element. +Make a recursion tree of this selection. We can select an element multiple times so one call +with same index another call from next index. +''' +class Solution: + def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: + res = [] + + def solve(i, target, path): + if target == 0: + res.append(path) + return + if target < 0 or i >= len(candidates): return + + solve(i, target-candidates[i], path + [candidates[i]]) + solve(i+1, target, path) + + solve(0, target, []) + return res + +# Time and space complexity of this problem if not fixed. But below is the hypothetical time and space +# Time: O(2^t * n) ; where t = target/candidate[i] ; n = len(candidates) +# Space: O(nCk) ; where n = len(candidates) ; k = number of combinations +# k depends on the values of candidates as if large value more quickly target can be achived and less length diff --git a/23_Recursion/04. Combination Sum II.py b/23_Recursion/04. Combination Sum II.py new file mode 100644 index 00000000..71b9a08a --- /dev/null +++ b/23_Recursion/04. Combination Sum II.py @@ -0,0 +1,27 @@ +# https://leetcode.com/problems/combination-sum-ii/ +''' +At each index we have 2 options. Either keep the current element or skip the current element. +Make a recursion tree of this selection. We can select an element multiple times so one call +with same index another call from next index. +''' +class Solution: + def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]: + candidates.sort() + res = [] + def solve(i, target, path): + if target == 0: + res.append(path) + return + if i >= len(candidates) or target < 0: return + + solve(i+1, target-candidates[i], path + [candidates[i]]) + # As we can use 1 index only onece and duplecate possible + while i+1 < len(candidates) and candidates[i] == candidates[i+1]: + i += 1 + solve(i+1, target, path) + + solve(0, target, []) + return res + +# Time: O(2^N) +# Space: O(2^N) \ No newline at end of file diff --git a/23_Recursion/05. Palindrome Partitioning.py b/23_Recursion/05. Palindrome Partitioning.py new file mode 100644 index 00000000..e0e3993e --- /dev/null +++ b/23_Recursion/05. Palindrome Partitioning.py @@ -0,0 +1,31 @@ +# https://leetcode.com/problems/palindrome-partitioning/ +''' +Traverse through the string and check whether left part of the iterator palindrom or not. +if palindrom call the function +''' +class Solution: + def partition(self, s: str) -> List[List[str]]: + res = [] + + def solve(s, path): + if not s: + res.append(path) + for i in range(len(s)): + if s[:i+1] == s[:i+1][::-1]: + solve(s[i+1:], path + [s[:i+1]]) + + solve(s, []) + return res + +''' +Time Complexity: O( (2^n) *k*(n/2) ) +Reason: O(2^n) to generate every substring and O(n/2) to check if the substring generated is a palindrome. + O(k) is for inserting the palindromes in another data structure, where k is the average length of the palindrome list. + + +Space Complexity: O(k * x) +Reason: The space complexity can vary depending upon the length of the answer. + k is the average length of the list of palindromes and if we have x such list of palindromes in our final answer. + The depth of the recursion tree is n, so the auxiliary space required is equal to the O(n). + +''' \ No newline at end of file diff --git a/23_Recursion/06. K-th permutation Sequence.py b/23_Recursion/06. K-th permutation Sequence.py new file mode 100644 index 00000000..45b37548 --- /dev/null +++ b/23_Recursion/06. K-th permutation Sequence.py @@ -0,0 +1,28 @@ +# https://leetcode.com/problems/permutation-sequence/ +# https://www.youtube.com/watch?v=wT7gcXLYoao + +''' +We if we fix one element then there can be (n-1) factorial of permutations starting with that element. +with this concept find the indeces and keep andding and popping elements from arr. +''' +import math +class Solution: + def getPermutation(self, n: int, k: int) -> str: + arr = [str(i) for i in range(1, n+1)] + p = math.factorial(n-1) + res = '' + k -= 1 + while k > 0: + i = k // p + res += arr[i] + arr.pop(i) + k %= p + n -= 1 + p //= n + + res += ''.join(arr) + return res + +# Time Complexity = O(N*N) ; we are traversing nums 1 time but POPPING ELEMENT FROM MIDDLE OF ARRAY TAKES O(N) TIME, AS AFTER POPPING ELEMENTS REARRANGE WITH NEW INDEX. +# Time Complexity = O(N!) # if we consider the time taken by math.factorial function. +# Space Complexity = O(N) ; for taking arr. \ No newline at end of file