├── README.md ├── Solutions ├── Arrays │ ├── Problem_41_first_missing_number.cpp │ ├── Problem_42_trapping_rain_water.cpp │ ├── Problem_448_find_all_numbers_disappeared_in_an_array.cpp │ ├── Problem_667_Beautiful_Arrangement_ii.cpp │ └── Problem_904_fruit_into_baskets.cpp ├── Dynamic-Programming │ ├── Problem_10_Regular_Expression_Matching.cpp │ ├── Problem_121_Best_Time_To_Buy_And_Sell_Stock.cpp │ ├── Problem_32_Longest_Valid_Parenthesis.cpp │ ├── Problem_64_Minimum_Path_Sum.cpp │ ├── Problem_91_Decode_Ways.cpp │ └── Problem_97_Interleaving_String.cpp ├── Linked-Lists │ ├── Problem_138_copy_list_with_random_pointer.cpp │ ├── Problem_142_Linked_List_Cycle_ii.cpp │ └── Problem_2_add_two_numbers.cpp ├── Strings │ ├── Problem_32_longest_valid_parentheses.cpp │ ├── Problem_49_group_anagrams.cpp │ ├── Problem_6_zigzag_conversion.cpp │ └── Problem_767_reorganize_string.cpp ├── Topological-And-Heap-Sort │ ├── Problem_1203_Sort_Items_By_Groups_Respecting_Dependencies.cpp │ ├── Problem_210_course_schedule_ii.cpp │ ├── Problem_215_Kth_Largest_Element_In_An_Array.cpp │ ├── Problem_23_Merge_K_Sorted_Lists.cpp │ └── Problem_329_longest-increasing-path-in-a-matrix.cpp └── Trees-1 │ ├── Problem_102_binary_tree_level_order_traversal.cpp │ ├── Problem_114_flatten_binary_tree_to_linked_list.cpp │ ├── Problem_144_binary_tree_preorder_traversal.cpp │ ├── Problem_145_binary_tree_postorder_traversal.cpp │ └── Problem_94_binary_tree_inorder_traversal.cpp └── images ├── arraymeme.jpg ├── dynamicprogrammingmeme.PNG ├── graph.png ├── heapmeme.png ├── linkedlistimg.PNG ├── linkedlistmeme.jpg ├── p105.PNG ├── p15.PNG ├── p207.PNG ├── p23.PNG ├── p236.PNG ├── p3.PNG ├── p30.PNG ├── p33.PNG ├── p347.PNG ├── p416.PNG ├── p48.PNG ├── p61.PNG ├── p62.PNG ├── p659.PNG ├── p70.PNG ├── p92.PNG ├── p92ill.PNG ├── p94.PNG ├── pr11.PNG ├── slidingwindow.png └── treememe.jpg /README.md: -------------------------------------------------------------------------------- 1 | # Advanced Technical Interview Preperation Problems and Solutions :watch: 2 | Presented by UCLA ACM ICPC 3 | Questions: https://tinyurl.com/ask-ucla-icpc
4 | ICPC Facebook Group: https://www.facebook.com/groups/ucla.icpc/ 5 | 6 |

Hello there! Welcome to the Advanced Technical Interview Guide presented to you by ACM ICPC. If you are looking to boost your 7 | technical skills, this is a great place to get started. If you have no idea about the types of questions you would encounter in a technical interview, make sure to check out our CS32 interview prep guide first and then make sure to come back. If you have already, you're in the right place.

8 | 9 | # Table of Contents :zap: 10 | 1. [Arrays](#arrays) 11 | 2. [Strings](#strings) 12 | 3. [Linked Lists](#linkedlists) 13 | 4. [Trees I](#trees1) 14 | 5. [Trees II](#trees2) 15 | 6. [Graphs](#graphs) 16 | 7. [Dynamic Programming](#recursionanddynamicprogramming) 17 | 8. [Topological and Heap Sort](#topologicalandheapsort) 18 | 19 | # Arrays :dolphin: 20 |

Array questions, often interchangeable with string questions are some of the most common questions asked during technical interviews. As we have learned previously, what makes arrays so popular is their simplicity in storing data. When dealing with arrays you want to deal with an algorithm that is in the O(N) or O(log(N)) ballpark. This is achieved by the constant time access with indexing. When required to sort, we would be dealing with an O(NlogN) algorithm. Let us take a look at some vanilla array problems.

21 |

22 | 23 | ## Two pointers approach :earth_asia: 24 |

Often, we come across problems that may have an O(N^2) solution on first glance. The two pointer approach allows us to solve such problems in O(N) or 25 | O(Nlog(N)) if sorting is involved. As the name suggests, it involves two pointers, one that starts at the beginning and one that starts in the end. In each iteration, we move the beginning pointer or the end pointer by some number of indices while searching for an optimal value or finding a maximum/minimum. Consider Leetcode problem 11.

26 |

27 |

The main idea behind solving this problem is to optimize the maximum amount of water that can be stored in a container. The amount of water in a container is given by height * width where the height is the minimum of the two walls and the width is the distance between the two walls. How do we keep track of the two walls? Simple, two pointers. How do we advance the pointers? we increment the start pointer or decrement the end pointer only if the wall that is pointed to by that pointer is shorter than the wall pointed to by the other. We do this in the hope of finding a higher wall so that we can store more water! We stop once the two pointers meet because we don't want to go out of bounds or double count. We solved a potentially O(N^2) time problem in 28 | O(N)!

29 | 30 | ```cpp 31 | class Solution { 32 | public: 33 | int maxArea(vector& height) { 34 | //initialize two pointers start and end that point to the start and end of the list. 35 | int start = 0; 36 | int end = height.size() - 1; 37 | //max_water is the value we are optimizing for and 38 | //the termination codition is when start equals end. 39 | int max_water = 0; 40 | while(start < end){ 41 | //calculate the amount of water that the two walls can hold between them. 42 | int water_amount = min(*(height.begin() + start), *(height.begin() + end))*(end - start); 43 | max_water = max(max_water, water_amount); 44 | //carefully increment start or decrement end (the wall with lesser height) 45 | //in the hope of leading to a higher wall. 46 | if(*(height.begin() + start) > *(height.begin() + end)){ 47 | end -= 1; 48 | }else{ 49 | start += 1; 50 | } 51 | } 52 | return max_water; 53 | } 54 | }; 55 | ``` 56 | ## Sorting :camel: 57 |

Now, sometimes we may not be so lucky when it comes to the order of elements in an array. Sorting, solves a lot of these problems. However, sorting does come at a cost, the fastest algorithm that we can write after performing a sort is of O(Nlog(N)) as sorting is bounded by this time complexity. This, however, is still better than an O(N^2) or O(N^3) solution. Let's demonstrate the importance of sorting in certain scenarios with leetcode problem 15.

58 |

59 | 60 |

This problem adds a layer over our usual two pointer approach. What is that layer? Sorting! What does sorting achieve in this scenario? First off, we notice that there is a simple brute force solution to this problem i.e. forming a triple for loop and finding the triplets that sum up to the given value. But this is ugly and your interviewer will ask you to optimize. Well, since a two pointers approach can only observe two values at a time we won't be able to use that approach. Or could we? We definitely wouldn't be able to find a 3sum in O(N) but what about O(N^2) which is, after all, better than O(N^3). This is where we start thinking along the lines of sorting. Sorting will not disturb the O(N^2) time complexity of our algorithm because we can sort an array in O(Nlog(N)). On sorting, we consider an element centric approach where we move from element to element and check if there exist two greater numbers that sum up to the remaining value using a two pointers approach. We also have to account for unique triplets so we skip values if we have already encountered them.

61 | 62 | ```cpp 63 | class Solution { 64 | public: 65 | vector> threeSum(vector& nums) { 66 | //First, we sort. 67 | sort(nums.begin(), nums.end()); 68 | vector> res; 69 | int pos = 0; 70 | //iterate throught each number in the sorted list one by one. 71 | for(int i = 0; i < nums.size(); i++){ 72 | //continue if we have already dealt with this value 73 | //of 'it' because we only want unique triplets in our result vector. 74 | if(i != 0 && nums[i] == nums[i-1]){ 75 | continue; 76 | } 77 | //initialize our two pointers 78 | int start = i+1; 79 | int end = nums.size() - 1; 80 | while(start < end){ 81 | //again we only want uniques so we advance 82 | //start or end if we have already accounted for the numbers pointed by them. 83 | if(start != i+1 && nums[start] == nums[start-1]){ 84 | start += 1; 85 | continue; 86 | }else if(end != nums.size() - 1 && nums[end] == nums[end + 1]){ 87 | end -= 1; 88 | continue; 89 | } 90 | //Check three possible cases. 91 | if(nums[i] + nums[start] + nums[end] == 0){ 92 | res.push_back({nums[i], nums[start], nums[end]}); 93 | start += 1; 94 | end -= 1; 95 | }else if (nums[i] + nums[start] + nums[end] < 0){ 96 | start += 1; 97 | }else{ 98 | end -= 1; 99 | } 100 | } 101 | } 102 | return res; 103 | } 104 | }; 105 | ``` 106 | ## Searching :snowman: 107 |

Often, we are presented with one or more sorted arrays or semi-sorted arrays. When the interviewer provides you with this information, you will need to take advantage of it to reduce the time complexity of the solution. The best way to take advantage of this is by using a binary search or some equivalent that yields an O(log(N)) time complexity.

108 |

The solution to a problem may not always be direct binary search. You may have to handle a few extra cases or edge cases but the solution is almost always "Binary Search-esque." For example, consider Leetcode problem 33.

109 | 110 |

111 | 112 |

What would the brute force solution be in this case? Well, simply searching each element one by one in a simple for loop. But we didn't take advantage of the fact that it is rotated sorted, i.e. the array can be divided into two sorted parts. Now if we want logarithmic time, we could simply perform a binary search on each part of the list to find the target element. So the key to the solution of the problem is finding the pivot i.e. the smallest element which divides the array into the two sorted parts. We find the pivot using a modified version of binary search.

113 | 114 | ```cpp 115 | class Solution { 116 | public: 117 | int search(vector& nums, int target) { 118 | //Note that we use start and end twice, once to find the pivot 119 | //and again to find the target element. 120 | int start = 0; 121 | int end = nums.size() - 1; 122 | int pivot_index = 0; 123 | 124 | //first we attempt to find the pivot if there exists any. 125 | while(start <= end){ 126 | int mid = (start + end) / 2; 127 | if(mid == 0 || mid == nums.size() - 1){ 128 | start = 0; 129 | end = nums.size() - 1; 130 | break; 131 | }else if(nums[mid] > nums[mid+1]){ //discrepency in increasing order of sort indicates 132 | //we have found the pivot. 133 | if(target <= nums[mid] && target >= nums[0]){ 134 | start = 0; 135 | end = mid; 136 | }else{ 137 | start = mid + 1; 138 | end = nums.size() - 1; 139 | } 140 | break; 141 | }else if(nums[mid] < nums[mid-1]){ 142 | if(target <= nums[mid-1] && target >= nums[0]){ 143 | start = 0; 144 | end = mid-1; 145 | }else{ 146 | start = mid; 147 | end = nums.size() - 1; 148 | } 149 | break; 150 | }else{ 151 | if(nums[mid] > nums[start]){ 152 | start = mid + 1; 153 | }else{ 154 | end = mid - 1; 155 | } 156 | } 157 | } 158 | //if start has crossed end then there is no rotations and we can perform a single binary 159 | //search on the entire array. 160 | if(start >= end){ 161 | start = 0; 162 | end = nums.size() - 1; 163 | } 164 | 165 | //Now that we know which sorted part of the array consists of the element, we perform binary search on that part. 166 | while(start <= end){ 167 | if(nums[start] == target){ 168 | return start; 169 | }else if(nums[end] == target){ 170 | return end; 171 | } 172 | int mid = (start + end) / 2; 173 | if(nums[mid] == target){ 174 | return mid; 175 | }else if (nums[mid] < target){ 176 | start = mid + 1; 177 | }else{ 178 | end = mid - 1; 179 | } 180 | } 181 | return -1; 182 | } 183 | }; 184 | ``` 185 | ## Logical Problems :lock: 186 |

Some array problems are simply math-based problems where we have to summon our math/logic skills. Sadly, there is no definite approach that we can use to solve such problems. The only way that we stand a good chance at solving such problems is solving more problems! Consider Leetcode problem 48.

187 | 188 |

189 | 190 |

At first glance, this problem seems rather simple. You pull out a pen and work through an example, you see a definite pattern where each element, on rotation, is taking the place of another element which is located at a definite number of places away in the matrix. However, while it seems like a definite pattern, coming up with an element-wise algorithm is tedious. Moreover, since we have to do it in place, swapping two elements will make us lose track of the element that already exists at the position we are swapping into. This is where we need to apply some kind of logic to the problem. We do this by breaking down the rotation into two predictable operations, i.e. a reflection about the middle row followed by a reflection about the diagonal. Note that swapping is not a problem here because when we swap, both elements are being swapped into their final positions.

191 | 192 | ```cpp 193 | class Solution { 194 | public: 195 | void rotate(vector>& matrix) { 196 | //first we reflect about the middle row. 197 | reverse(matrix.begin(), matrix.end()); 198 | //now we reflect about the diagonal. 199 | for(int i = 0; i < matrix.size(); i++){ 200 | for(int j = i+1; j < matrix.size(); j++){ 201 | swap(matrix[i][j], matrix[j][i]); 202 | } 203 | } 204 | } 205 | }; 206 | ``` 207 | ## Practice :muscle: 208 |

These are some of the techniques that we use when solving array problems. While most of these techniques seem rather abstract, knowing them will allow us to nudge ourselves towards the right solution. Now that you have been equipped with these techniques, attempt to solve the following problems. If you get stuck, don't worry, we've got your back. Feel free to peek at the solution in the solutions directory or if you don't want to, zoom into office hours!

209 | 1. Leetcode Problem 41: First Missing Number
210 | 2. Leetcode Problem 42: Trapping Rain Water
211 | 3. Leetcode Problem 448: Find All Numbers Disappeared In An Array
212 | 4. Leetcode Problem 667: Beautiful Arrangement ii
213 | 5. Leetcode Problem 904: Fruit Into Baskets 214 | 215 | # Strings :ramen: 216 |

Array problems are often interchangable with string problems. After all, a string is an array of characters! When solving string problems, we might have to employ the two pointers approach that we discussed and sometimes even sorting (where alphabetical ordering matters). But there are a few new tricks that we could employ to solve string problems. Remember! if we can solve string problems using array techniques, we can solve array problems with string techniques.

217 | 218 |

219 | 220 | ## Using A Hashtable :rocket: 221 |

What's so cool about strings and characters is that they are hashable. We can associate a character, sub-string or string with a certain value that we would like to keep track of for each character, sub-string or string respectively. This could be the number of times it appears or even the position of the character in a string or string in an array. Let us illustrate the use of a hashtable with Leetcode problem 3.

222 | 223 |

224 | 225 |

In order to find the longest substring with non-repeating characters, we keep track of the last seen position of each character in the string. This way we know that the next sub-string that we observe has to begin at the next position of the last seen position.

226 | 227 | ```cpp 228 | class Solution { 229 | public: 230 | int lengthOfLongestSubstring(string s) { 231 | unordered_map seen; 232 | int start = 0; 233 | int end = 0; 234 | int longest_len = 0; 235 | while(start != s.length() && end != s.length()){ 236 | if(seen.find(s[end]) == seen.end() || seen[s[end]] < start){ 237 | seen[s[end]] = end; 238 | if(end - start + 1 > longest_len) 239 | longest_len = end - start + 1; 240 | end += 1; 241 | } 242 | else{ 243 | start = seen[s[end]] + 1; 244 | seen[s[end]] = end; 245 | end += 1; 246 | } 247 | } 248 | return longest_len; 249 | } 250 | }; 251 | ``` 252 | ## Sliding Window :running: 253 |

Another greedy approach to solving string problems is using a sliding window of a fixed size to slide across our string while we check constraints and keep track of some values. A sliding window is essentially a range of characters in our string that starts from the beginning and terminates a fixed-length before the end of the string. The concept is better illustrated in this figure from TinyFool's blog.

254 | 255 |

256 | 257 |

In the above figure, the window is three characters wide and slides across from the beginning to the end. We need to make sure to check edge cases and not slide out of bounds. Let us work through a problem that makes use of the sliding window technique. Consider Leetcode problem 30.

258 | 259 |

260 | 261 |

The above problem requires using both the techniques that we just learned. A hash table to keep track of the which words are present in the words vector and the number of times each one occurs, and a sliding window of some length to slide across all possible candidate substrings that are of that length. On close observation, we notice that the length of the window is going to be the total number of characters in the words vector. This is because every concatenation of all words will be of that fixed length. Now we just slide our window across the string. For each candidate, we check if all the individual words in the words vector are present. We do this by counting backward, decrementing the count of a word in a copy of the previously constructed hash table every time we encounter a valid word. We also count the number of words encountered to ensure that we account for exactly the right number of words. The time complexity of the above algorithm is going to be O((N-K) * K) where K is the length of the concatenated string and N is the length of the given string.

262 | 263 | ```cpp 264 | class Solution { 265 | public: 266 | vector findSubstring(string s, vector& words) { 267 | //result we are going to return 268 | vector result; 269 | //handle edge cases 270 | if(s == "" || words.size() == 0) 271 | return result; 272 | //we obtain the length of each word in words vector and the size of the window 273 | //we will be considering. 274 | int word_len = words[0].length(); 275 | int window_len = words.size() * word_len; 276 | //handle invalid case where our window is bigger than our string. 277 | if(window_len > s.size()) 278 | return result; 279 | //create a hash table to keep track of words in the words vector and the number 280 | //of times each word appears. This will be used to check if a given window over 281 | //the string s has a combination of any of these words 282 | unordered_map word_counts; 283 | for(auto it : words){ 284 | if(word_counts.find(it) == word_counts.end()){ 285 | word_counts[it] = 1; 286 | }else{ 287 | word_counts[it] += 1; 288 | } 289 | } 290 | //iterate through all windows 291 | for(int i = 0; i <= s.size() - window_len; i++){ 292 | //create a temporary hash map that will keep track of the words encountered 293 | //in this window. 294 | unordered_map temp_words_counts(word_counts); 295 | int j = i; 296 | //number of words that are in the words vector which must be zero if we have 297 | //encountered all the words in our window. 298 | int count = words.size(); 299 | while(j < i + window_len){ 300 | //check every substring of length 'word_len' 301 | string temp = s.substr(j, word_len); 302 | //if the word is not in our words vector or even if it is but there are too many 303 | //break out of this window. 304 | if(word_counts.find(temp) == word_counts.end() || temp_words_counts[temp] == 0){ 305 | break; 306 | }else{ 307 | //else we decrement the count of that particular word and count, moving us 308 | //closer to the result. 309 | temp_words_counts[temp] -= 1; 310 | count -= 1; 311 | } 312 | j += word_len; 313 | } 314 | //we have encountered all the words in this window in some permutation. 315 | //we add the starting index to our result vector. 316 | if(count == 0) 317 | result.push_back(i); 318 | 319 | } 320 | 321 | return result; 322 | } 323 | }; 324 | ``` 325 | ## Practice :muscle: 326 |

There are a few more types of string questions that one may encounter in technical interviews. These include Dynamic Programming, BFS/DFS, Topological Sort, etc. Don't worry, we'll cover these concepts when we get there. But for now, we will just work with the above problem-solving techniques. Remember to use the skills that you employed when solving array questions because they can come in handy as well.

327 | 328 | 1. [Leetcode Problem 6: ZigZag Conversion](https://leetcode.com/problems/zigzag-conversion/) 329 | 2. [Leetcode Problem 32: Longest Valid Parentheses](https://leetcode.com/problems/longest-valid-parentheses/) 330 | 3. [Leetcode Problem 49: Group Anagrams](https://leetcode.com/problems/group-anagrams/) 331 | 4. [Leetcode Problem 767: Reorganize String](https://leetcode.com/problems/reorganize-string/) 332 | 333 | # Linked Lists :musical_note: 334 |

Linked Lists are a pretty neat data structure. As you might have learned in CS 32, the beginning of the list is marked by the head and the end of the list is marked by a pointer to a null pointer. There are various kinds of linked lists like singly-linked lists, doubly linked lists, circular linked lists, etc. When it comes to technical interviews, you are most often going to deal with linked lists which are singly linked. Why? simply they are the most challenging algorithmically. You would have to take extra care when traversing a linked list to not make null access or keep track of the previous element because you will not be able to just step back if you need to access it. There are no particular linked list problem-solving techniques but the keen eye will catch a few tricks that we can employ when dealing with singly-linked lists.

335 | 336 |

337 | 338 | ## Using a dummy node :smiley_cat: 339 | 340 |

Let's take a look at one particular interesting trick that can help us solve a lot of linked list problems without worrying too much about edge cases. That trick is using a dummy pointer. Using a dummy pointer in the beginning and setting it to the head allows us to treat one and zero element lists no differently from linked lists with more than one node. Further, using a dummy head helps us to keep track of the head of the list easily. For example, if the head of the linked list is moved away and is no longer the linked list of the new re-ordered list, we can simply return the head of the list by returning the dummy's next pointer.

341 | 342 |

343 | 344 |

This trick is best illustrated with the following example. Consider Leetcode problem 92.

345 | 346 |

347 | 348 |

First off, we notice that this problem could get messy with the edge cases since it involves partially reversing a list. We attach a dummy node to the beginning of the list to avoid handling these special edge cases. Once we're done with that we iterate with a pointer 'prev' to the node just before the beginning of the sub-list we are going to reverse. We then set a pointer to point to the start of the list (i.e. the pointer 'start'). The idea now would be to move the start of the sub-list forward by moving the node to the next of the start of the sub-list to be just after the node 'prev'. We thus accomplish reversing the linked list in a single pass and in O(N) time where N is the number of nodes in the list. This is best illustrated with the following figure.

349 | 350 |

351 | 352 | ```cpp 353 | class Solution { 354 | public: 355 | ListNode* reverseBetween(ListNode* head, int m, int n) { 356 | //create a dummy node. 357 | ListNode *dummy = new ListNode(0); 358 | dummy->next = head; 359 | ListNode* prev = dummy; 360 | //move the prev node until the node just before the start of 361 | //the sublist that we want to reverse. 362 | for(int i = 0; i < m-1; i++) prev = prev->next; 363 | ListNode* curr = prev->next; 364 | for(int i = 0; i < n-m; i++){ 365 | ListNode* temp = prev->next; 366 | prev->next = curr->next; 367 | curr->next = curr->next->next; 368 | prev->next->next = temp; 369 | 370 | } 371 | return dummy->next; 372 | } 373 | }; 374 | ``` 375 | 376 | ## Messy pointer movement :horse: 377 | 378 |

The one problem unlike arrays that we face with linked is that the length of the linked list is not so easy to calculate. Luckily, we can determine the length of the linked list in O(N) time so it will not, in most cases, affect the time complexity of the algorithm. We need to be careful when we make generalizations regarding the math surrounding linked lists. We would have to handle some edge cases as best illustrated by the solution to the following problem. Consider the Leetcode problem 61.

379 | 380 |

381 | 382 |

On close observation, we notice that we need to rotate the list about the kth node from the end as shown in the example above. This corresponds to the (length - k)th node where 'length' is the length of the linked list. We also need to be careful about the case when k is greater than the length of the linked list which simply means that we are rotating multiple times even after returning to the original configuration of the linked list one or more times. This can be handled by taking the remainder with the length of the list to obtain the effective number of places we are going to rotate the list. Once be determine the kth node from the end (or the (length-k)th node from the beginning) it is only a matter of moving some pointers around after which we reach our result configuration. The time complexity would be O(N) for this solution.

383 | 384 | ```cpp 385 | class Solution { 386 | public: 387 | ListNode* rotateRight(ListNode* head, int k) { 388 | if(head == nullptr || head->next == nullptr || k == 0) return head; 389 | int length = 1; 390 | ListNode* tail = head; 391 | while(tail->next != nullptr){ 392 | tail = tail->next; 393 | length += 1; 394 | } 395 | k = length - (k % length); 396 | if(k == length) return head; 397 | ListNode* newHead = head; 398 | ListNode* prev = nullptr; 399 | while(k){ 400 | prev = newHead; 401 | newHead = newHead->next; 402 | k -= 1; 403 | } 404 | tail->next = head; 405 | prev->next = nullptr; 406 | return newHead; 407 | } 408 | }; 409 | ``` 410 | 411 | ## A hard problem :rose: 412 | 413 |

Lastly, let us attempt to solve a hard problem related to linked lists. Merging two sorted lists is pretty straight forward. If you have never attempted that problem, be sure to check that out first. We discuss the problem in the CS32 interview prep guide. Let us consider a harder problem i.e. Leetcode problem 23. One where we merge K sorted lists. 414 | 415 |

416 | 417 |

At first glance, the most basic idea derived from the implementation of the merge two lists is to take the list with the smallest head, add the element to our list and advance the head of that node by one. We keep doing this until all our lists' heads are at null which means we have successfully appended all the elements of our list to the end of our sorted list. The important aspect here is to deal with keeping track of which element to pick from the sorted lists. We would have to iterate through all the list head elements each time we add a new element making the time complexity of this approach O(N*K) where K is the number of sorted lists. If we want to do better, we have to exploit the fact that all the lists are already sorted.

418 | 419 |

We introduce the idea of a priority queue, a queue, often implemented as a heap, that keeps track of the order of elements. We define a custom comparison class for the priority queue so that it can store the linked lists' heads in a way that the head with the least value is at the front of our queue. This makes our algorithm efficient because when we pop a list from the queue, append the head node to our result list and advance the head pointer, we do this in constant time. The only costly operation here is to maintain the order of the queue which when implemented as a heap just takes log(K) time to reorder when we add a new head node to the queue. We do this O(N) times while constructing our N element sorted result list. This makes our algorithm more efficient as the time complexity is O(N*log(K)). 420 |

421 | 422 | ```cpp 423 | //This is how we define a custom compare class for a priority queue in C++. 424 | //The custom comparator takes in the heads of two sorted lists and 425 | //returns true if the first list head is greater than that of the second. 426 | //note that this is opposite to a custom comparator for the sort function because 427 | //the priority queue defaults to storing the elements in decreasing order. 428 | struct CompareHead { 429 | bool operator()(ListNode* const& h1, ListNode* const& h2) 430 | { 431 | return h1->val > h2->val; 432 | } 433 | }; 434 | 435 | class Solution { 436 | public: 437 | ListNode* mergeKLists(vector& lists) { 438 | //initialize a priority queue. 439 | priority_queue, CompareHead> Q; 440 | //use a dummy head to mark the beginning of the result linked list. 441 | ListNode* dummy = new ListNode(0); 442 | dummy->next = nullptr; 443 | ListNode* curr = dummy; 444 | //push all the linked lists in the lists vector into our priority queue 445 | //which orders them based on the custom comparator defined above. 446 | for(int i = 0; i < lists.size(); i++) { 447 | if(lists[i] != nullptr) 448 | Q.push(lists[i]); 449 | } 450 | //while we still have elements in the Priority Queue. 451 | while(Q.size()){ 452 | ListNode* temp = Q.top(); 453 | Q.pop(); 454 | //append the popped head to the result linked list. 455 | curr->next = new ListNode(temp->val); 456 | curr = curr->next; 457 | curr->next = nullptr; 458 | //advance the head by one node if there are elements left in the 459 | //linked list. 460 | if(temp->next != nullptr) 461 | Q.push(temp->next); 462 | } 463 | return dummy->next; 464 | } 465 | }; 466 | ``` 467 | 468 | ## Practice :muscle: 469 | The best way to solve hard linked list problems is to get good at reversing lists, sublists, pairs of nodes etc. It always helps to draw out the linked list and visualize how the pointers have to move around before getting into actual code. 470 | 471 | 472 | 1. [Leetcode Problem 2: Add Two Numbers](https://leetcode.com/problems/add-two-numbers/) 473 | 2. [Leetcode Problem 138: Copy List With Random Pointer](https://leetcode.com/problems/copy-list-with-random-pointer/) 474 | 3. [Leetcode Problem 142: Linked List Cycle II](https://leetcode.com/problems/linked-list-cycle-ii/) 475 | 476 | # Trees I 477 |

Trees are an important data structure that one often deals with during technical interviews. When we take a look at trees we won't just be understanding trees as a data structure but we will also be dealing with quite a few tree algorithms. Tree algorithms involve tree traversals, tree searches, maintaining a particular ordering of nodes in a tree, etc. We will also be looking into a special class of trees called Binary Search Trees and how we can perform a Binary Search on such a tree. For now, let us deal with problems involving simple rooted trees.

478 | 479 |

480 | 481 | ## Tree Traversals 482 |

A tree traversal is a walk through the entire tree visiting each node of the tree at least once. What order do we visit the nodes in? That is exactly what different tree traversals are all about, an ordering of the nodes. Let us consider In Order Traversal. Inorder traversal involves recursively visiting the left sub tree, followed by the root node and finally the right sub-tree. If we were asked to print the value of every node in a tree in order, we would perform an in order traversal like this.

483 | 484 | ```cpp 485 | void inorderTraversal(TreeNode* root){ 486 | if(root == nullptr) 487 | return; 488 | inorder_traversal(root->left); 489 | cout << root->val; 490 | inorder_traversal(root->right); 491 | return; 492 | } 493 | ``` 494 | 495 |

While the above function performs inorder traversal of a tree and the code looks beautiful: clean and simple, there is a better solution. Consider Leetcode problem 94. While the solution is optimal in terms of time complexity (O(N) where N is the number of nodes in the tree since we are visiting each node exactly once), there is a more space efficient solution that can be achieved by taking an iterative approach. The recursive approach is more memory hungry as it takes up O(N) stack space. Allocating more stack space also slows down the recursion. So a faster and more memory efficient approach would be to perform the traversal iteratively.

496 | 497 |

498 | 499 |

If do not want to use the memory stack because of the overhead, we will create our own stack to assist us with the traversal. Below is the solution to the stack-based iterative solution to performing an inorder traversal of a tree.

500 | 501 | ```cpp 502 | class Solution { 503 | public: 504 | vector inorderTraversal(TreeNode* root) { 505 | vector result; 506 | stack nodes; 507 | TreeNode* curr = root; 508 | while(true){ 509 | if(curr != nullptr){ 510 | nodes.push(curr); 511 | curr = curr->left; 512 | }else{ 513 | if(nodes.size() == 0){ 514 | break; 515 | }else{ 516 | TreeNode* temp = nodes.top(); 517 | nodes.pop(); 518 | result.push_back(temp->val); 519 | curr = temp->right; 520 | } 521 | } 522 | } 523 | return result; 524 | } 525 | }; 526 | ``` 527 |

There are two other types of traversals, post-order traversal and pre-order traversal. Post-order traversal involves visiting the left sub-tree, the right sub-tree and then the root. Pre-order traversal involves visiting the root, followed by the left sub-tree, followed by the right sub-tree. The recursive implementation is naive and just involves moving around the recursive statements in the recursive implementation of in-order traversal. The iterative implementation of these two traversals is left as an exercise. Use the same technique that we discussed above. Do it!

528 | 529 |

Now that we know how to traverse the trees, let us attempt to construct a tree from the inorder and pre-order traversal orders. Consider Leetcode problem 105.

530 | 531 |

532 | 533 |

This problem is all about using the right pointers. Upon close observation of the inorder and preorder lists, we notice that subtrees are grouped together. The nodes of the left subtree is grouped together in the preorder and inorder lists. The same goes to the left. So we can take advantage of this grouping in our recursion. We maintain 4 pointer in each recursive call. The start of the preorder list, the end of the preorder list, the start of the inorder list and the end of the inorder list. During the call we generate eight new pointers, the four mentioned pointers for the left and right subtrees of the root.

534 | 535 | ```cpp 536 | /** 537 | * Definition for a binary tree node. 538 | * struct TreeNode { 539 | * int val; 540 | * TreeNode *left; 541 | * TreeNode *right; 542 | * TreeNode() : val(0), left(nullptr), right(nullptr) {} 543 | * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} 544 | * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} 545 | * }; 546 | */ 547 | class Solution { 548 | public: 549 | 550 | TreeNode* buildTreeHelper(vector& preorder, vector& inorder, int preorder_start, int preorder_end, int inorder_start, int inorder_end){ 551 | if(preorder_start >= preorder_end || inorder_start >= inorder_end) 552 | return nullptr; 553 | 554 | int root = preorder[preorder_start]; 555 | int iroot; 556 | for(int j = inorder_start; j < inorder_end; j++){ 557 | if(inorder[j] == root){ 558 | iroot = j; 559 | break; 560 | } 561 | } 562 | TreeNode* rootptr = new TreeNode(root); 563 | int length_of_left_list = iroot - inorder_start; 564 | int new_preorder_start_left = preorder_start + 1; 565 | int new_preorder_end_left = preorder_start + length_of_left_list + 1; 566 | int new_preorder_start_right = new_preorder_end_left; 567 | int new_preorder_end_right = preorder_end; 568 | int new_inorder_start_left = inorder_start; 569 | int new_inorder_end_left = iroot; 570 | int new_inorder_start_right = iroot + 1; 571 | int new_inorder_end_right = inorder_end; 572 | rootptr->left = buildTreeHelper(preorder, inorder, new_preorder_start_left, new_preorder_end_left, new_inorder_start_left, new_inorder_end_left); 573 | rootptr->right = buildTreeHelper(preorder, inorder, new_preorder_start_right, new_preorder_end_right, new_inorder_start_right, new_inorder_end_right); 574 | return rootptr; 575 | } 576 | 577 | TreeNode* buildTree(vector& preorder, vector& inorder) { 578 | return buildTreeHelper(preorder, inorder, 0, preorder.size(), 0, inorder.size()); 579 | } 580 | }; 581 | ``` 582 | 583 |

Now that we are comfortable with recursion, let us deal with a classic problem that a lot of companies like to ask in their interviews. This is Leetcode problem 236.

584 | 585 | 586 | 587 |

So how do we identify the Lowest Common Ancestor (LCA) of two nodes p and q? Well first let's ask the question how do we identify the nodes p and q? It is pretty straight-forward, we perform a preorder traversal of the tree. Once we do find p or q, we need to propogate the information that we found p or q up to the ancestors. The best way to do that would be to return the pointer to the node. When we move beyond a leaf, we just return the null pointer. So a null pointer would indicate that we have found neither p or q among its successors and a non-null pointer would indicate that we did find p or q. If the recursive call on the left subtree and the right subtree both return non-null pointers, then we have found the LCA and we propogate a pointer to the LCA upwards.

588 | 589 | ```cpp 590 | /** 591 | * Definition for a binary tree node. 592 | * struct TreeNode { 593 | * int val; 594 | * TreeNode *left; 595 | * TreeNode *right; 596 | * TreeNode(int x) : val(x), left(NULL), right(NULL) {} 597 | * }; 598 | */ 599 | class Solution { 600 | public: 601 | TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { 602 | if(root == nullptr) 603 | return nullptr; 604 | if(root == p || root == q) 605 | return root; 606 | else{ 607 | TreeNode* l = lowestCommonAncestor(root->left, p, q); 608 | TreeNode* r = lowestCommonAncestor(root->right, p , q); 609 | if(l == nullptr && r == nullptr) 610 | return nullptr; 611 | else if(l != nullptr && r == nullptr) 612 | return l; 613 | else if(l == nullptr && r != nullptr) 614 | return r; 615 | else 616 | return root; 617 | } 618 | } 619 | }; 620 | ``` 621 | ## Practice :muscle: 622 | 1. [Leetcode Problem 102: Binary Tree Level Order Traversal](https://leetcode.com/problems/binary-tree-level-order-traversal/) 623 | 2. [Leetcode Problem 144: Binary Tree Preorder Traversal](https://leetcode.com/problems/binary-tree-preorder-traversal/) 624 | 3. [Leetcode Problem 99: Binary Tree Inorder Traversal](https://leetcode.com/problems/binary-tree-inorder-traversal/) 625 | 4. [Leetcode Problem 145: Binary Tree Postorder Traversal](https://leetcode.com/problems/binary-tree-postorder-traversal/) 626 | 5. [Leetcode Problem 114: Flatten Binary Tree to Linked List](https://leetcode.com/problems/flatten-binary-tree-to-linked-list/) 627 | 628 | # Dynamic Programming 629 |

Dynamic Programming is a problem solving technique where we solve hard problems by building the solution from smaller ones while trying to satisfy some optimization criterion. There are two key components that make up a dynamic programming problem: overlapping subproblems and optimal substructure. This sounds a lot fancier than it really is. Overlapping subproblems means that the overall problem can be solved by breaking down the problem into sub-problems and the solution can be built up using the solutions to these sub-problems. The optimal sub-structure states that the solution to the sub-problems are optimal according to our optimization critera and the solution to the overall problem that is built using these solutions will also, thus, be optimal according to our optimization criterea. The core idea of Dynamic Programming is to avoid repeated work by remembering partial results and this concept finds it application in a lot of real life situations. Let us understand this technique using the examples shown below.

630 | 631 |

632 | 633 |

The two most important step while solving a DP problem is finding a recurrance relation i.e. what is the equation that relates the solution to a larger problem to the solutions of smaller subproblems and identifying the solutions to the base case, i.e. what is the solution to the problem in the simplest case. This serves as a starting point to build our solution. Once we identify these steps, it is easy to build the required solution ground up. Let us work with Leetcode problem 70.

634 | 635 |

636 | 637 |

Well, let us work through the two key steps when solving any DP problem. What would the recurrance relation be? Well to get to the ith step, we could either get to it from the i-1th step by climbing a single step or we could get to it from the i-2th step by climbing two steps. Since DP problems guarantee optimal sub-structure, the number of ways to get to the ith step is the sum of the number of ways to get to the i-1th step and the i-2th step. And for the base cases, the solution is quite intuitive. For the first step, there is exactly one way to get to it and for the second step, there are exactly two ways to get to it. Now we just build our solution from the ground up as shown in the code below! The time complexity of the algorithm is O(N) where N is the number of stairs.

638 | 639 | ```cpp 640 | class Solution { 641 | public: 642 | int climbStairs(int n) { 643 | if(n == 0 || n == 1) return n; 644 | vector dp(n+1, 0); 645 | dp[0] = 0; 646 | dp[1] = 1; 647 | dp[2] = 2; 648 | for(int i = 3; i <= n; i++){ 649 | dp[i] = dp[i-1] + dp[i-2]; 650 | } 651 | return dp[n]; 652 | } 653 | }; 654 | ``` 655 | 656 |

Using the dp array to store results in the previous example is known as tabulation. A data-structure (often a single or multi-dimensional vector/list) allows us the cache results to smaller sub-problems and build the solutions to the larger problems using these results. Let us take a look at an example of a problem with multi-dimensional tabulation. Consider Leetcode problem 62.

657 | 658 |

659 | 660 |

Once again, the two most important steps in DP! What is our recurrance relation? Well its very similar to the previous example, except now we are moving in two dimensions (i.e. right and down). So in order to get to a particular cell (i,j), we could get to it from (i-1, j) by moving one step down or we could get to it from (i, j-1) by moving one step to the right. So the number of ways to get to cell (i,j) would be the number of ways to get to cell (i-1,j) + the number of ways to get to cell (i,j-1). And what would the base cases be? The number of ways we can get to cells where we cannot apply this recurrance relation because we would go out of bounds i.e. the first row and the first column. Note that the number of ways to get to any cell in the first row would be 1 since we can get to it only from the cell to its left and the number of ways that we can get to any cell in the first column would be 1 since the only way to get to it would be from the cell above it.

661 | 662 | ```cpp 663 | class Solution { 664 | public: 665 | int uniquePaths(int m, int n) { 666 | vector> dp(m+1, vector(n+1)); 667 | for(int i = 0; i <= m; i++) dp[i][1] = 1; 668 | for(int i = 0; i <= n; i++) dp[1][i] = 1; 669 | for(int i = 2; i <= m; i++){ 670 | for(int j = 2; j <= n; j++){ 671 | dp[i][j] = dp[i-1][j] + dp[i][j-1]; 672 | } 673 | } 674 | return dp[m][n]; 675 | } 676 | }; 677 | ``` 678 | 679 |

The previous two problems demonstrated that dynamic programming can be used to count the number of ways to achieve a certain goal. Let us take a look at a special class of DP problems known as knapsack problems. Consider leetcode problem 416.

680 | 681 |

682 | 683 |

The main idea behind knapsack problems is that you have a target and you have to pick a subset of a set of elements that will help you achive the target. Sometimes, there is an optimization criteria, i.e. you would want to maximize or minimize some metric while reaching that target. If there isn't one, as in this case, it is known as a 0-1 Knapsack Problem. This problem is equivalent to asking the question, is there a subset of the numbers that sums up to half the total sum of all the numbers in the array. If there is, then it is implicit that the remaining half will sum to the total sum - half the total sum which is again half the total sum of all the numbers in the array. So the target is sum of the numbers / 2. Once we have established the target, we go through all the elements and check if there is a subset of these elements that sums up to k where 1 <= k <= target. The sub-structure here is that for each element that we add to the sub-set, we ask if adding it will make the numbers in the sub-set sum up to some k if we include it or not. The time complexity of this algorithm is O(NS) where S is the sum of the numbers in the vector and N is the size of the vector.

684 | 685 | ```cpp 686 | class Solution { 687 | public: 688 | bool canPartition(vector& nums) { 689 | int sum = 0; 690 | for(int i = 0; i < nums.size(); i++) sum += nums[i]; 691 | if(sum % 2 != 0) return false; 692 | int target = sum / 2; 693 | vector> dp(nums.size()+1, vector(target+1, false)); 694 | for(int i = 0; i <= nums.size(); i++) dp[i][0] = true; 695 | for(int i = 1; i <= nums.size(); i++){ 696 | for(int j = 1; j <= target; j++){ 697 | if(j-nums[i-1] >= 0){ 698 | dp[i][j] = dp[i-1][j-nums[i-1]] || dp[i-1][j]; 699 | } 700 | } 701 | } 702 | return dp[nums.size()][target]; 703 | } 704 | }; 705 | ``` 706 | 707 | ## Practice :muscle: 708 | 1. [Leetcode Problem 10: Regular Expression Matching](https://leetcode.com/problems/regular-expression-matching/) 709 | 2. [Leetcode Problem 1203: Sort Items By Groups Respecting Dependencies](https://leetcode.com/problems/sort-items-by-groups-respecting-dependencies/) 710 | 3. [Leetcode Problem 215: Kth Largest Element In An Array](https://leetcode.com/problems/kth-largest-element-in-an-array/) 711 | 4. [Leetcode Problem 23: Merge K Sorted Lists](https://leetcode.com/problems/merge-k-sorted-lists/) 712 | 5. [Leetcode Problem 210: Course Schedule II](https://leetcode.com/problems/course-schedule-ii/) 713 | 714 | # Topological and Heap Sort :palm_tree: 715 | ## Topological Sort :earth_americas: 716 |

Topological sort is a sorting technique that can be applied on elements with some precedence order associated with each element. There is, however, an important caveat concerned with topological sort: the elements which can be topologically sorted have form a Directed Acyclic Graph. We will examine Directed Acyclic Graphs in the upcoming section, but just so that you don't lose sight of our goal of finding a precedence order for elements, we can only determine precedence order only if there are no cyclic dependencies. For example, if we state that an element A comes before element B, and element B comes before element C then if A, B and C can be topologically sorted, C cannot come before A.

717 | 718 |

So what is a Directed Acyclic Graph? Well it's a graph that is directed and acyclic, i.e. all the edges of the graph are directed and there are no cycles in the graph. This is best illustrated by the following graph.

719 | 720 |

721 | 722 |

Notice that in the above graph all the edges are directed and there are no cycles. 4, 1, 3 and 5 do not form a cycle because the edge between 3 and 5 is opposite to the direction of the cycle. Great, we have established that the above graph is a DAG, but how does this relate to topological ordering? Well, if each directed edge from a node u to a node v implies that u "precedes" v, we can extract a topological ordering from the above graph. So for the above graph, one possible topological ordering would be 0 2 4 1 5 3 6. Let us call this ordering a. Notice that for any two elements ai and aj in the ordering such that i < j, if a directed edge exists in the graph between ai and aj, it has to be from ai to aj. We can never find an edge from aj to ai. Ofcourse, it is also possible that no edge exists between ai and aj, simply implying that ai doesn't have to precede aj and similarly aj doesn't have to precede ai.

723 | 724 |

Given a DAG, let us study the algorithm that is used for topological sort. The main idea behind this algorithm is to keep track of the nodes which have no incoming vertices. In a topological ordering, notice that the first element, 0, is a node with no incoming vertices in the DAG. Great! we figured out how to determine the first element! Now what? Well, let us remove 0 from the DAG. Make sure to remove the outgoing edge from 0 as well. Now, let us observe the node with no incoming vertices. It is 2! And just like that, we have determined the second node in the graph. This is no coincidence! we keep doing that until we run out of nodes in the DAG: removing the node with 0 incoming vertices and then updating the list of nodes with no incoming vertices until we empty the graph. If at any point we have run out of nodes with no incoming vertices but we still have nodes left over in the graph, then a cycle must exist and the nodes of the DAG cannot be topologically sorted.

725 | 726 |

Now that we know the algorithm, let's try implementing the algorithm to solve a problem. Consider Leetcode problem 207.

727 | 728 |

729 | 730 |

For this problem we use topological sort in order to determine if the course ordering is valid. We can think of a course ordering as a directed graph where each node is a course. There is a node pointing from node A to a node B if A is a pre-requisite of B. If a course schedule is valid then the graph formed must be a DAG so that we can take one course at a time without running into a cyclic dependency. We determine the topological ordering and if one can be determined successfully we know the given course schedule forms a DAG. We use the standard algorithm for determining a topological ordering from a DAG to solve the problem.

731 | 732 | ## Heap Sort :milky_way: 733 |

Now on to heap sort. Well as the name suggests, Heap Sort is a sorting algorithm that employs heaps. Heaps as we all know from CS32 is a data structure that is used to maintain a particular order in elements, most commonly, in the form of a min heap or a max heap. A min heap maintains the elements in an order such that the minimum element is the topmost element of the heap, and every element is less than that its children (if children exist). Heap sort, just like any sorting algorithm is bounded by O(NlogN) time complexity. In C++ a heap is used to implement a priority queue. A priority queue can be used to maintain a custom ordering of just about anything. We would have to declare a custom comparision class in order to do so but this is often easier than it sounds as we will see in the upcoming examples.

734 | 735 |

736 | 737 |

So the way heap sort would work would be to arrange all the elements to be sorted in a heap and then keep popping the topmost element until there are no remaining elements. The order in which the elements are popped is the heap sort ordering! While heap sort achieves what any other equally efficient algorithm like merge sort or pivot sort would achieve, the heap data structure itself (priority queue in C++) can be very useful in several interview problems. Consider Leetcode problem 347.

738 | 739 | ```cpp 740 | class Solution { 741 | public: 742 | bool canFinish(int numCourses, vector>& prerequisites) { 743 | int input_degree[100000] = {0}; 744 | unordered_map> edges; 745 | queue proc; 746 | vector order; 747 | for(auto prereq : prerequisites){ 748 | input_degree[prereq[0]] += 1; 749 | edges[prereq[1]].push_back(prereq[0]); 750 | } 751 | for(int i = 0; i < numCourses; i++){ 752 | if(input_degree[i] == 0) proc.push(i); 753 | } 754 | while(proc.size()){ 755 | int temp = proc.front(); 756 | proc.pop(); 757 | for(int i = 0; i < edges[temp].size(); i++){ 758 | input_degree[edges[temp][i]] -= 1; 759 | if(input_degree[edges[temp][i]] == 0) proc.push(edges[temp][i]); 760 | } 761 | order.push_back(temp); 762 | } 763 | return order.size() == numCourses; 764 | } 765 | }; 766 | ``` 767 | 768 |

769 | 770 |

We solve this problem by making use of a heap. The problem asks us to do better than O(NlogN) time complexity so we achieve a time complexity of O(NlogK) using a priority queue which stores the K most frequent elements. We maintain the element value and its frequency in a pair. We spend O(N) time constructing the unordered map to store element, frequency key-value pairs in order to construct our pair easily. Then we insert pairs into the priority queue while maintaining the restriction that there can only be K elements in the priority queue. As promised we write a custom comparision struct that our heap will use to order the pairs. The second element in the pair is the frequeuncy of the first element. Once we are done passing through all pairs, the elements remaining in our priority queue are the our top K frequent elements in the vector nums. Note that we have to define a custom comparator for the priority queue in order to compare pairs.

771 | 772 | ```cpp 773 | class Solution { 774 | public: 775 | struct CustomCompare { 776 | bool operator()(pair n1, pair n2) 777 | { 778 | return n1.second > n2.second; 779 | } 780 | }; 781 | vector topKFrequent(vector& nums, int k) { 782 | vector result; 783 | unordered_map counts; 784 | for(int i = 0; i < nums.size(); i++) counts[nums[i]] = 0; 785 | for(int i = 0; i < nums.size(); i++) counts[nums[i]] += 1; 786 | priority_queue, vector>, CustomCompare> Q; 787 | for(auto iter : counts){ 788 | if(Q.size() < k){ 789 | Q.push(iter); 790 | }else{ 791 | pair n1 = Q.top(); 792 | if(counts[n1.first] < counts[iter.first]){ 793 | Q.pop(); 794 | Q.push(iter); 795 | } 796 | } 797 | } 798 | while(Q.size()){ 799 | result.push_back(Q.top().first); 800 | Q.pop(); 801 | } 802 | return result; 803 | } 804 | }; 805 | ``` 806 | 807 |

Lets tackle Leetcode problem 659 using heaps. While the use of heaps to solve this problem is not optimal, it does allow us to get creative with using heaps to solve problems. Try working out the more optimal greedy solution on your own!

808 | 809 |

810 | 811 |

We maintain a priority queue for each number nums[i] that appears in the vector nums passed into the function. The priority queue will contain all the continuous increasing subsequences encountered thus far ending with the number nums[i] and will be ordered in increasing order of size (i.e. fewer the elements in the subsequence, the higher up in the priority queue). So we need a custom comparision struct to order the subsequences. That way, for any element nums[i] that we encounter in the vector nums, we can append it to the shortest consecutive increasing subsequence that ends with the number nums[i]-1. If there is no such subsequence, we create a new one with the first element being nums[i]. The time complexity for this solution is O(NlogN) where N is the number of distinct elements in the vector nums.

812 | 813 | ```cpp 814 | class Solution { 815 | public: 816 | struct CustomCompare { 817 | bool operator()(vector* v1, vector* v2) 818 | { 819 | return v1->size() > v2->size(); 820 | } 821 | }; 822 | 823 | bool isPossible(vector& nums) { 824 | unordered_map*, vector*>, CustomCompare>> heaps; 825 | for(int i = 0; i < nums.size(); i++){ 826 | if(heaps.find(nums[i]-1) == heaps.end() || heaps[nums[i]-1].size() == 0){ 827 | vector* v = new vector; 828 | v->push_back(nums[i]); 829 | heaps[nums[i]].push(v); 830 | }else{ 831 | vector* v = heaps[nums[i]-1].top(); 832 | heaps[nums[i]-1].pop(); 833 | v->push_back(nums[i]); 834 | heaps[nums[i]].push(v); 835 | } 836 | } 837 | bool is_valid = true; 838 | for(auto it : heaps){ 839 | if(it.second.size() != 0){ 840 | if(it.second.top()->size() < 3){ 841 | is_valid = false; 842 | break; 843 | } 844 | } 845 | } 846 | return is_valid; 847 | } 848 | }; 849 | ``` 850 | 851 | ## Practice :muscle: 852 | 1. [Leetcode Problem 329: Longest Increasing Path In A Matrix](https://leetcode.com/problems/longest-increasing-path-in-a-matrix/) 853 | 2. [Leetcode Problem 1203: Sort Items By Groups Respecting Dependencies](https://leetcode.com/problems/sort-items-by-groups-respecting-dependencies/) 854 | 3. [Leetcode Problem 215: Kth Largest Element In An Array](https://leetcode.com/problems/kth-largest-element-in-an-array/) 855 | 4. [Leetcode Problem 23: Merge K Sorted Lists](https://leetcode.com/problems/merge-k-sorted-lists/) 856 | 5. [Leetcode Problem 210: Course Schedule II](https://leetcode.com/problems/course-schedule-ii/) 857 | -------------------------------------------------------------------------------- /Solutions/Arrays/Problem_41_first_missing_number.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | The main idea behind this solution is that it is guaranteed that the 3 | first missing element will lie between 1 and n inclusive where n is 4 | the size of the vector nums. We iterate through the array and swap the 5 | the number i into the index i-1 if and only if the number i is between 6 | 1 and n inclusive. We are guaranteed that the inner while loop will not 7 | run infinitely because there can be at most n-1 swaps until the we encounter 8 | an element in nums[i] which is already in the index nums[nums[i] - 1]. 9 | */ 10 | class Solution 11 | { 12 | public: 13 | int firstMissingPositive(vector& nums) 14 | { 15 | int curr = 1; 16 | for(int i = 0; i < nums.size(); i++){ 17 | while(nums[i] <= nums.size() && nums[i] > 0 && nums[i] != nums[nums[i] - 1]){ 18 | swap(nums[i], nums[nums[i]-1]); 19 | } 20 | } 21 | 22 | for(int i = 0; i < nums.size(); i++){ 23 | if(nums[i] != i+1){ 24 | return i + 1; 25 | } 26 | } 27 | 28 | return nums.size() + 1; 29 | } 30 | }; -------------------------------------------------------------------------------- /Solutions/Arrays/Problem_42_trapping_rain_water.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Let H be the highest wall in the sequence. 3 | We can divide the problem into 3 parts. 4 | 1. Find the amount of water trapped to the left of the first wall of height H. 5 | The amount of water trapped by a given wall be the difference between the maximum heighted wall 6 | to its left (including itself) and the height of the wall. 7 | 2. Find the amount of water trapped between the left-most wall of height H and the right-most wall 8 | of height H. If there is only one wall of height H we skip this part of the problem. The amount of 9 | water trapped by each wall in this part will be the difference between H and the height of the current wall. 10 | 3. Find the amount of water trapped to the right of the right-most wall of height H. The amount of water 11 | trapped by a given wall in this part will be the difference between the maximum heighted wall to its left 12 | (including itself) and the height of the wall. 13 | */ 14 | class Solution { 15 | public: 16 | int trap(vector& height) { 17 | int left_max_wall_index = 0; //index of the left-most wall of height H. 18 | int right_max_wall_index = 0; //index of the right-most wall of height H. 19 | for(int i = 0; i < height.size(); i++){ 20 | if(height[i] >= height[right_max_wall_index]){ 21 | right_max_wall_index = i; 22 | } 23 | } 24 | for(int i = 0; i < height.size(); i++){ 25 | if(height[i] == height[right_max_wall_index]){ 26 | left_max_wall_index = i; 27 | break; 28 | } 29 | } 30 | int rain_water_level = 0; 31 | int max_wall_so_far = 0; 32 | 33 | for(int i = 0; i < left_max_wall_index; i++){ 34 | if(height[i] > max_wall_so_far){ 35 | max_wall_so_far = height[i]; 36 | } 37 | rain_water_level += max_wall_so_far - height[i]; 38 | } 39 | 40 | for(int i = left_max_wall_index; i < right_max_wall_index; i++){ 41 | rain_water_level += height[left_max_wall_index] - height[i]; 42 | } 43 | 44 | max_wall_so_far = 0; 45 | 46 | for(int i = height.size()-1; i > right_max_wall_index; i--){ 47 | if(height[i] > max_wall_so_far){ 48 | max_wall_so_far = height[i]; 49 | } 50 | rain_water_level += max_wall_so_far - height[i]; 51 | } 52 | 53 | return rain_water_level; 54 | } 55 | }; -------------------------------------------------------------------------------- /Solutions/Arrays/Problem_448_find_all_numbers_disappeared_in_an_array.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | UCLA ACM ICPC: Interview Track Leetcode Problem Solutions 3 | 4 | Disclaimer: This is not the only valid solution and we do not claim our solutions 5 | to be optimal in terms of runtime or memory usage, we are merely giving example 6 | solutions to questions for learning purposes only 7 | 8 | Find All Numbers Disappeared in an Array 9 | Leetcode Problem 448 10 | https://leetcode.com/problems/find-all-numbers-disappeared-in-an-array/ 11 | */ 12 | 13 | // Given an array of integers where 1 ≤ a[i] ≤ n (n = size of array), 14 | // some elementsappear twice and others appear once. 15 | // Find all the elements of [1, n] inclusive that do not appear in this array. 16 | class Solution { 17 | public: 18 | // We want to solve this problem without using extra space and in O(N) 19 | // runtime, since a O(N) time and O(N) space complexity can be easily 20 | // solved with a hash set. 21 | 22 | // For each number we encounter in the array, we want to somehow mark that we 23 | // have already seen this number. Since the integers in the array range from 24 | // 1 to n, we can use array indexes. 25 | vector findDisappearedNumbers(vector& nums) { 26 | vector result; 27 | 28 | // Our stretegy is for each number X we encounter, we can show that 29 | // we have seen this number by marking nums at index nums[X]-1 (We 30 | // subtract 1 since the X range from 1 to N). However, we want to 31 | // mark nums[nums[i]-1] in a way that preserves the value at 32 | // nums[nums[X]-1]. We do this be making nums[nums[X]-1] a 33 | // negative value 34 | for(int i = 0; i < nums.size(); i++) { 35 | // For each integer X in range 0 to N-1, it's not guaranteed that 36 | // nums[X] is positive since we manipulate the sign of array elements 37 | // in the loop. Therefore, every time we get the value at nums[X], 38 | // we take the absolute value of it. 39 | nums[abs(nums[i])-1] = -abs(nums[abs(nums[i])-1]); 40 | } 41 | 42 | // For each index i, if nums[i] is unmarked (positive), then 43 | // we know that we have not seen the number i+1 in the array 44 | // (We add 1 since i ranges from 0 to N-1). 45 | for(int i = 0; i < nums.size(); i++) { 46 | if(nums[i] > 0) result.push_back(i + 1); 47 | } 48 | 49 | return result; 50 | } 51 | }; -------------------------------------------------------------------------------- /Solutions/Arrays/Problem_667_Beautiful_Arrangement_ii.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | The main idea behind this solution is to vary the difference by 1 for k/2 pairs and then 3 | append the remaining numbers because we don't care about them having a unique difference. 4 | So we would group 5 | 1, 1+k, 2, 2+k-2, 3, 3+k-4,...,1+k+1,1+k+2,...n. 6 | So the differences between successive pairs will be 7 | k, k-1, k-2, k-3...1, 1, 1,...1. 8 | In order to keep track of the numbers that have been inserted into our result we use an 9 | unordered map. 10 | */ 11 | class Solution { 12 | public: 13 | vector constructArray(int n, int k) { 14 | vector res; 15 | unordered_map inserted; 16 | int i = 1; 17 | int temp = k; 18 | while(k > 0){ 19 | res.push_back(i); 20 | res.push_back(i+k); 21 | inserted[i] = true; 22 | inserted[i+k] = true; 23 | k-=2; 24 | i+=1; 25 | } 26 | for(int j = 1; j <= n; j++){ 27 | if(inserted.find(j) == inserted.end()) 28 | res.push_back(j); 29 | } 30 | return res; 31 | } 32 | }; -------------------------------------------------------------------------------- /Solutions/Arrays/Problem_904_fruit_into_baskets.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | UCLA ACM ICPC: Interview Track Leetcode Problem Solutions 3 | 4 | Disclaimer: This is not the only valid solution and we do not claim our solutions 5 | to be optimal in terms of runtime or memory usage, we are merely giving example 6 | solutions to questions for learning purposes only 7 | 8 | Fruit Into Baskets 9 | Leetcode Problem 904 10 | https://leetcode.com/problems/fruit-into-baskets/ 11 | */ 12 | 13 | // In a row of trees, the i-th tree produces fruit with type tree[i]. 14 | 15 | // You start at any tree of your choice, then repeatedly perform the 16 | // following steps: 17 | 18 | // Add one piece of fruit from this tree to your baskets. If you cannot, stop. 19 | 20 | // Move to the next tree to the right of the current tree. If there is no 21 | // tree to the right, stop. 22 | 23 | // Note that you do not have any choice after the initial choice of starting tree: 24 | 25 | // you must perform step 1, then step 2, then back to step 1, then step 2, and so 26 | // on until you stop. 27 | 28 | // You have two baskets, and each basket can carry any quantity of fruit, but you 29 | // want each basket to only carry one type of fruit each. 30 | 31 | // What is the total amount of fruit you can collect with this procedure? 32 | class Solution { 33 | public: 34 | // We want to solve this problem in O(N) time because an O(N^2) time solution 35 | // is trivial. The optimal solution probably won't in O(N logN) because the 36 | // nature of this problem won't let us rearrange the fruits. 37 | 38 | // One way to solve this problem is to start at the index 0 and begin picking 39 | // up fruit with your two baskets. When you come across a third fruit, you want 40 | // to empty the appropriate basket to make room for the third fruit. All the 41 | // while, you are keeping track of the total amount of fruits you have in the 42 | // baskets at one time, and when you reach the end, you return the max number. 43 | int totalFruit(vector& tree) { 44 | // Represents the largest amount of fruits 45 | // you've had in your two baskets at one time. 46 | int maxFruits = 1; 47 | 48 | // A hash map that maps a partifular fruit to 49 | // the last index that we encounted that fruit 50 | unordered_map map; 51 | 52 | // Two pointers. At the end of each loop iteration, 53 | // we will have a valid sequence of fruits which we 54 | // can put into two baskets. `i` represents the start 55 | // of that sequence, and `j` represents the first index 56 | // not in the sequence 57 | int i = 0, j = 0; 58 | 59 | while(j < tree.size()) { 60 | // Recall that `tree[j]` is a fruit, which we want to map 61 | // to the last index that we encountered it, which in 62 | // this case is `j`. 63 | map[tree[j]] = j; 64 | 65 | // We want `j` to represent the first index not in the 66 | // sequence, so we increment `j` 67 | j++; 68 | 69 | // `map.size() > 2` means we have encountered a third 70 | // fruit that doesn't fit into our two baskets. 71 | if(map.size() > 2) { 72 | // We need to decide which basket to empty out. 73 | // Since we want our sequence of fruits to be 74 | // continuous, we want to empty the basket that 75 | // contains the fruit which we have not seen 76 | // the longest. 77 | 78 | // Example: Suppose we have fruits A and B in 79 | // our baskets and we encounter fruit C. 80 | 81 | // A A A B C -> Empty basket with A because we 82 | // have seen B more recently than A. Our new 83 | // sequence is B C 84 | 85 | // B A A A C -> Empty basket with B because we 86 | // have seen A more recently than B. Our new 87 | // sequence is A A A C 88 | 89 | // We do this by removing the fruit in our hash 90 | // map that contains the smallest index. 91 | int minIndex = tree.size() - 1; 92 | for(auto k = map.begin(); k != map.end(); k++) { 93 | int index = k->second; 94 | minIndex = min(minIndex, index); 95 | } 96 | map.erase(tree[minIndex]); 97 | 98 | // We update `i` to make it the start of our 99 | // new sequence. That value is `minIndex + 1` 100 | // since `minIndex` is the last index before 101 | // the sequence that is not in it. 102 | i = minIndex + 1; 103 | } 104 | 105 | // `j - i` represents the length of our current sequence 106 | // of fruits. If it's longer than our maximum sequence 107 | // length, update the `maxFruits` variable 108 | maxFruits = max(maxFruits, j - i); 109 | } 110 | 111 | return maxFruits; 112 | } 113 | }; -------------------------------------------------------------------------------- /Solutions/Dynamic-Programming/Problem_10_Regular_Expression_Matching.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | bool isMatch(string s, string p) { 4 | int s_len = s.size(); 5 | int p_len = p.size(); 6 | vector> dp(s_len+1, vector(p_len+1, false)); 7 | dp[0][0] = true; // when we don't consider any of the characters of the two strings. 8 | for(int i = 0; i <= s_len; i++){ 9 | for(int j = 1; j <= p_len; j++){ 10 | if(p[j-1] == '*'){ // The previous chracter was a *. 11 | dp[i][j] = dp[i][j-2] || (i && dp[i-1][j] && (s[i-1] == p[j-2] || p[j-2] == '.')); 12 | }else{ // The previous character was a . or a letter. 13 | dp[i][j] = i && dp[i-1][j-1] && (( s[i-1] == p[j-1]) || p[j-1] == '.'); 14 | } 15 | } 16 | } 17 | return dp[s_len][p_len]; 18 | } 19 | }; -------------------------------------------------------------------------------- /Solutions/Dynamic-Programming/Problem_121_Best_Time_To_Buy_And_Sell_Stock.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | UCLA ACM ICPC: Interview Track Leetcode Problem Solutions 3 | 4 | Disclaimer: This is not the only valid solution and we do not claim our solutions 5 | to be optimal in terms of runtime or memory usage, we are merely giving example 6 | solutions to questions for learning purposes only 7 | 8 | Best Time to Buy and Sell Stock 9 | Leetcode Problem 121 10 | https://leetcode.com/problems/best-time-to-buy-and-sell-stock/ 11 | */ 12 | 13 | // Say you have an array for which the ith element is the price of a given stock on day i. 14 | 15 | // If you were only permitted to complete at most one transaction (i.e., buy one and sell 16 | // one share of the stock), design an algorithm to find the maximum profit. 17 | 18 | // Note that you cannot sell a stock before you buy one. 19 | 20 | class Solution { 21 | public: 22 | // We will solve this problem by checking 23 | // the max profit we can make if we sell on 24 | // day 1, day 2, ..., and day N. The answer 25 | // will be the max of these values. 26 | 27 | // Intuition: If we're guaranteed to sell on 28 | // day i, then we must buy before or on day i. 29 | // The day we choose to buy must be the minimum 30 | // price before or on day i 31 | int maxProfit(vector& prices) { 32 | // `min` represents the minimum value we 33 | // have encountered so far. We keep this 34 | // variable so that we don't have to 35 | // continuously calculate the minimum 36 | // value before day i. 37 | int min = INT_MAX; 38 | 39 | // `max` represents the maximum profit 40 | // we can make completing one transaction 41 | // using the values we have seen so far. 42 | // We initially set it to 0 because we'll 43 | // never make a transaction that results 44 | // in a negative profit. 45 | int max = 0; 46 | 47 | for(int i = 0; i < prices.size(); i++) { 48 | 49 | // Update `min` if necessary 50 | if(prices[i] < min) min = prices[i]; 51 | 52 | // Update `max` if necessary. This involves comparing 53 | // our current `max` with a new tranaction: Buying 54 | // today and selling on the day where that price of 55 | // the stock is `min` 56 | if(prices[i] - min > max) max = prices[i] - min; 57 | } 58 | 59 | // After looping through the entire vector, `max` 60 | // represents the maximum profit we can make completing 61 | // one transaction using the values in the vector 62 | return max; 63 | } 64 | }; 65 | 66 | // Time Complexity: O(n) - We traverse through the vector once. 67 | // Space Complexity: O(1) - We only store useful information in two variables. -------------------------------------------------------------------------------- /Solutions/Dynamic-Programming/Problem_32_Longest_Valid_Parenthesis.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int longestValidParentheses(string s) { 4 | stack parens; 5 | vector dp(s.length(), 0); 6 | for(int i = 0; i < s.length(); i++){ 7 | if(s[i] == '('){ 8 | parens.push(i); 9 | }else{ 10 | if(parens.size()){ 11 | int j = parens.top(); 12 | if(j != 0){ 13 | dp[i] = dp[i-1] + dp[j-1] + 2; 14 | }else{ 15 | dp[i] = dp[i-1] + 2; 16 | } 17 | parens.pop(); 18 | } 19 | } 20 | } 21 | int max_substring = 0; 22 | for(int i = 0; i < s.length(); i++) max_substring = max(max_substring, dp[i]); 23 | return max_substring; 24 | } 25 | }; -------------------------------------------------------------------------------- /Solutions/Dynamic-Programming/Problem_64_Minimum_Path_Sum.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int minPathSum(vector>& grid) { 4 | for(int i = 1; i < grid.size(); i++) grid[i][0] += grid[i-1][0]; 5 | for(int j = 1; j < grid[0].size(); j++) grid[0][j] += grid[0][j-1]; 6 | for(int i = 1; i < grid.size(); i++){ 7 | for(int j = 1; j < grid[0].size(); j++){ 8 | grid[i][j] += min(grid[i-1][j],grid[i][j-1]); 9 | } 10 | } 11 | return grid[grid.size()-1][grid[0].size()-1]; 12 | } 13 | }; -------------------------------------------------------------------------------- /Solutions/Dynamic-Programming/Problem_91_Decode_Ways.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | UCLA ACM ICPC: Interview Track Leetcode Problem Solutions 3 | 4 | Disclaimer: This is not the only valid solution and we do not claim our solutions 5 | to be optimal in terms of runtime or memory usage, we are merely giving example 6 | solutions to questions for learning purposes only 7 | 8 | Decode Ways 9 | Leetcode Problem 91 10 | https://leetcode.com/problems/decode-ways/ 11 | */ 12 | 13 | // A message containing letters from A-Z is being encoded to numbers 14 | // using the following mapping: 15 | 16 | // 'A' -> 1 17 | // 'B' -> 2 18 | // ... 19 | // 'Z' -> 26 20 | 21 | // Given a non-empty string containing only digits, determine the 22 | // total number of ways to decode it. 23 | 24 | class Solution { 25 | public: 26 | // Base case: If the string length is 1, then there's one way to 27 | // decode the string iff the string is in between 1 - 9 28 | // Base case: If the string length is 2, then there's one way to 29 | // decode the string iff the string is in between 10 - 26 30 | // Recursive case: If the string length is N, then the string can 31 | // be split into two cases: 32 | // The first (N-1) characters + the last character 33 | // The first (N-2) characters + the last two characters 34 | // If the last character can be decoded, then we add the number 35 | // of ways to decode the first (N-1) characters to our result 36 | // If the last two character can be decoded, then we add the number 37 | // of ways to decode the first (N-2) characters to our result 38 | int numDecodings(string s) { 39 | 40 | // Instead of recursion, we will use dynamic programming for 41 | // optimization purposes. Given a string of length N, we need 42 | // the subsolutions for the N-1 and N-2 case, so we only need 43 | // to allocate two variables for this solution. 44 | 45 | // Given a number i... 46 | // let X be the subsolution for the i-1 case 47 | // Let Y be the subsolution for the i case 48 | 49 | // Pretend that this is our first iteration (i=1) 50 | // x represents the number of ways to decode nothing 51 | // y represents the number of ways to decode the first character 52 | //////////////////////////////////////////////////////////////// 53 | // There is 1 way to decode nothing 54 | int x = 1; 55 | // Then there is 1 way to decode the first character 56 | // if it is nonzero, and 0 otherwise 57 | int y = s[0] != '0' ? 1 : 0; 58 | 59 | for(int i = 2; i <= s.size(); i++) { 60 | int z = 0; 61 | 62 | // first represents the i'th character 63 | // second represents the (i-1)'th and i'th character 64 | int first = stoi(s.substr(i-1, 1)); 65 | int second = stoi(s.substr(i-2, 2)); 66 | 67 | // If the i'th character can be decoded by itself, then 68 | // we want to add the number of ways to decode the 69 | // first (i-1) characters 70 | if(first >= 1 && first <= 9) z += y; 71 | 72 | // If the last two characters can be decoded by itself, 73 | // then we want to add the number of ways to decode the 74 | // first (i-2) characters 75 | if(second >= 10 && second <= 26) z += x; 76 | 77 | // Update variables: 78 | x = y; // x now represents the number of ways to decode 79 | // the first (i-1) characters 80 | y = z; // y now represents the number of ways to decode 81 | // the first i characters 82 | } 83 | 84 | // By the end of the loop, y represents the 85 | // number of ways to decode the entire string 86 | return y; 87 | } 88 | }; 89 | 90 | // Time Complexity: O(n) - We traverse through the string once. 91 | // Space Complexity: O(1) - We only store useful information in 92 | // a constant number variables. -------------------------------------------------------------------------------- /Solutions/Dynamic-Programming/Problem_97_Interleaving_String.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | UCLA ACM ICPC: Interview Track Leetcode Problem Solutions 3 | 4 | Disclaimer: This is not the only valid solution and we do not claim our solutions 5 | to be optimal in terms of runtime or memory usage, we are merely giving example 6 | solutions to questions for learning purposes only 7 | 8 | Interleaving String 9 | Leetcode Problem 97 10 | https://leetcode.com/problems/interleaving-string/ 11 | */ 12 | 13 | // Given s1, s2, s3, find whether s3 is formed by the interleaving of s1 and s2. 14 | 15 | class Solution { 16 | public: 17 | // We will solve this problem using dynamic programming 18 | bool isInterleave(string s1, string s2, string s3) { 19 | // If the size of s3 isn't the num of the sizes of 20 | // s1 and s2, we can just stop here and return false 21 | if(s3.size() != s1.size() + s2.size()) return false; 22 | 23 | // Set up out DP vector. 24 | // DP[i][j] represents whether it is the first i 25 | // characters of s1 and the first j characters of 26 | // s2 interleave the first (i+j) characters of s3 27 | vector> dp(s1.size() + 1); 28 | for(int i = 0; i < s1.size() + 1; i++) { 29 | for(int j = 0; j < s2.size() + 1; j++) { 30 | dp[i].push_back(false); 31 | } 32 | } 33 | 34 | for(int i=0; i < s1.length() + 1; i++) { 35 | for(int j=0; j < s2.length() + 1; j++) { 36 | 37 | // Base case: Trivially, the first 0 characters of s1 and 38 | // s2 always interleave the first 0 characters of s3 39 | if(i==0 && j==0) 40 | dp[i][j] = true; 41 | 42 | // Recusrive case: The first i characters of s1 and the 43 | // first j characters of s2 always interleave the first 44 | // (i+j) characters of s3 if and only if... 45 | 46 | // The first (i-1) characters of s1 and the first j 47 | // characters of s2 interleave the first (i+j-1) characters 48 | // of s3, and the i-th character of s1 is the same as 49 | // the (i+j)-th character of s3 50 | 51 | // OR 52 | 53 | // The first i characters of s1 and the first (j-1) 54 | // characters of s2 interleave the first (i+j-1) characters 55 | // of s3, and the j-th character of s2 is the same as 56 | // the (i+j)-th character of s3 57 | 58 | else if(i == 0) 59 | dp[i][j] = (dp[i][j-1] && s2[j-1] == s3[i+j-1]); 60 | else if(j == 0) 61 | dp[i][j] = (dp[i-1][j] && s1[i-1] == s3[i+j-1]); 62 | else 63 | dp[i][j] = (dp[i-1][j] && s1[i-1] == s3[i+j-1]) || 64 | (dp[i][j-1] && s2[j-1] == s3[i+j-1]); 65 | } 66 | } 67 | 68 | // Return the value that represents whether the characters of s1 69 | // and the characters of s2 interleave the characters of s3 70 | return dp[s1.length()][s2.length()]; 71 | } 72 | }; 73 | 74 | // Time Complexity: O(N * M), where N is the size of s1, and M is the size 75 | // of s2. We have a nested loop where we do M conputations N times. 76 | // Space Complexity: O(N * M), where N is the size of s1, and M is the size 77 | // of s2. We make a N * M vector to solve the dynamic programming problem -------------------------------------------------------------------------------- /Solutions/Linked-Lists/Problem_138_copy_list_with_random_pointer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | UCLA ACM ICPC: Interview Track Leetcode Problem Solutions 3 | 4 | Disclaimer: This is not the only valid solution and we do not claim our solutions 5 | to be optimal in terms of runtime or memory usage, we are merely giving example 6 | solutions to questions for learning purposes only 7 | 8 | Copy List with Random Pointer 9 | Leetcode Problem 138 10 | https://leetcode.com/problems/copy-list-with-random-pointer/ 11 | */ 12 | 13 | // A linked list is given such that each node contains an additional 14 | // random pointer which could point to any node in the list or null. 15 | 16 | // Return a deep copy of the list. 17 | 18 | // The Linked List is represented in the input/output as a list of n nodes. 19 | // Each node is represented as a pair of [val, random_index] where: 20 | // val: an integer representing Node.val 21 | // random_index: the index of the node (range from 0 to n-1) where random 22 | // pointer points to, or null if it does not point to any node. 23 | 24 | /* 25 | // Definition for a Node. 26 | class Node { 27 | public: 28 | int val; 29 | Node* next; 30 | Node* random; 31 | 32 | Node(int _val) { 33 | val = _val; 34 | next = NULL; 35 | random = NULL; 36 | } 37 | }; 38 | */ 39 | 40 | class Solution { 41 | public: 42 | // Our strategy will be to create a copy of the list within the given 43 | // list, and then separate the two at the end. 44 | // Example: 45 | 46 | // Given: 1 -> 2 -> 3 -> 4 47 | // Copy: 1 -> 1' -> 2 -> 2' -> 3 -> 3' -> 4 -> 4' 48 | // Separate: 1 -> 2 -> 3 -> 4 49 | // 1' -> 2' -> 3' -> 4' 50 | Node* copyRandomList(Node* head) { 51 | // Base case check 52 | if(head == NULL) return NULL; 53 | 54 | // Create a copy of the list within the given list, 55 | // ignoring the values of the random pointers 56 | Node* cur = head; 57 | while(cur != NULL) { 58 | // Suppose cur is node A 59 | // ... -> A -> B -> ... 60 | Node* next = cur->next; 61 | // ... -> A -> A' | B -> ... 62 | cur->next = new Node(cur->val); 63 | // ... -> A -> A' -> B -> ... 64 | cur->next->next = next; 65 | // cur is now node B 66 | cur = next; 67 | } 68 | 69 | // Add the values of the random pointers to 70 | // the duplicate elements of the list 71 | cur = head; 72 | while(cur != NULL) { 73 | // Suppose cur is node A 74 | // ... -> A -> A' -> B -> B'... 75 | if(cur->random != NULL) 76 | // Suppose A->random = C. This 77 | // instruction makes A'->random = C' 78 | cur->next->random = cur->random->next; 79 | // cur is now node B. 80 | cur = cur->next->next; 81 | } 82 | 83 | // Separate the duplicates from the original list 84 | cur = head; 85 | // The head of the copy 86 | Node* copyHead = head->next; 87 | Node* copy = copyHead; 88 | while(copy->next != NULL) { 89 | // Suppose cur is node A 90 | // ... -> A -> A' -> B -> B' -> ... 91 | // A now points to B 92 | cur->next = cur->next->next; 93 | // cur now equals B 94 | cur = cur->next; 95 | 96 | // A' now points to B' 97 | copy->next = copy->next->next; 98 | // copy now equals B' 99 | copy = copy->next; 100 | } 101 | 102 | // The last node (Z) still points to it's copy, 103 | // so we must make it so it doesn't point to anything 104 | // ... -> Z -> Z' 105 | cur->next = NULL; 106 | // ... -> Z 107 | return copyHead; 108 | } 109 | }; -------------------------------------------------------------------------------- /Solutions/Linked-Lists/Problem_142_Linked_List_Cycle_ii.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | UCLA ACM ICPC: Interview Track Leetcode Problem Solutions 3 | 4 | Disclaimer: This is not the only valid solution and we do not claim our solutions 5 | to be optimal in terms of runtime or memory usage, we are merely giving example 6 | solutions to questions for learning purposes only 7 | 8 | Linked List Cycle ii 9 | Leetcode Problem 142 10 | https://leetcode.com/problems/linked-list-cycle-ii/ 11 | */ 12 | 13 | /** 14 | * Definition for singly-linked list. 15 | * struct ListNode { 16 | * int val; 17 | * ListNode *next; 18 | * ListNode(int x) : val(x), next(NULL) {} 19 | * }; 20 | */ 21 | 22 | class Solution { 23 | public: 24 | ListNode *detectCycle(ListNode *head) { 25 | //if the head is null or the linked list contains a single node, then 26 | //we cannot have a cycle. 27 | if(head == nullptr || head->next == nullptr) 28 | return nullptr; 29 | 30 | //we start out with a slow pointer and a fast pointer. 31 | //The slow pointer moves one node every iteration while the fast pointer 32 | //moves two nodes in every iteration. When the two meet, we know that there 33 | //exists a cycle. 34 | ListNode* slow = head; 35 | ListNode* fast = head; 36 | while(slow != nullptr && fast != nullptr){ 37 | slow = slow->next; 38 | fast = fast->next; 39 | if(fast != nullptr) 40 | fast = fast->next; 41 | else 42 | break; 43 | if(slow == fast) 44 | break; 45 | } 46 | 47 | //no cycle exists 48 | if(slow == nullptr || fast == nullptr) 49 | return nullptr; 50 | 51 | //The node that the two pointer meets is at a point in the cycle 52 | //such that it is k places away from the first node in the cycle (from observation). 53 | //The head node is also k places away from the first node in the cycle. 54 | //So we start a pointer at the head and advace it as we advace the meeting point node pointer 55 | //one node at a time and break when the two meet (i.e. the node at the start of the linked list). 56 | ListNode* start = head; 57 | while(start != slow) { 58 | start = start->next; 59 | slow = slow->next; 60 | } 61 | return start; 62 | } 63 | }; -------------------------------------------------------------------------------- /Solutions/Linked-Lists/Problem_2_add_two_numbers.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | UCLA ACM ICPC: Interview Track Leetcode Problem Solutions 3 | 4 | Disclaimer: This is not the only valid solution and we do not claim our solutions 5 | to be optimal in terms of runtime or memory usage, we are merely giving example 6 | solutions to questions for learning purposes only 7 | 8 | Add Two Numbers 9 | Leetcode Problem 2 10 | https://leetcode.com/problems/add-two-numbers/ 11 | */ 12 | 13 | // You are given two non-empty linked lists representing two 14 | // non-negative integers. The digits are stored in reverse order 15 | // and each of their nodes contain a single digit. Add the two 16 | // numbers and return it as a linked list. 17 | 18 | // Note: You may assume the two numbers do not contain any 19 | // leading zero, except the number 0 itself. 20 | 21 | /** 22 | * Definition for singly-linked list. 23 | * struct ListNode { 24 | * int val; 25 | * ListNode *next; 26 | * ListNode(int x) : val(x), next(NULL) {} 27 | * }; 28 | */ 29 | class Solution { 30 | public: 31 | ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) { 32 | // If a linked list represents the number 0, 33 | // just return the other linked list 34 | if(l1->val == 0 && l1->next == NULL) return l2; 35 | if(l2->val == 0 && l2->next == NULL) return l1; 36 | 37 | // Create a node for the head. It will have the 38 | // value of the 1's place of l1's val + l2's val, 39 | // which we get from doing `% 10` 40 | int val = l1->val + l2->val; 41 | ListNode* head = new ListNode(val % 10); 42 | 43 | // Represents the value we carry over to the next 44 | // node. It will either be 0 or 1 depending on 45 | // whether `val >= 10` 46 | int carry = val / 10; 47 | 48 | // The loop will compute the next node, which we 49 | // have to connect to the previous one, which is 50 | // represented by this pointer 51 | ListNode* prev = head; 52 | 53 | // While there are still nodes to compute 54 | while(l1->next != NULL || l2->next != NULL) { 55 | // Compute the value of the next node 56 | int val1 = l1->next ? l1->next->val : 0; 57 | int val2 = l2->next ? l2->next->val : 0; 58 | ListNode* next = new ListNode((val1 + val2 + carry) % 10); 59 | 60 | // Determine if we have to carry over a 1 or not 61 | carry = (val1 + val2 + carry) / 10; 62 | 63 | // Update the pointers 64 | prev->next = next; 65 | prev = next; 66 | l1 = l1->next ? l1->next : l1; 67 | l2 = l2->next ? l2->next : l2; 68 | } 69 | 70 | // If `carry == 1`, then we need to create a new node 71 | // with the value of 1 to place at the end. 72 | // Ex. 9 -> 9 (99) + 1 (1) = 0 -> 0 -> 1 (100) 73 | if(carry) { 74 | ListNode* tail = new ListNode(1); 75 | prev->next = tail; 76 | } 77 | 78 | return head; 79 | } 80 | }; -------------------------------------------------------------------------------- /Solutions/Strings/Problem_32_longest_valid_parentheses.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | UCLA ACM ICPC: Interview Track Leetcode Problem Solutions 3 | 4 | Disclaimer: This is not the only valid solution and we do not claim our solutions 5 | to be optimal in terms of runtime or memory usage, we are merely giving example 6 | solutions to questions for learning purposes only 7 | 8 | Longest Valid Parentheses 9 | Leetcode Problem 32 10 | https://leetcode.com/problems/longest-valid-parentheses/ 11 | */ 12 | 13 | // Given a string containing just the characters '(' and ')', find 14 | // the length of the longest valid (well-formed) parentheses substring. 15 | 16 | class Solution { 17 | public: 18 | int longestValidParentheses(string s) { 19 | int left = 0; 20 | int right = 0; 21 | int longest = 0; 22 | 23 | // Iterate through the string from left to right, keeping 24 | // a counter of the number of left parentheses and the 25 | // number of right parentheses 26 | for(int i = 0; i < s.size(); i++) { 27 | if(s[i] == '(') left++; 28 | if(s[i] == ')') right++; 29 | 30 | // P1: When the number of left and right parentheses equal, 31 | // we have a valid set of parentheses of size `left + right`. 32 | 33 | // If P1, check to see if it's the longest valid set we've seen 34 | if(left == right) longest = max(longest, left + right); 35 | 36 | // When the number of right parentheses becomes larger than 37 | // the number of left parentheses, reset the counts to ensure 38 | // that P1 always remains true. 39 | if(right > left) { 40 | left = 0; 41 | right = 0; 42 | } 43 | } 44 | 45 | 46 | left = 0; 47 | right = 0; 48 | 49 | // Reset the counters, and do the exact same process going 50 | // from right to left. 51 | for(int i = s.size() - 1; i >= 0; i--) { 52 | if(s[i] == '(') left++; 53 | if(s[i] == ')') right++; 54 | if(left == right) longest = max(longest, left + right); 55 | 56 | // Remember to swap left and right when going from right to 57 | // left to ensure P1 remains true going from right to left 58 | if(left > right) { 59 | left = 0; 60 | right = 0; 61 | } 62 | } 63 | 64 | return longest; 65 | } 66 | }; 67 | 68 | // The time complexity is O(n) because we make two loops 69 | // through the string. 70 | // The space is complexity is O(1) because we only store a 71 | // constant amount of memory. -------------------------------------------------------------------------------- /Solutions/Strings/Problem_49_group_anagrams.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | UCLA ACM ICPC: Interview Track Leetcode Problem Solutions 3 | 4 | Disclaimer: This is not the only valid solution and we do not claim our solutions 5 | to be optimal in terms of runtime or memory usage, we are merely giving example 6 | solutions to questions for learning purposes only 7 | 8 | Group Anagrams 9 | Leetcode Problem 49 10 | https://leetcode.com/problems/group-anagrams/ 11 | */ 12 | 13 | class Solution { 14 | public: 15 | // Our strategy is to map each string to its group of anagrams 16 | // We do this by categorizing each string by the counts of 17 | // each letter, and then mapping that to a group of anagrams. 18 | 19 | // Example: 20 | // ["aab", "aba", "baa", "abbccc"] 21 | // "a2b1c0...z0" -> ["aab", "aba", "baa"] 22 | // "a1b2c3...z0" -> ["abbccc"] 23 | vector> groupAnagrams(vector& strs) { 24 | 25 | // If there are no strings in the given vector, 26 | // the answer is trivial 27 | if(strs.size() == 0) return {}; 28 | 29 | // Initialize a vector of size 26: 30 | // The number of lowercase letters in the alphabet 31 | vector count(26); 32 | unordered_map> anagrams; 33 | 34 | for(string s : strs) { 35 | // Fill the vector with 0's 36 | fill(count.begin(), count.end(), 0); 37 | 38 | // Get the counts of each character 39 | for(char c : s) count[c - 'a']++; 40 | // Note: Since we are guaranteed that S only contains 41 | // lowercase letters, we can map each letter to a 42 | // number by subtracting 'a' 43 | 44 | // Generate the string with the 45 | // counts of each character 46 | string key = ""; 47 | for(int i = 0; i < 26; i++) { 48 | key += i + 'a'; 49 | key += count[i]; 50 | } 51 | 52 | // Add to the current string to the appropriate 53 | // list of anagrams using the key 54 | anagrams[key].push_back(s); 55 | } 56 | 57 | // Return a vector of all the anagram groups 58 | vector> result; 59 | for(auto i = anagrams.begin(); i != anagrams.end(); i++) 60 | result.push_back(i->second); 61 | return result; 62 | } 63 | }; 64 | 65 | // Our time complexity is O(MN), where M is the number of strings given, 66 | // and N is the average number of characters per string. The O(M) comes 67 | // from looping through each string, and O(N) is for looping through 68 | // each character of the string. 69 | 70 | // Our space complexity is also O(MN). We store each given string as a 71 | // part of our answer, which has a size of O(MN). -------------------------------------------------------------------------------- /Solutions/Strings/Problem_6_zigzag_conversion.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | UCLA ACM ICPC: Interview Track Leetcode Problem Solutions 3 | 4 | Disclaimer: This is not the only valid solution and we do not claim our solutions 5 | to be optimal in terms of runtime or memory usage, we are merely giving example 6 | solutions to questions for learning purposes only 7 | 8 | ZigZag Conversion 9 | Leetcode Problem 6 10 | https://leetcode.com/problems/zigzag-conversion/ 11 | */ 12 | 13 | // The string "PAYPALISHIRING" is written in a zigzag pattern on a 14 | // given number of rows (in this case, 3) like this: 15 | 16 | // P A H N 17 | // A P L S I I G 18 | // Y I R 19 | 20 | // And then read line by line: "PAHNAPLSIIGYIR" 21 | 22 | // Write the code that will take a string and make this conversion 23 | // given a number of rows 24 | 25 | class Solution { 26 | public: 27 | // Our algorithm will loop through the string to create a vector 28 | // representing each row. Then, we concatenate all the strings 29 | // to get our answer. 30 | string convert(string s, int numRows) { 31 | 32 | // If there is only one row, or there are more rows than 33 | // characters in the string, then the solution is trivially 34 | // just the string itself. 35 | if(numRows == 1 || s.size() < numRows) return s; 36 | 37 | vector rows(numRows); 38 | int curRow = 0; 39 | bool goingDown = false; 40 | 41 | // Trace through the zigzag pattern. 42 | for(char c: s) { 43 | rows[curRow] += c; 44 | 45 | // If we are on the first row, move from going up to going down. 46 | // It we are on the last row, move from going down to going up. 47 | if(curRow == 0 || curRow == numRows - 1) goingDown = !goingDown; 48 | 49 | // If we are going down, move down a row. 50 | // If we are going up, move up a row. 51 | curRow += goingDown ? 1 : -1; 52 | } 53 | 54 | // Concatenate all the row strings 55 | string result = ""; 56 | for(string row : rows) result += row; 57 | return result; 58 | } 59 | }; 60 | 61 | // The time complexity is O(n) because we make one loop through the string. 62 | // The space complexity is O(n) because all the rows add up to a string 63 | // that has the same size as the original string. -------------------------------------------------------------------------------- /Solutions/Strings/Problem_767_reorganize_string.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | UCLA ACM ICPC: Interview Track Leetcode Problem Solutions 3 | 4 | Disclaimer: This is not the only valid solution and we do not claim our solutions 5 | to be optimal in terms of runtime or memory usage, we are merely giving example 6 | solutions to questions for learning purposes only 7 | 8 | Reorganize String 9 | Leetcode Problem 767 10 | https://leetcode.com/problems/reorganize-string/ 11 | */ 12 | 13 | // Given a string S, check if the letters can be rearranged so that 14 | // two characters that are adjacent to each other are not the same. 15 | 16 | // If possible, output any possible result. 17 | // If not possible, return the empty string. 18 | 19 | class Solution { 20 | public: 21 | // Take the count of each letter in S and find the character C 22 | // with the most occurrences in S. If the size of S is N, and 23 | // There are more than (N + 1) / 2 occurrences of C, then we 24 | // can't reorganize the string (Why? Hint: Pigeonhole Principle) 25 | 26 | // Otherwise, we space out the occurrences of each character in 27 | // S starting with C. We do this by filling in positions in the 28 | // order of 0, 2, 4... and then 1, 3, 5... 29 | // Example: 30 | 31 | // aaaabbbbb 32 | // -> b_b_b_b_b 33 | // -> babababab 34 | 35 | // aaabbbcdd 36 | // -> a_a_a____ 37 | // -> aba_a_b_b 38 | // -> abaca_b_b 39 | // -> abacadbdb 40 | string reorganizeString(string S) { 41 | // Take the count of each letter in S 42 | 43 | // Initialize a vector of 0's of size 26 44 | vector hash(26, 0); 45 | for(int i = 0; i < S.size(); i++) 46 | hash[S[i] - 'a']++; 47 | // Note: Since we are guaranteed that S only contains 48 | // lowercase letters, we can map each letter to a 49 | // number by subtracting 'a' 50 | 51 | // Find the index in the vector with the highest value 52 | // and store the index and value 53 | int maxCount = 0; 54 | int maxIndex = 0; 55 | for(int i = 0; i < 26; i++) { 56 | if(maxCount < hash[i]) { 57 | maxIndex = i; 58 | maxCount = hash[i]; 59 | } 60 | } 61 | 62 | // Pigeonhole Principle check 63 | if(maxCount > (S.size() + 1) / 2) return ""; 64 | 65 | 66 | // Initialize a string of the same size as S, 67 | // but every character is a '*' 68 | string result(S.size(), '*'); 69 | int curIndex = 0; 70 | 71 | // Starting with the character C, filling in positions in 72 | // the order of 0, 2, 4... 73 | // We do it this way because it's the only way to ensure 74 | // we can space out all instances of C. 75 | while(hash[maxIndex] > 0) { 76 | result[curIndex] = maxIndex + 'a'; 77 | curIndex += 2; 78 | hash[maxIndex]--; 79 | } 80 | 81 | // Continue filling in positions in the order of 0, 2, 4... 82 | // but now we are allowed to use any character 83 | int j = 0; 84 | while(curIndex < S.size()) { 85 | while(hash[j] == 0) j++; 86 | result[curIndex] = j + 'a'; 87 | hash[j]--; 88 | curIndex += 2; 89 | } 90 | 91 | // Fill in positions in the order of 1, 3, 5... 92 | curIndex = 1; 93 | while(curIndex < S.size()) { 94 | while(hash[j] == 0) j++; 95 | result[curIndex] = j + 'a'; 96 | hash[j]--; 97 | curIndex += 2; 98 | } 99 | 100 | return result; 101 | } 102 | }; 103 | 104 | // The time complexity is O(n) because we make loops through 105 | // structures as big as the string. 106 | // The space complexity is O(n) because we construct a new 107 | // string of the same size for the answer. -------------------------------------------------------------------------------- /Solutions/Topological-And-Heap-Sort/Problem_1203_Sort_Items_By_Groups_Respecting_Dependencies.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | UCLA ACM ICPC: Interview Track Leetcode Problem Solutions 3 | 4 | Disclaimer: This is not the only valid solution and we do not claim our solutions 5 | to be optimal in terms of runtime or memory usage, we are merely giving example 6 | solutions to questions for learning purposes only 7 | 8 | Sort Items by Groups Respecting Dependencies 9 | Leetcode Problem 1203 10 | https://leetcode.com/problems/sort-items-by-groups-respecting-dependencies/ 11 | */ 12 | 13 | class Solution { 14 | public: 15 | // First, we make sure that each group can be topologically ordered against each other. 16 | // Then, we make sure that within each group, the items can be topologically ordered. 17 | vector sortItems(int n, int m, vector& group, vector>& beforeItems) { 18 | 19 | // To make like easier for us, put each item 20 | // that doesn't have a group into its own group 21 | for(int i = 0; i < n; i++) { 22 | if(group[i] == -1) { 23 | group[i] = m; 24 | m++; 25 | } 26 | } 27 | 28 | // Construct a vector of groups 29 | // Given the group number: Return 30 | // the items inside that group 31 | vector> groups(m); 32 | for(int i = 0; i < n; i++) { 33 | groups[group[i]].push_back(i); 34 | } 35 | 36 | // Keep track of information regarding 37 | // dependencies between each group 38 | vector> groupGraph(m); 39 | vector groupIncoming(m, 0); 40 | vector groupVisited(m, false); 41 | for(int i = 0; i < n; i++) { 42 | for(int j = 0; j < beforeItems[i].size(); j++) { 43 | if(group[beforeItems[i][j]] != group[i]) { 44 | groupGraph[group[beforeItems[i][j]]].push_back(group[i]); 45 | groupIncoming[group[i]]++; 46 | } 47 | } 48 | } 49 | 50 | // Run topological sort on our constructed graph 51 | queue q; 52 | for(int i = 0; i < m; i++) 53 | if(groupIncoming[i] == 0) q.push(i); 54 | vector groupTopoSort; 55 | while(!q.empty()) { 56 | int cur = q.front(); q.pop(); 57 | groupTopoSort.push_back(cur); 58 | groupVisited[cur] = true; 59 | for(int node: groupGraph[cur]) { 60 | groupIncoming[node]--; 61 | if(!groupVisited[node] && groupIncoming[node] == 0) { 62 | q.push(node); 63 | } 64 | } 65 | } 66 | 67 | // If the topological sort ends early, a sort isn't possible, 68 | // so return the empty vector 69 | if(groupTopoSort.size() != m) return {}; 70 | 71 | /////////////////////////////////////////////////////////////////////// 72 | 73 | vector itemsSort; 74 | 75 | // Topologically items within each group, maintaining the 76 | // order of the topological sort between each group 77 | for(int x = 0; x < m; x++) { 78 | vector curGroup = groups[groupTopoSort[x]]; 79 | int size = curGroup.size(); 80 | 81 | // Keep track of information regarding 82 | // dependencies within the items of the group 83 | unordered_map> graph; 84 | unordered_map incoming; 85 | unordered_map visited; 86 | for(int i : curGroup) { 87 | incoming[i] = 0; 88 | visited[i] = 0; 89 | } 90 | for(int i : curGroup) { 91 | for(int j : beforeItems[i]) { 92 | if(incoming.find(j) != incoming.end()) { 93 | graph[j].push_back(i); 94 | incoming[i]++; 95 | } 96 | } 97 | } 98 | 99 | // Run topological sort on our constructed graph 100 | queue q; 101 | for(int i : curGroup) 102 | if(incoming[i] == 0) q.push(i); 103 | vector topoSort; 104 | while(!q.empty()) { 105 | int cur = q.front(); q.pop(); 106 | topoSort.push_back(cur); 107 | visited[cur] = true; 108 | for(int node: graph[cur]) { 109 | incoming[node]--; 110 | if(!visited[node] && incoming[node] == 0) { 111 | q.push(node); 112 | } 113 | } 114 | } 115 | 116 | // If the topological sort ends early, a sort isn't possible, 117 | // so return the empty vector. Else, concatenate this late to 118 | // our final result 119 | if(topoSort.size() != size) return {}; 120 | else itemsSort.insert(itemsSort.end(), topoSort.begin(), topoSort.end()); 121 | } 122 | 123 | return itemsSort; 124 | } 125 | }; -------------------------------------------------------------------------------- /Solutions/Topological-And-Heap-Sort/Problem_210_course_schedule_ii.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | UCLA ACM ICPC: Interview Track Leetcode Problem Solutions 3 | 4 | Disclaimer: This is not the only valid solution and we do not claim our solutions 5 | to be optimal in terms of runtime or memory usage, we are merely giving example 6 | solutions to questions for learning purposes only 7 | 8 | Course Schedule II 9 | Leetcode Problem 210 10 | https://leetcode.com/problems/course-schedule-ii/ 11 | */ 12 | 13 | // There are a total of n courses you have to take, labeled from 0 to n-1. 14 | 15 | // Some courses may have prerequisites, for example to take course 0 you 16 | // have to first take course 1, which is expressed as a pair: [0,1] 17 | 18 | // Given the total number of courses and a list of prerequisite pairs, 19 | // return the ordering of courses you should take to finish all courses. 20 | 21 | // There may be multiple correct orders, you just need to return one of them. 22 | // If it is impossible to finish all courses, return an empty array. 23 | 24 | class Solution { 25 | public: 26 | // This problem is essentially topological sort, 27 | // but we must return one such sort if possible. 28 | vector findOrder(int numCourses, vector>& prerequisites) { 29 | // We want to keep track of certain information given a node 30 | // * What nodes it points to 31 | // * How many incoming edges it has from visited nodes 32 | // * Whether we have visited the node yet 33 | 34 | // This section of code builds up this information 35 | vector> graph(numCourses); // Adjacency list 36 | vector incoming(numCourses, 0); 37 | vector visited(numCourses, false); 38 | for(vector e: prerequisites) { 39 | graph[e[1]].push_back(e[0]); 40 | incoming[e[0]]++; 41 | } 42 | 43 | // This queue will store nodes that aren't visited 44 | // and have zero incoming edges. We pick nodes from 45 | // here to put into our topological sort 46 | queue q; 47 | for(int i = 0; i < numCourses; i++) { 48 | if(incoming[i] == 0) q.push(i); 49 | } 50 | 51 | vector topoSort; 52 | while(!q.empty()) { 53 | // Get a node from the queue and add it to 54 | // the topological sort. Also mark it as 55 | // visited 56 | int cur = q.front(); q.pop(); 57 | topoSort.push_back(cur); 58 | visited[cur] = true; 59 | 60 | // Add nodes that the current node points to 61 | // to the queue only if we haven't visited it 62 | // and it has no incoming edges 63 | for(int node: graph[cur]) { 64 | incoming[node]--; 65 | if(!visited[node] && incoming[node] == 0) { 66 | q.push(node); 67 | } 68 | } 69 | } 70 | 71 | // A valid topological sort should contain all 72 | // `numCourses` nodes. If it doesn't, we return 73 | // an empty vector 74 | if(topoSort.size() != numCourses) return {}; 75 | else return topoSort; 76 | } 77 | }; 78 | 79 | // Time Complexity: O(V + E) where V is the number of nodes 80 | // and E is the number of edges. We loop through all nodes 81 | // a few times, which is O(V). The while loop with the 82 | // queue potentially processes all edges, which is O(E) 83 | 84 | // Space Complexity: O(V + E) where V is the number of nodes 85 | // and E is the number of edges. This is because the adjacency 86 | // list takes up O(V + E) space, and it is the biggest data 87 | // structure we have in our algorithm -------------------------------------------------------------------------------- /Solutions/Topological-And-Heap-Sort/Problem_215_Kth_Largest_Element_In_An_Array.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | UCLA ACM ICPC: Interview Track Leetcode Problem Solutions 3 | 4 | Disclaimer: This is not the only valid solution and we do not claim our solutions 5 | to be optimal in terms of runtime or memory usage, we are merely giving example 6 | solutions to questions for learning purposes only 7 | 8 | Kth Largest Element in an Array 9 | Leetcode Problem 215 10 | https://leetcode.com/problems/kth-largest-element-in-an-array/submissions/ 11 | */ 12 | 13 | class Solution { 14 | public: 15 | // A trivial solution is to sort the vector to find 16 | // the k-th largest, which has a runtime on O(N log N). 17 | // We can do a bit better by building a max heap with 18 | // the contents of the vector, and then finding the 19 | // largest element k times. 20 | int findKthLargest(vector& nums, int k) { 21 | // By default, priority queues in C++ are max heaps 22 | priority_queue maxHeap(nums.begin(), nums.end()); 23 | 24 | // Pull out the first k-1 largest elements 25 | for(int i = 0; i < k-1; i++) maxHeap.pop(); 26 | 27 | // Get the kth largest element 28 | return maxHeap.top(); 29 | } 30 | }; 31 | 32 | // Time complexity: O(N + K logN). It takes O(N) to build the 33 | // heap, and we remove from the heap K times, which takes 34 | // O(log N) time. 35 | 36 | // Space complexity: O(N). A heap takes O(N) space -------------------------------------------------------------------------------- /Solutions/Topological-And-Heap-Sort/Problem_23_Merge_K_Sorted_Lists.cpp: -------------------------------------------------------------------------------- 1 | struct CompareHead { 2 | bool operator()(ListNode* const& h1, ListNode* const& h2) 3 | { 4 | return h1->val > h2->val; 5 | } 6 | }; 7 | 8 | class Solution { 9 | public: 10 | ListNode* mergeKLists(vector& lists) { 11 | //initialize a priority queue. 12 | priority_queue, CompareHead> Q; 13 | //use a dummy head to mark the beginning of the result linked list. 14 | ListNode* dummy = new ListNode(0); 15 | dummy->next = nullptr; 16 | ListNode* curr = dummy; 17 | //push all the linked lists in the lists vector into our priority queue 18 | //which orders them based on the custom comparator defined above. 19 | for(int i = 0; i < lists.size(); i++) { 20 | if(lists[i] != nullptr) 21 | Q.push(lists[i]); 22 | } 23 | //while we still have elements in the Priority Queue. 24 | while(Q.size()){ 25 | ListNode* temp = Q.top(); 26 | Q.pop(); 27 | //append the popped head to the result linked list. 28 | curr->next = new ListNode(temp->val); 29 | curr = curr->next; 30 | curr->next = nullptr; 31 | //advance the head by one node if there are elements left in the 32 | //linked list. 33 | if(temp->next != nullptr) 34 | Q.push(temp->next); 35 | } 36 | return dummy->next; 37 | } 38 | }; -------------------------------------------------------------------------------- /Solutions/Topological-And-Heap-Sort/Problem_329_longest-increasing-path-in-a-matrix.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | UCLA ACM ICPC: Interview Track Leetcode Problem Solutions 3 | 4 | Disclaimer: This is not the only valid solution and we do not claim our solutions 5 | to be optimal in terms of runtime or memory usage, we are merely giving example 6 | solutions to questions for learning purposes only 7 | 8 | Longest Increasing Path in a Matrix 9 | Leetcode Problem 329 10 | https://leetcode.com/problems/longest-increasing-path-in-a-matrix/ 11 | */ 12 | 13 | // Given an integer matrix, find the length of the longest increasing path. 14 | 15 | // From each cell, you can either move to four directions: left, right, 16 | // up or down. You may NOT move diagonally or move outside of the 17 | // boundary (i.e. wrap-around is not allowed). 18 | 19 | class Solution { 20 | public: 21 | // This is a graph problem in disguise (tends to happen with 22 | // problems with multidimensional arrays). In fact, it's a 23 | // topological sort problem if you think hard enough about it! 24 | 25 | // Suppose the matrix were instead a DAG where each cell is 26 | // a node and each cell is connected to those adjacent to it 27 | // only if the cell's value is lower than the adjacent cell's 28 | // value. 29 | 30 | // Then, the longest increasing path would be the same as 31 | // the longest increasing path in the graph. We can find 32 | // the length of the path by doing a combination of the 33 | // topological sort algorithm and BFS 34 | int longestIncreasingPath(vector>& matrix) { 35 | // Trivial edge case 36 | if(matrix.size() == 0) return 0; 37 | 38 | int rows = matrix.size(); 39 | int cols = matrix[0].size(); 40 | 41 | // We want to keep track of certain information given a node 42 | // * What nodes it points to 43 | // * How many incoming edges it has from visited nodes 44 | // * Whether we have visited the node yet 45 | vector> graph(rows * cols); // Adjacency list 46 | vector incoming(rows * cols, 0); 47 | vector visited(rows * cols, false); 48 | 49 | // Build up the information 50 | for(int i = 0; i < rows; i++) { 51 | for(int j = 0; j < cols; j++) { 52 | int index = i * cols + j; 53 | 54 | // Check the up adjacent node 55 | if(i > 0 && matrix[i][j] < matrix[i-1][j]) { 56 | graph[index].push_back((i-1) * cols + j); 57 | incoming[(i-1) * cols + j]++; 58 | } 59 | 60 | // Check the down adjacent node 61 | if(i < rows - 1 && matrix[i][j] < matrix[i+1][j]) { 62 | graph[index].push_back((i+1) * cols + j); 63 | incoming[(i+1) * cols + j]++; 64 | } 65 | 66 | // Check the left adjacent node 67 | if(j > 0 && matrix[i][j] < matrix[i][j-1]) { 68 | graph[index].push_back(i * cols + j-1); 69 | incoming[i * cols + j-1]++; 70 | } 71 | 72 | // Check the right adjacent node 73 | if(j < cols - 1 && matrix[i][j] < matrix[i][j+1]) { 74 | graph[index].push_back(i * cols + j+1); 75 | incoming[i * cols + j+1]++; 76 | } 77 | 78 | } 79 | } 80 | 81 | // This queue will store nodes that aren't visited 82 | // and have zero incoming edges. We pick nodes from 83 | // here to put into our topological sort 84 | queue q; 85 | for(int i = 0; i < rows * cols; i++) { 86 | if(incoming[i] == 0) q.push(i); 87 | } 88 | 89 | // We process the graph "level-by-level", where each 90 | // level is the nodes that aren't visited and have 91 | // zero incoming edges after we remove all the nodes 92 | // on the upper levels. 93 | 94 | // The longest path in the graph equals the number of 95 | // levels, because moving down one level means walking 96 | // along an edge 97 | 98 | int length = 0; 99 | while(!q.empty()) { 100 | // Each iteration of the outer loop 101 | // represents one level 102 | length++; 103 | 104 | // Remeber to save this number outside of 105 | // a loop, or q.size() may change with each 106 | // iteration 107 | int size = q.size(); 108 | 109 | // Process all nodes on the current level 110 | // (all nodes currently in the queue) 111 | for(int i = 0; i < size; i++) { 112 | // Get a node from the queue and 113 | // mark it as visited 114 | int cur = q.front(); q.pop(); 115 | visited[cur] = true; 116 | 117 | // Add nodes that the current node points to 118 | // to the queue only if we haven't visited it 119 | // and it has no incoming edges 120 | for(int node: graph[cur]) { 121 | incoming[node]--; 122 | if(!visited[node] && incoming[node] == 0) { 123 | q.push(node); 124 | } 125 | } 126 | } 127 | } 128 | 129 | return length; 130 | } 131 | }; -------------------------------------------------------------------------------- /Solutions/Trees-1/Problem_102_binary_tree_level_order_traversal.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | UCLA ACM ICPC: Interview Track Leetcode Problem Solutions 3 | 4 | Disclaimer: This is not the only valid solution and we do not claim our solutions 5 | to be optimal in terms of runtime or memory usage, we are merely giving example 6 | solutions to questions for learning purposes only 7 | 8 | Binary Tree Level Order Traversal 9 | Leetcode Problem 102 10 | https://leetcode.com/problems/binary-tree-level-order-traversal/ 11 | */ 12 | 13 | // Given a binary tree, return the level order traversal of its nodes' 14 | // values. (ie, from left to right, level by level). 15 | 16 | // For example: 17 | // Given binary tree, 18 | 19 | // 3 20 | // / \ 21 | // 9 20 22 | // / \ 23 | // 15 7 24 | 25 | // return its level order traversal as: 26 | 27 | // [ 28 | // [3], 29 | // [9,20], 30 | // [15,7] 31 | // ] 32 | 33 | /** 34 | * Definition for a binary tree node. 35 | * struct TreeNode { 36 | * int val; 37 | * TreeNode *left; 38 | * TreeNode *right; 39 | * TreeNode() : val(0), left(nullptr), right(nullptr) {} 40 | * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} 41 | * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} 42 | * }; 43 | */ 44 | class Solution { 45 | public: 46 | // Our strategy is to use BFS with a queue. For each level process 47 | // the correct amount of nodes on that level while removing them from 48 | // the queue. Meanwhile, we add nodes on the next level to the queue 49 | vector> levelOrder(TreeNode* root) { 50 | // Edge case: If root is NULL, then we don't do a traversal 51 | if(root == NULL) return {}; 52 | 53 | queue q; 54 | q.push(root); 55 | 56 | // Represents how many nodes are on the current level 57 | // Our first level only has the root, so it's initially set 58 | // to 1 59 | int nodesOnThisLevel = 1; 60 | 61 | // Represents how many nodes are on the next level 62 | // We don't know how many are on the next level yet, so 63 | // we set it to 0, and increment it when necessary 64 | int nodesOnNextLevel = 0; 65 | 66 | vector> result; 67 | 68 | // If there are nodes on our current level, there are 69 | // still nodes we need to process 70 | while(nodesOnThisLevel != 0) { 71 | // This vector represents the current level 72 | vector level; 73 | 74 | // Process each node of this level 75 | for(int i = 0; i < nodesOnThisLevel; i++) { 76 | // Process the front node. This involves just 77 | // putting it in our vector and dequeueing it 78 | TreeNode* cur = q.front(); 79 | level.push_back(cur->val); 80 | q.pop(); 81 | 82 | // Add the left child of the current node to the 83 | // queue if possible 84 | if(cur->left != NULL) { 85 | nodesOnNextLevel++; 86 | q.push(cur->left); 87 | } 88 | 89 | // Add the right child of the current node to the 90 | // queue if possible 91 | if(cur->right != NULL) { 92 | nodesOnNextLevel++; 93 | q.push(cur->right); 94 | } 95 | } 96 | 97 | // Add the level to our vector of levels 98 | result.push_back(level); 99 | 100 | // Update the level variables 101 | nodesOnThisLevel = nodesOnNextLevel; 102 | nodesOnNextLevel = 0; 103 | } 104 | return result; 105 | } 106 | }; 107 | 108 | // Time Complexity: O(N) because each node is looked at a 109 | // constant number of times 110 | // Space Complexity: O(N) because in the worst case, all 111 | // nodes can be in the queue at all times -------------------------------------------------------------------------------- /Solutions/Trees-1/Problem_114_flatten_binary_tree_to_linked_list.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | UCLA ACM ICPC: Interview Track Leetcode Problem Solutions 3 | 4 | Disclaimer: This is not the only valid solution and we do not claim our solutions 5 | to be optimal in terms of runtime or memory usage, we are merely giving example 6 | solutions to questions for learning purposes only 7 | 8 | Binary Tree Inorder Traversal 9 | Leetcode Problem 94 10 | https://leetcode.com/problems/binary-tree-inorder-traversal/ 11 | */ 12 | 13 | // Given a binary tree, flatten it to a linked list in-place. 14 | // For example, given the following tree: 15 | /* 16 | 1 17 | / \ 18 | 2 5 19 | / \ \ 20 | 3 4 6 21 | */ 22 | // The flattened tree should look like: 23 | /* 24 | 1 25 | \ 26 | 2 27 | \ 28 | 3 29 | \ 30 | 4 31 | \ 32 | 5 33 | \ 34 | 6 35 | */ 36 | 37 | /** 38 | * Definition for a binary tree node. 39 | * struct TreeNode { 40 | * int val; 41 | * TreeNode *left; 42 | * TreeNode *right; 43 | * TreeNode() : val(0), left(nullptr), right(nullptr) {} 44 | * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} 45 | * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} 46 | * }; 47 | */ 48 | class Solution { 49 | public: 50 | // We will use simple recursion to solve this problem 51 | void flatten(TreeNode* root) { 52 | // Base Case: If the root is NULL, there's nothing to do 53 | if(root == NULL) return; 54 | 55 | // Recursive Case: If the root has no left subtree, all 56 | // you need to do is flatten the right subtree 57 | else if(root->left == NULL) flatten(root->right); 58 | 59 | // Recursive Case: If the root has a left subtree... 60 | else { 61 | // Flatten both subtrees 62 | flatten(root->left); 63 | flatten(root->right); 64 | 65 | // Construct `leftTail`, a variable representing the 66 | // end of the flattened left subtree 67 | TreeNode* leftTail = root->left; 68 | while(leftTail->right != NULL) leftTail = leftTail->right; 69 | 70 | // Add the right subtree to the end of the left subtree 71 | leftTail->right = root->right; 72 | 73 | // Replace the location of the right subtree with the 74 | // left subtree 75 | root->right = root->left; 76 | 77 | // Replace the location of the left subtree with NULL 78 | root->left = NULL; 79 | } 80 | } 81 | }; 82 | 83 | // Time Complexity: O(N) because each node is looked at a 84 | // constant number of times in the worst case 85 | // Space Complexity: O(1) because the algorithm is done in-place -------------------------------------------------------------------------------- /Solutions/Trees-1/Problem_144_binary_tree_preorder_traversal.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | UCLA ACM ICPC: Interview Track Leetcode Problem Solutions 3 | 4 | Disclaimer: This is not the only valid solution and we do not claim our solutions 5 | to be optimal in terms of runtime or memory usage, we are merely giving example 6 | solutions to questions for learning purposes only 7 | 8 | Binary Tree Preorder Traversal 9 | Leetcode Problem 144 10 | https://leetcode.com/problems/binary-tree-preorder-traversal/ 11 | */ 12 | 13 | // Given a binary tree, return the preorder traversal of its nodes' values. 14 | // Follow up: Recursive solution is trivial, could you do it iteratively? 15 | 16 | /** 17 | * Definition for a binary tree node. 18 | * struct TreeNode { 19 | * int val; 20 | * TreeNode *left; 21 | * TreeNode *right; 22 | * TreeNode() : val(0), left(nullptr), right(nullptr) {} 23 | * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} 24 | * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} 25 | * }; 26 | */ 27 | class Solution { 28 | public: 29 | // Our strategy is to use DFS with a stack, where the top of the 30 | // stack represents the next element we need to process 31 | vector preorderTraversal(TreeNode* root) { 32 | // Edge case: If root is NULL, then we don't do a traversal 33 | if(root == NULL) return {}; 34 | 35 | vector result; 36 | stack s; 37 | s.push(root); 38 | 39 | while(!s.empty()) { 40 | // Process the current node. This involves 41 | // popping it off the stack and putting it in our vector 42 | TreeNode* cur = s.top(); 43 | s.pop(); 44 | result.push_back(cur->val); 45 | 46 | // Add the right child to the stack if it exists 47 | if(cur->right != NULL) s.push(cur->right); 48 | 49 | // Add the left child to the stack if it exists 50 | if(cur->left != NULL) s.push(cur->left); 51 | 52 | // Note: We add the right child and then the left child 53 | // because we want to process the left child first. Since 54 | // stacks are LIFO, the last thing going into it will be 55 | // the first thing coming out. 56 | } 57 | return result; 58 | } 59 | }; 60 | 61 | // Time Complexity: O(N) because each node is looked at a 62 | // constant number of times 63 | // Space Complexity: O(N) because in the worst case, all 64 | // nodes can be in the stack at all times -------------------------------------------------------------------------------- /Solutions/Trees-1/Problem_145_binary_tree_postorder_traversal.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | UCLA ACM ICPC: Interview Track Leetcode Problem Solutions 3 | 4 | Disclaimer: This is not the only valid solution and we do not claim our solutions 5 | to be optimal in terms of runtime or memory usage, we are merely giving example 6 | solutions to questions for learning purposes only 7 | 8 | Binary Tree Postorder Traversal 9 | Leetcode Problem 145 10 | https://leetcode.com/problems/binary-tree-postorder-traversal/ 11 | */ 12 | 13 | // Given a binary tree, return the postorder traversal of its nodes' values. 14 | // Follow up: Recursive solution is trivial, could you do it iteratively? 15 | 16 | /** 17 | * Definition for a binary tree node. 18 | * struct TreeNode { 19 | * int val; 20 | * TreeNode *left; 21 | * TreeNode *right; 22 | * TreeNode(int x) : val(x), left(NULL), right(NULL) {} 23 | * }; 24 | */ 25 | class Solution { 26 | public: 27 | // Our strategy is to run a preorder traversal of the tree, 28 | // putting each value we come across onto a stack. Popping 29 | // off all the values of the stack yields a postorder traversal. 30 | 31 | // This works because in a preorder traversal, the parent is 32 | // processed before the children. In this case, this means the 33 | // children will be put into the stack after the parent, and will 34 | // be taken out first. 35 | vector postorderTraversal(TreeNode* root) { 36 | // Edge case: If root is NULL, then we don't do a traversal 37 | if(root == NULL) return {}; 38 | 39 | stack s1; 40 | stack s2; 41 | s1.push(root); 42 | 43 | while(!s1.empty()) { 44 | TreeNode* cur = s1.top(); 45 | 46 | // Take `cur` out of `s1` and push its value to `s2` 47 | s1.pop(); 48 | s2.push(cur->val); 49 | 50 | // Push the children of `cur` onto `s1` if they exist 51 | if(cur->left != NULL) s1.push(cur->left); 52 | if(cur->right != NULL) s1.push(cur->right); 53 | 54 | // Note: We push the left child onto the node first 55 | // because that means the right child will be on top in 56 | // Stack 1. After adding the values of each child onto 57 | // Stack 2, the left child's value will be on top of 58 | // the right child's, which is what we want. 59 | } 60 | 61 | // Convert the stack into a vector and return it 62 | vector result; 63 | while(!s2.empty()) { 64 | result.push_back(s2.top()); 65 | s2.pop(); 66 | } 67 | return result; 68 | } 69 | }; 70 | 71 | // Time Complexity: O(N) because each node is looked at a constant number 72 | // of times when it is being processed. It is also takes linear time to 73 | // convert the second stack into a vector 74 | // Space Complxity: O(N) because each of the data structures has as most 75 | // N items in it in the worse case -------------------------------------------------------------------------------- /Solutions/Trees-1/Problem_94_binary_tree_inorder_traversal.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | UCLA ACM ICPC: Interview Track Leetcode Problem Solutions 3 | 4 | Disclaimer: This is not the only valid solution and we do not claim our solutions 5 | to be optimal in terms of runtime or memory usage, we are merely giving example 6 | solutions to questions for learning purposes only 7 | 8 | Binary Tree Inorder Traversal 9 | Leetcode Problem 94 10 | https://leetcode.com/problems/binary-tree-inorder-traversal/ 11 | */ 12 | 13 | // Given a binary tree, return the inorder traversal of its nodes' values. 14 | // Follow up: Recursive solution is trivial, could you do it iteratively? 15 | 16 | /** 17 | * Definition for a binary tree node. 18 | * struct TreeNode { 19 | * int val; 20 | * TreeNode *left; 21 | * TreeNode *right; 22 | * TreeNode() : val(0), left(nullptr), right(nullptr) {} 23 | * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} 24 | * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} 25 | * }; 26 | */ 27 | class Solution { 28 | public: 29 | // Our strategy is to use DFS with a stack. We will have it so that 30 | // whenever we read the top of the stack, it is the next node we have 31 | // to process. We do this by trying to always move left until we're 32 | // forced to do something else 33 | vector inorderTraversal(TreeNode* root) { 34 | // Edge case: If root is NULL, then we don't do a traversal 35 | if(root == NULL) return {}; 36 | 37 | vector result; 38 | stack s; 39 | TreeNode* cur = root->left; 40 | s.push(root); 41 | 42 | while(true) { 43 | // If our current node is not NULL, go left 44 | if(cur != NULL) { 45 | s.push(cur); 46 | cur = cur->left; 47 | } else { 48 | // If there is nothing in the stack at this point, we 49 | // have processed all nodes and are done 50 | if(s.empty()) break; 51 | 52 | // Inorder traversal goes left-current-right. Since we've 53 | // been going left and adding to the stack until we hit 54 | // NULL, we know that the top node on the stack is one 55 | // one we need to process 56 | 57 | // Remove the top of the stack and add its value 58 | cur = s.top(); 59 | s.pop(); 60 | result.push_back(cur->val); 61 | 62 | // After you process your current node, go right 63 | cur = cur->right; 64 | } 65 | } 66 | return result; 67 | } 68 | }; 69 | 70 | // Time Complexity: O(N) because each node is looked at a 71 | // constant number of times 72 | // Space Complexity: O(N) because in the worst case, all 73 | // nodes can be in the stack at all times -------------------------------------------------------------------------------- /images/arraymeme.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uclaacm/advanced-interview-prep20/689bb73cbf5102b821e6e57b2036e6c8f0384ef9/images/arraymeme.jpg -------------------------------------------------------------------------------- /images/dynamicprogrammingmeme.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uclaacm/advanced-interview-prep20/689bb73cbf5102b821e6e57b2036e6c8f0384ef9/images/dynamicprogrammingmeme.PNG -------------------------------------------------------------------------------- /images/graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uclaacm/advanced-interview-prep20/689bb73cbf5102b821e6e57b2036e6c8f0384ef9/images/graph.png -------------------------------------------------------------------------------- /images/heapmeme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uclaacm/advanced-interview-prep20/689bb73cbf5102b821e6e57b2036e6c8f0384ef9/images/heapmeme.png -------------------------------------------------------------------------------- /images/linkedlistimg.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uclaacm/advanced-interview-prep20/689bb73cbf5102b821e6e57b2036e6c8f0384ef9/images/linkedlistimg.PNG -------------------------------------------------------------------------------- /images/linkedlistmeme.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uclaacm/advanced-interview-prep20/689bb73cbf5102b821e6e57b2036e6c8f0384ef9/images/linkedlistmeme.jpg -------------------------------------------------------------------------------- /images/p105.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uclaacm/advanced-interview-prep20/689bb73cbf5102b821e6e57b2036e6c8f0384ef9/images/p105.PNG -------------------------------------------------------------------------------- /images/p15.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uclaacm/advanced-interview-prep20/689bb73cbf5102b821e6e57b2036e6c8f0384ef9/images/p15.PNG -------------------------------------------------------------------------------- /images/p207.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uclaacm/advanced-interview-prep20/689bb73cbf5102b821e6e57b2036e6c8f0384ef9/images/p207.PNG -------------------------------------------------------------------------------- /images/p23.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uclaacm/advanced-interview-prep20/689bb73cbf5102b821e6e57b2036e6c8f0384ef9/images/p23.PNG -------------------------------------------------------------------------------- /images/p236.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uclaacm/advanced-interview-prep20/689bb73cbf5102b821e6e57b2036e6c8f0384ef9/images/p236.PNG -------------------------------------------------------------------------------- /images/p3.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uclaacm/advanced-interview-prep20/689bb73cbf5102b821e6e57b2036e6c8f0384ef9/images/p3.PNG -------------------------------------------------------------------------------- /images/p30.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uclaacm/advanced-interview-prep20/689bb73cbf5102b821e6e57b2036e6c8f0384ef9/images/p30.PNG -------------------------------------------------------------------------------- /images/p33.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uclaacm/advanced-interview-prep20/689bb73cbf5102b821e6e57b2036e6c8f0384ef9/images/p33.PNG -------------------------------------------------------------------------------- /images/p347.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uclaacm/advanced-interview-prep20/689bb73cbf5102b821e6e57b2036e6c8f0384ef9/images/p347.PNG -------------------------------------------------------------------------------- /images/p416.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uclaacm/advanced-interview-prep20/689bb73cbf5102b821e6e57b2036e6c8f0384ef9/images/p416.PNG -------------------------------------------------------------------------------- /images/p48.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uclaacm/advanced-interview-prep20/689bb73cbf5102b821e6e57b2036e6c8f0384ef9/images/p48.PNG -------------------------------------------------------------------------------- /images/p61.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uclaacm/advanced-interview-prep20/689bb73cbf5102b821e6e57b2036e6c8f0384ef9/images/p61.PNG -------------------------------------------------------------------------------- /images/p62.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uclaacm/advanced-interview-prep20/689bb73cbf5102b821e6e57b2036e6c8f0384ef9/images/p62.PNG -------------------------------------------------------------------------------- /images/p659.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uclaacm/advanced-interview-prep20/689bb73cbf5102b821e6e57b2036e6c8f0384ef9/images/p659.PNG -------------------------------------------------------------------------------- /images/p70.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uclaacm/advanced-interview-prep20/689bb73cbf5102b821e6e57b2036e6c8f0384ef9/images/p70.PNG -------------------------------------------------------------------------------- /images/p92.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uclaacm/advanced-interview-prep20/689bb73cbf5102b821e6e57b2036e6c8f0384ef9/images/p92.PNG -------------------------------------------------------------------------------- /images/p92ill.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uclaacm/advanced-interview-prep20/689bb73cbf5102b821e6e57b2036e6c8f0384ef9/images/p92ill.PNG -------------------------------------------------------------------------------- /images/p94.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uclaacm/advanced-interview-prep20/689bb73cbf5102b821e6e57b2036e6c8f0384ef9/images/p94.PNG -------------------------------------------------------------------------------- /images/pr11.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uclaacm/advanced-interview-prep20/689bb73cbf5102b821e6e57b2036e6c8f0384ef9/images/pr11.PNG -------------------------------------------------------------------------------- /images/slidingwindow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uclaacm/advanced-interview-prep20/689bb73cbf5102b821e6e57b2036e6c8f0384ef9/images/slidingwindow.png -------------------------------------------------------------------------------- /images/treememe.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uclaacm/advanced-interview-prep20/689bb73cbf5102b821e6e57b2036e6c8f0384ef9/images/treememe.jpg --------------------------------------------------------------------------------