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
--------------------------------------------------------------------------------