├── .gitignore ├── README.md ├── binary-search-tree └── README.md ├── build-tool ├── README.md └── solution.py ├── counting-islands ├── README.md └── solution.py ├── intersection └── README.md ├── joker.jpg ├── kth-largest └── README.md ├── largest-product └── README.md ├── letter-board └── README.md ├── merge-intervals ├── README.md └── solution.py ├── mergesort ├── README.md └── solution.py ├── monotonic ├── README.md ├── solution.go └── solution.py ├── reverse-collection └── README.md ├── reverse-html ├── README.md ├── solution.py └── solution2.py ├── shift-integers ├── README.md └── solution.py ├── starts-in-group └── README.md ├── subsequence ├── README.md └── solution.py ├── target-number ├── README.md └── solution-sinwoobang.py ├── tic-tac-toe ├── README.md └── solution.py └── two-stack-queue ├── README.md └── solution.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | .mypy_cache 3 | .pytest_cache 4 | *.swp 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Interview Questions 2 | 3 | Job interviews sometimes can be a daunting task. Especially when you're 4 | confronted with those problems with algorithms and data structures. 5 | 6 | I'm sure you've been through a lot when you were in school. Linked lists, 7 | binary trees, directed acyclic graphs, hash maps, greedy algorithm, divide and 8 | conquer, dynamic programming, depth-first search, breadth-first search, and all 9 | those good stuff. Not to mention space and time complexity analysis. 10 | 11 | Even though computer science classes were a pain in the \*\*\*, somehow 12 | you managed to complete them all and made your friends and family proud of 13 | you. You will definitely remember all those all-nighter days with your 14 | classmates trying to build a compiler, a file system, and a network router. It 15 | was traumatizing. You realized you can push yourself much further than you 16 | thought possible. 17 | 18 | Even with years of hard work, you probably wouldn't claim yourself as a master 19 | of computer science, because you know there is much more to be learned. But at 20 | least you had some level of confidence that you understand the fundamentals of 21 | computer science. 22 | 23 | Soon after you got yourself a job as a software engineer. You started building 24 | awesome stuff. Years have passed by, and things got rusty. Even though you were 25 | writing code on a daily basis, you weren't always concerned with implementing 26 | algorithms and data structures yourself. In many cases, you were working on the 27 | top of some libraries and frameworks to get things done. 28 | 29 | More years have passed by, and you started thinking about taking a different 30 | set of challenges outside your company. You started responding to recruiting 31 | messages on LinkedIn that you have been ignoring. Writing a new resume, phone 32 | screening, and online coding tests are usual hurdles to jump over. 33 | 34 | Then here we go. You made all the way through the final round of your hiring 35 | process and you were invited to an on-site interview. You are standing in front 36 | of a big whiteboard. Your interviewer is asking you to write some code to merge 37 | two lists of sorted integers. It is supposed to be an *easy* problem, but 38 | writing code on a whiteboard without syntax highlighting, without 39 | auto-complete, without any assistance from a compiler or an interpreter 40 | whatsoever is a completely different experience when compared to writing code 41 | with a highly advanced integrated development environment (IDE) that reads your 42 | mind to write as much as half of the code on your behalf. 43 | 44 | 45 | 46 | 47 | 48 | I recently had serveral job interviews with different companies. Some 49 | interviews went well, others were a bit more challenging than they are supposed 50 | to be. I've got a few offers and one rejection so far. Some are still 51 | in progress and one of them is highly likely going to turn out as a rejection. 52 | 53 | I was very lucky that one of the companies that I had an interview with gave me 54 | detailed feedback. They shared comments from each of the four interviewers, which 55 | is very unusual. In essence, they had a positive evaluation of the behavioral 56 | interview (communication skills, personality, cultural fit, leadership, etc.) 57 | and the system design interview (designing scalable systems at extreme sizes), 58 | but they had some doubts about my coding abilities. Regardless of the outcome, 59 | I sincerely appreciate that kind of feedback. That's what keeps me moving 60 | forward. 61 | 62 | It's okay to fail. It's okay to make mistakes. As [Dr. 63 | Hong](http://www.romela.org/dr-dennis-hong/) said, *you can't always win, but 64 | you can always learn*. I've created this repository in an attempt to *learn 65 | from mistakes*. 66 | 67 | 68 | ## This Repository 69 | 70 | This repository contains some on-site interview questions that I have 71 | encountered throughout the last few years. I'm still working on providing 72 | solutions for some of the problems. No particular company name is mentioned, 73 | but those questions are general enough that they can be asked in any tech 74 | interview. 75 | 76 | The solutions are the best recollection of mime, but could be slightly 77 | different from what I actually wrote down on a whiteboard during the interview. 78 | There is plenty of room for improvement for each solution I provided, but I 79 | wanted to show what kind of code I was able to write under a harsh environment 80 | (a whiteboard, time constraints, psychological pressures, etc.) 81 | 82 | 83 | ## Practice 84 | 85 | Keep in mind that: 86 | 87 | Getting a complete, working solution is important. Pseudo-code is not enough. 88 | Interviewers are generally okay with any language of your choice among popular 89 | ones (C/C++, Java, Python, etc.) but I'd avoid using bizarre languages like 90 | [Whitespace](https://en.wikipedia.org/wiki/Whitespace_(programming_language)), 91 | [Brainfuck](https://en.wikipedia.org/wiki/Brainfuck), or 92 | [아희](https://aheui.github.io/specification.en). No matter what language you 93 | use, practice to get *working* code with some time limit. Try to get all the 94 | boundary conditions right. 95 | 96 | You are generally expected to write code on a whiteboard or on a basic text 97 | editor without syntax highlighting. You should be able to run the code in your 98 | head comfortably. It may be acceptable to write some fuzzy code that partially 99 | works then gradually improve by running the code with a compiler or an 100 | interpreter in a real work environment. But that is not how things work at a 101 | job interview. For this reason, writing code on an actual whiteboard or a piece 102 | of paper is the best way to prepare yourself for tech interviews. If you still 103 | insist typing code on your favorite editor, at least try to write code at the 104 | entirety, improve the code if possible, simulate all possible edge cases, fix 105 | any bug you found. When you think all is good, run your code to see if your 106 | code really works. 107 | 108 | Last but not least, practice working under tight time constraint. Depending on 109 | the format of your interview and the level of complexity of the problems, you 110 | will be given 10-45 minutes per each problem. It is highly unlikely that you 111 | will encounter anything super complicated, but problems can be quite 112 | challenging under time constraint. 113 | 114 | 115 | ## Time Limits 116 | 117 | In many cases, a final round consists of multiple interview sessions. The 118 | longest one I have had so far was five sessions long. Each interview session 119 | runs for about 50-60 minutes. Generally, you and your interviewer will spend 120 | some time introducing yourself, with regards to your current role and your past 121 | experience, to the other and vice versa. Your interviewer may ask you some 122 | behavioral questions. Most interviewers want to leave a few minutes at the end 123 | of the session for you to ask some questions. That leaves about 30-40 minutes 124 | at most for the coding part itself. 125 | 126 | For each problem description, I wrote down how much time I had for that 127 | particular problem. 128 | 129 | 130 | ## Contribution 131 | 132 | Feel free to submit your solutions via pull requests. Some rules to follow: 133 | 134 | - Your solution must be contained in a single file. 135 | - Avoid relying on external libraries and frameworks. 136 | - File name should be in a format of `solution-{your GitHub username}.{ext}`. 137 | For example, if your GitHub username is `johndoe` and your solution is 138 | written in C++, your filename should be `solution-johndoe.cpp` 139 | -------------------------------------------------------------------------------- /binary-search-tree/README.md: -------------------------------------------------------------------------------- 1 | Binary Search Tree 2 | ================== 3 | Given a binary tree, determine whether it is a binary search tree. 4 | 5 | Time limit: 20min -------------------------------------------------------------------------------- /build-tool/README.md: -------------------------------------------------------------------------------- 1 | Build Toold 2 | =========== 3 | 4 | Problem: Suppose we are making a build tool that takes a list of paths 5 | containing source files. The build tool produces a list of paths that it 6 | actually has to work on. 7 | 8 | When building code for a particular directory, source files in all its 9 | subdirectories are handled. For example, both `/a/b/c` and `/a/b` are given, 10 | the build tool only needs to work on `/a/b` as all files under `/a/b/c` are 11 | already handled when working on `/a/b`. 12 | 13 | The build tools is also expected to perform deduplications of directories. 14 | 15 | Time limit: 40 min 16 | -------------------------------------------------------------------------------- /build-tool/solution.py: -------------------------------------------------------------------------------- 1 | class Node(object): 2 | 3 | def __init__(self, name): 4 | self.name = name 5 | self.is_leaf = False 6 | self.children = {} 7 | 8 | 9 | def build(paths): 10 | head = Node('') 11 | for path in paths: 12 | node = head 13 | for name in path.split('/')[1:]: 14 | if name in node.children: 15 | node = node.children[name] 16 | else: 17 | child = Node(name) 18 | node.children[name] = child 19 | node = child 20 | node.is_leaf = True 21 | 22 | return head 23 | 24 | 25 | def traverse(node, prev=[], depth=0): 26 | if node.is_leaf: 27 | yield '/'.join(prev + [node.name]) 28 | else: 29 | for key, child in node.children.items(): 30 | for x in traverse(child, prev + [node.name], depth + 1): 31 | yield x 32 | 33 | 34 | def test_traverse(): 35 | paths = [ 36 | '/a/b/c', 37 | '/a/b/d', 38 | '/a/b', 39 | '/c', 40 | '/c', 41 | ] 42 | 43 | result = list(traverse(build(paths))) 44 | assert result == ['/a/b', '/c'] 45 | -------------------------------------------------------------------------------- /counting-islands/README.md: -------------------------------------------------------------------------------- 1 | Counting Islands 2 | ================ 3 | 4 | Problem: A world is described as an `n`-by-`m` 2D array of `0`s and `1`s. `0` 5 | and `1` represent water and land respectively. An islands is defined as a chunk 6 | of land surrounded by water. 7 | 8 | Either horizontally or vertically consecutive series of `1`s is considered as a 9 | distinct island. Diagonally adjacent `1`s are not considered as a single 10 | island. 11 | 12 | For example, the following world contains five islands. 13 | 14 | ``` 15 | 1 1 0 1 0 0 1 0 16 | 1 0 0 1 1 1 0 0 17 | 0 0 1 1 1 0 0 1 18 | 0 0 1 1 1 0 0 1 19 | 1 0 0 1 0 0 1 1 20 | 1 1 1 0 0 1 1 0 21 | ``` 22 | 23 | You may assume that no island contains a lake (water surrounded by land). 24 | 25 | Time limit: 45 min 26 | -------------------------------------------------------------------------------- /counting-islands/solution.py: -------------------------------------------------------------------------------- 1 | def count_islands(world, x, y): 2 | h = len(world) 3 | w = len(world[0]) 4 | 5 | count = 0 6 | for j in range(0, h): 7 | for i in range(0, w): 8 | if world[j][i] != 0: 9 | count += 1 10 | mark_world(world, i, j) 11 | 12 | return count 13 | 14 | 15 | def mark_world(world, x, y): 16 | queue = [] 17 | queue.append((x, y)) 18 | while queue: 19 | i, j = queue.pop(0) 20 | if in_boundary(world, i, j): 21 | if world[j][i] != 0: 22 | world[j][i] = 0 23 | queue.append((i - 1, j)) 24 | queue.append((i + 1, j)) 25 | queue.append((i, j - 1)) 26 | queue.append((i, j + 1)) 27 | 28 | 29 | def in_boundary(world, x, y): 30 | h = len(world) 31 | w = len(world[0]) 32 | 33 | return (0 <= x < w) and (0 <= y < h) 34 | 35 | 36 | def test_1(): 37 | world = [ 38 | [0, 1, 0], 39 | [1, 1, 0], 40 | [0, 0, 1], 41 | ] 42 | assert count_islands(world, 0, 0) == 2 43 | 44 | 45 | def test_2(): 46 | world = [ 47 | [1, 1, 0, 1, 0, 0, 1, 0], 48 | [1, 0, 0, 1, 1, 1, 0, 0], 49 | [0, 0, 1, 1, 1, 0, 0, 1], 50 | [0, 0, 1, 1, 1, 0, 0, 1], 51 | [1, 0, 0, 1, 0, 0, 1, 1], 52 | [1, 1, 1, 0, 0, 1, 1, 0], 53 | ] 54 | assert count_islands(world, 0, 0) == 5 55 | -------------------------------------------------------------------------------- /intersection/README.md: -------------------------------------------------------------------------------- 1 | Intersection 2 | ============ 3 | 4 | Given two sorted lists of integers, find the intersection of the two lists. 5 | 6 | For example, suppose two lists are given as: 7 | 8 | xs = [1, 2, 3, 5, 8, 13, 21, 34] 9 | ys = [3, 6, 9, 12, 15, 18, 21] 10 | 11 | Then your solution should return `[3, 21]`. 12 | 13 | Time limit: 10 min 14 | 15 | Variation 16 | --------- 17 | What if one list is significantly larger than the other? For example, one 18 | contains billions of elements whereas the other only contains several. Would 19 | it possible to come up with a more efficient solution? 20 | -------------------------------------------------------------------------------- /joker.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suminb/interview-questions/1afe34345aae7b9ba311e20888b392bb0c28ab3b/joker.jpg -------------------------------------------------------------------------------- /kth-largest/README.md: -------------------------------------------------------------------------------- 1 | k-th Largest Element 2 | ==================== 3 | 4 | Problem: Given a list of unsorted integers, find the k-th largest element in 5 | linear time. 6 | -------------------------------------------------------------------------------- /largest-product/README.md: -------------------------------------------------------------------------------- 1 | Largest Product 2 | =============== 3 | 4 | Given a list of unsorted integers and a positive integer `k`, draw `k` elements 5 | from the list that makes the largest product. 6 | 7 | Suppose the following input is given, 8 | 9 | [1, -9, 5, 0, 7, 3, -8, -1], k = 3 10 | 11 | Then we would like to return `[-9, -8, 7]`. You may assume `k` is equal to or less than the number of elements in the list. 12 | 13 | What are the time complexity and the space complexity of your solution? 14 | 15 | Time limit: 20 min 16 | -------------------------------------------------------------------------------- /letter-board/README.md: -------------------------------------------------------------------------------- 1 | Letter Board 2 | ============ 3 | 4 | NOTE: This was for an online coding test, rather than for an on-site interview. 5 | I was provided with a basic editor, [CoderPad](https://coderpad.io), with no 6 | typical IDE-like features whatsoever. The only thing comes in handy was 7 | auto-indentation. 8 | 9 | Problem: Given an `N` by `N` board with letters and a word, determine whether 10 | the word appears on the board. 11 | 12 | - You may traverse in any (up, down, right, left, all diagonal) direction 13 | - You may change the direction at each step 14 | - You may *not* re-use a letter at the same location 15 | 16 | For example, a board may look like this: 17 | 18 | ``` 19 | T Z J Q 20 | A R E E 21 | P B A S 22 | C F X H 23 | ``` 24 | 25 | Then you will find words like `TREE`, `TAR`, `BEE`, `SEA`, and so on. 26 | 27 | Time limit: 45 min 28 | -------------------------------------------------------------------------------- /merge-intervals/README.md: -------------------------------------------------------------------------------- 1 | Merge Intervals 2 | =============== 3 | 4 | Problem 1: Merge two intervals, where an interval is defined as a tuple of 5 | `[min, max]`. If those two intervals have no *overlapping*, return the original 6 | input as-is. The output is a list of intervals. 7 | 8 | Suppose two intervals are given as `[1, 3]`, `[2, 5]` then the merged interval 9 | is `[[1, 5]]`. When `[1, 2]`, `[5, 7]` are given, then the output shall be 10 | `[[1, 2], [5, 7]]`. 11 | 12 | Problem 2: Merge `k` intervals. 13 | 14 | Time limit: 15 min 15 | -------------------------------------------------------------------------------- /merge-intervals/solution.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | def merge(interval1, interval2): 5 | min1, max1 = interval1 6 | min2, max2 = interval2 7 | 8 | min_ = min(min1, min2) 9 | max_ = max(max1, max2) 10 | 11 | if max_ - min_ <= (max1 - min1) + (max2 - min2): 12 | return [(min_, max_)] 13 | else: 14 | return [interval1, interval2] 15 | 16 | 17 | def merge_all(*intervals): 18 | raise NotImplemented 19 | 20 | 21 | @pytest.mark.parametrize('interval1, interval2, expected', [ 22 | ((1, 3), (2, 5), [(1, 5)]), 23 | ((3, 8), (2, 5), [(2, 8)]), 24 | ((1, 2), (5, 7), [(1, 2), (5, 7)]), 25 | ((5, 6), (1, 2), [(5, 6), (1, 2)]), 26 | ]) 27 | def test_merge(interval1, interval2, expected): 28 | assert merge(interval1, interval2) == expected 29 | 30 | 31 | @pytest.mark.skip 32 | def test_merge_all(): 33 | intervals = [(1, 4), (2, 5), (3, 4), (7, 8), (8, 9)] 34 | expected = [(1, 5), (7, 9)] 35 | assert merge_all(*intervals) == expected 36 | -------------------------------------------------------------------------------- /mergesort/README.md: -------------------------------------------------------------------------------- 1 | Merge Sort 2 | ========== 3 | 4 | Problem 1: Given two lists of sorted integers (in ascending order), merge 5 | them into a single list. 6 | 7 | Problem 2: Discuss an algorithm to merge `k` lists. Analyze time complexity of 8 | the algorithm. Can you make it faster? (Hint: *O(nk log k)* is possible) 9 | 10 | Problem 3: Implement merge sort algorithm using the function above. 11 | 12 | Time limit: 45 min 13 | -------------------------------------------------------------------------------- /mergesort/solution.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | def merge(l1, l2): 5 | i = j = 0 6 | n, m = len(l1), len(l2) 7 | 8 | merged = [] 9 | while i < n and j < m: 10 | if l1[i] < l2[j]: 11 | merged.append(l1[i]) 12 | i += 1 13 | else: 14 | merged.append(l2[j]) 15 | j += 1 16 | 17 | return merged + l1[i:] + l2[j:] 18 | 19 | 20 | def sort(ls): 21 | n = len(ls) 22 | if n > 1: 23 | mid = n // 2 # integer division 24 | return merge(sort(ls[:mid]), sort(ls[mid:])) 25 | else: 26 | return ls 27 | 28 | 29 | @pytest.mark.parametrize('l1, l2, expected', [ 30 | ([], [], []), 31 | ([], [1, 2, 3], [1, 2, 3]), 32 | ([0, 1, 2], [], [0, 1, 2]), 33 | ([1, 3, 5, 7], [2, 4, 6], [1, 2, 3, 4, 5, 6, 7]), 34 | ]) 35 | def test_merge(l1, l2, expected): 36 | assert merge(l1, l2) == expected 37 | 38 | 39 | @pytest.mark.parametrize('ls, expected', [ 40 | ([], []), 41 | ([1], [1]), 42 | ([2, 1], [1, 2]), 43 | ([3, 0, 4, -5, 2, 7, 8], [-5, 0, 2, 3, 4, 7, 8]), 44 | ]) 45 | def test_sort(ls, expected): 46 | assert sort(ls) == expected 47 | -------------------------------------------------------------------------------- /monotonic/README.md: -------------------------------------------------------------------------------- 1 | Monotonic Lists 2 | =============== 3 | 4 | Problem: Given a list of integers, determine whether the list is monotonic. 5 | That is, determine whether its elements are either never-increasing or 6 | never-decreasing. In mathematical sense, *never-increasing* means 7 | *ai ≥ ai+1* whereas *never-decreasing* means 8 | *ai ≤ ai+1*. 9 | 10 | Examples of monotonic lists: 11 | 12 | - `[1, 2, 3, 4]` 13 | - `[4, 3, 2, 1]` 14 | - `[0, 0, 0, 0]` 15 | 16 | Examples of non-monotonic lists: 17 | 18 | - `[1, 2, 1, 2]` 19 | 20 | Time limit: 10 min 21 | -------------------------------------------------------------------------------- /monotonic/solution.go: -------------------------------------------------------------------------------- 1 | // NOTE: I've written this code without any prior Go experience whatsoever, so 2 | // there is most likely plenty of room for improvement. I didn't write code in 3 | // Go in the real interview :p 4 | 5 | package main 6 | 7 | func isMonotonic(list []int) bool { 8 | n := len(list) 9 | if n <= 2 { 10 | return true 11 | } else { 12 | diff := make([]int, n-1) 13 | for i := 0; i < n-1; i++ { 14 | diff[i] = list[i+1] - list[i] 15 | } 16 | 17 | flag1, flag2 := true, true 18 | for i := 0; i < n-1; i++ { 19 | flag1 = flag1 && diff[i] <= 0 20 | flag2 = flag2 && diff[i] >= 0 21 | } 22 | 23 | return flag1 || flag2 24 | } 25 | } 26 | 27 | func assert(v bool) { 28 | if !v { 29 | panic("Assertion failed") 30 | } 31 | } 32 | 33 | func main() { 34 | assert(isMonotonic([]int{})) 35 | assert(isMonotonic([]int{0})) 36 | assert(isMonotonic([]int{1, 2, 3, 4})) 37 | assert(isMonotonic([]int{4, 3, 2, 1})) 38 | assert(isMonotonic([]int{0, 0, 0, 0})) 39 | assert(!isMonotonic([]int{1, 2, 1, 2})) 40 | } 41 | -------------------------------------------------------------------------------- /monotonic/solution.py: -------------------------------------------------------------------------------- 1 | def is_monotonic(xs): 2 | delta = [x - y for x, y in zip(xs[1:], xs[:-1])] 3 | return all([d >= 0 for d in delta]) or all([d <= 0 for d in delta]) 4 | 5 | 6 | def test_is_monotonic(): 7 | assert is_monotonic([1, 2, 3, 4]) 8 | assert is_monotonic([4, 3, 2, 1]) 9 | assert is_monotonic([0, 0, 0, 0]) 10 | assert not is_monotonic([1, 2, 1, 2]) 11 | -------------------------------------------------------------------------------- /reverse-collection/README.md: -------------------------------------------------------------------------------- 1 | Functionally Reverse Collection 2 | =============================== 3 | 4 | Given an interface `Collection` that provides the following functionalities, 5 | write a function to reverse a collection. 6 | 7 | ```java 8 | interface Collection { 9 | boolean isEmpty(); 10 | T first(); 11 | T last(); 12 | Collection dropFirst(); 13 | Collection dropLast(); 14 | Collection append(T); 15 | Collection empty(); 16 | } 17 | ``` 18 | 19 | Time limit: 10min 20 | -------------------------------------------------------------------------------- /reverse-html/README.md: -------------------------------------------------------------------------------- 1 | Reverse HTML 2 | ============ 3 | 4 | Problem: Reverse an HTML document. Text and HTML shall be reversed but HTML 5 | code must be kept in a valid form. 6 | 7 | - Input example: `Hello World` 8 | - Output example: `dlroW olleH` 9 | 10 | You may assume: 11 | 12 | - HTML elements contain no attributes. 13 | - HTML elements always have opening and closing. No single element 14 | (e.g., `
`). 15 | 16 | Time limit: 40 min 17 | -------------------------------------------------------------------------------- /reverse-html/solution.py: -------------------------------------------------------------------------------- 1 | # NOTE: I don't think I can jot this down on a whiteboard in 40 2 | # minutes, and that leads me to believe that there should be a 3 | # simpler solution. 4 | 5 | 6 | class Node(object): 7 | 8 | def __init__(self, parent, mode, data): 9 | self.mode = mode 10 | self.data = data 11 | self.parent = parent 12 | self.children = [] 13 | 14 | 15 | class Token(object): 16 | 17 | def __init__(self, mode, data): 18 | self.mode = mode 19 | self.data = data 20 | 21 | def __repr__(self): 22 | if self.mode == 'text': 23 | return self.data 24 | elif self.mode == 'tag_open': 25 | return '<{0}>'.format(self.data) 26 | elif self.mode == 'tag_close': 27 | return ''.format(self.data) 28 | 29 | def __eq__(self, other): 30 | return self.mode == other.mode and self.data == other.data 31 | 32 | 33 | def reverse(html): 34 | tokens = tokenize(html) 35 | root = Node(None, 'text', '') 36 | build(tokens, root) 37 | 38 | reversed_tokens = [] 39 | traverse(root, reversed_tokens) 40 | 41 | return ''.join([str(t) for t in reversed_tokens]) 42 | 43 | 44 | def traverse(node, tokens): 45 | if node.mode == 'tag_open': 46 | tokens.append(Token('tag_open', node.data)) 47 | for child in node.children[::-1]: 48 | traverse(child, tokens) 49 | tokens.append(Token('tag_close', node.data)) 50 | elif node.mode == 'text': 51 | tokens.append(Token(node.mode, node.data[::-1])) 52 | for child in node.children[::-1]: 53 | traverse(child, tokens) 54 | 55 | 56 | def build(tokens, prev_node): 57 | if tokens: 58 | token = tokens[0] 59 | node = Node(prev_node, token.mode, token.data) 60 | if token.mode == 'text': 61 | prev_node.children.append(node) 62 | build(tokens[1:], prev_node) 63 | elif token.mode == 'tag_open': 64 | prev_node.children.append(node) 65 | build(tokens[1:], node) 66 | elif token.mode == 'tag_close': 67 | # prev_node.append(node) 68 | build(tokens[1:], node.parent) 69 | 70 | 71 | def tokenize(html): 72 | tokens = [] 73 | text = '' 74 | tag = '' 75 | mode = 'text' 76 | for c1, c2 in zip(html, html[1:] + ' '): 77 | if c1 == '<': 78 | if text: 79 | tokens.append(Token(mode, text)) 80 | text = '' 81 | 82 | if c2 == '/': 83 | mode = 'tag_close' 84 | else: 85 | mode = 'tag_open' 86 | elif c1 == '/': 87 | pass 88 | elif c1 == '>': 89 | tokens.append(Token(mode, tag)) 90 | tag = '' 91 | mode = 'text' 92 | else: 93 | if mode == 'text': 94 | text += c1 95 | elif mode in ['tag_open', 'tag_close']: 96 | tag += c1 97 | 98 | if mode == 'text' and text: 99 | tokens.append(Token(mode, text)) 100 | elif mode == 'tag_close' and tag: 101 | tokens.append(Token(mode, tag)) 102 | 103 | return tokens 104 | 105 | 106 | def test_tokenize(): 107 | data = 'Hello World' 108 | expected = [ 109 | Token('text', 'Hello '), 110 | Token('tag_open', 'i'), 111 | Token('text', 'W'), 112 | Token('tag_open', 'b'), 113 | Token('text', 'orld'), 114 | Token('tag_close', 'b'), 115 | Token('tag_close', 'i'), 116 | ] 117 | assert tokenize(data) == expected 118 | 119 | 120 | def test_1(): 121 | data = 'Reverse HTML' 122 | expected = 'LMTH esreveR' 123 | assert reverse(data) == expected 124 | 125 | 126 | def test_2(): 127 | data = 'Hello World' 128 | expected = 'dlroW olleH' 129 | assert reverse(data) == expected 130 | -------------------------------------------------------------------------------- /reverse-html/solution2.py: -------------------------------------------------------------------------------- 1 | # This is a second trial. This time, I wrote down the code on a piece of paper, 2 | # trying to make it as completely as possible, and then copied it to a text 3 | # editor. Besides a couple of typing errors and one minor mistake that can be 4 | # easily found and corrected, everything worked as expected. That one minor 5 | # mistake was this: 6 | # 7 | # I passed `tag_buf` when creating `Tag` instance where I was supposed to pass 8 | # `''.join(tag_buf)`. 9 | # 10 | # This is definitely easier than building a tree and traversing it, and I was 11 | # able to finish coding (on paper) in about 15 minutes or so. 12 | 13 | # TODO: This violates the file naming convention mentioned in README. Need to 14 | # come up with a suitable name. 15 | 16 | class Tag: 17 | 18 | def __init__(self, tag, opening=True): 19 | self.tag = tag 20 | self.opening = opening 21 | 22 | def __str__(self): 23 | if self.opening: 24 | return f'<{self.tag}>' 25 | else: 26 | return f'' 27 | 28 | def flip_mode(self): 29 | self.opening = not self.opening 30 | return self 31 | 32 | 33 | def tokenize(html): 34 | tag_buf = [] 35 | tag_mode = None 36 | tokens = [] 37 | 38 | for c in html: 39 | if tag_mode: 40 | if c == '/': 41 | tag_mode = 'close' 42 | elif c == '>': 43 | tokens.append(Tag(''.join(tag_buf), tag_mode == 'open')) 44 | tag_buf = [] 45 | tag_mode = None 46 | else: 47 | tag_buf.append(c) 48 | elif c == '<': 49 | tag_mode = 'open' 50 | else: 51 | tokens.append(c) 52 | 53 | return tokens 54 | 55 | 56 | def reverse_tokens(tokens): 57 | def process(token): 58 | if isinstance(token, Tag): 59 | return str(token.flip_mode()) 60 | else: 61 | return token 62 | 63 | return ''.join([process(t) for t in tokens[::-1]]) 64 | 65 | 66 | def reverse(html): 67 | return reverse_tokens(tokenize(html)) 68 | 69 | def test_1(): 70 | data = 'Reverse HTML' 71 | expected = 'LMTH esreveR' 72 | assert reverse(data) == expected 73 | 74 | 75 | def test_2(): 76 | data = 'Hello World' 77 | expected = 'dlroW olleH' 78 | assert reverse(data) == expected 79 | -------------------------------------------------------------------------------- /shift-integers/README.md: -------------------------------------------------------------------------------- 1 | Shift Integers 2 | ============== 3 | 4 | Problem: Given a list of integers, shift non-zeros to the left (or shift zeros 5 | to the right). The order of elements must be preserved. For example, suppose we 6 | are given a list of integers like: 7 | 8 | [1, 3, -4, 0, 9, 2, 0, 5] 9 | 10 | Then the outcome is expected to be: 11 | 12 | [1, 3, -4, 9, 2, 5, 0, 0] 13 | 14 | Expected time complexity: O(n) 15 | Expected space complexity: O(1) 16 | 17 | Time limit: 15 min 18 | -------------------------------------------------------------------------------- /shift-integers/solution.py: -------------------------------------------------------------------------------- 1 | def shift(ls): 2 | i = c = 0 3 | n = len(ls) 4 | 5 | while i < n: 6 | m = ls[i] 7 | if c > 0: 8 | ls[i - c] = ls[i] 9 | if m == 0: 10 | c += 1 11 | i += 1 12 | 13 | for i in range(n - c, n): 14 | ls[i] = 0 15 | 16 | return ls 17 | 18 | 19 | def test(): 20 | data = [1, 3, -4, 0, 9, 2, 0, 5] 21 | expected = [1, 3, -4, 9, 2, 5, 0, 0] 22 | assert shift(data) == expected 23 | -------------------------------------------------------------------------------- /starts-in-group/README.md: -------------------------------------------------------------------------------- 1 | Stars In Group 2 | ============== 3 | 4 | Problem: There is an N by N grid and a set of letters in the grid. The value of 5 | `N` ranges from `MIN_INT` to `MAX_INT`, so in a 64-bit architecture the grid 6 | would represent a quite large space and the letters are sparsely located. A 7 | group is defined as adjacent letters in the grid, except that diagonally 8 | neighboring letters are not considered belonging to the same group. Given a 9 | coordinate `(x, y)`, write a function to return a list of all letters in the 10 | group to which the letter is belonging. 11 | 12 | For example, let's suppose the letters are located as follows: 13 | 14 | (-1, 0): A 15 | (0, 0): B 16 | (0, 1): C 17 | (2, 0): X 18 | (3, 1): Y 19 | 20 | Visually speaking, letters are located as: 21 | 22 | C Y 23 | A B X 24 | 25 | Given a coordinate `(0, 0)`, all letters belonging in that particular group are 26 | A, B, and C and thus the function shall return `[A, B, C]`. If the coordinate 27 | is given as `(2, 0)` then the function should return `[X]` as `Y` is diagonally 28 | adjacent to `X` and hence belonging to a different group. If a coordinate of an 29 | empty space, say `(1, 1)`, if given then the function should return an empty 30 | list. 31 | 32 | Time limit: 20 min -------------------------------------------------------------------------------- /subsequence/README.md: -------------------------------------------------------------------------------- 1 | Consecutive Subsequence 2 | ======================= 3 | 4 | Problem: Given a list of integers and a target number, which is an integer, 5 | determine whether there exists a consecutive subsequence that adds up to the 6 | target number. You may assume all integers in the list are non-negative for 7 | now. 8 | 9 | For example, 10 | 11 | list = [1, 4, 5, 3, 2], target = 8 12 | 13 | then your function returns `true`. If there is no such subsequence that adds 14 | up to `target` then it returns `false`. 15 | 16 | Time limit: 15 min 17 | 18 | Variation 1 19 | ----------- 20 | Find the subsequence. For example, 21 | 22 | list = [1, 4, 5, 3, 2], target = 8 23 | 24 | then your function returns `[5, 3]`. If there are multiple subsequences 25 | satisfying the constraint, you may return one of the valid solutions. 26 | 27 | Variation 2 28 | ----------- 29 | 30 | The list may contain negative numbers. Hint: there is a solution that runs in linear time. 31 | -------------------------------------------------------------------------------- /subsequence/solution.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | # TODO: Deal with negative integers 5 | def find(seq, target): 6 | n = len(seq) 7 | s = 0 8 | i = j = 0 9 | 10 | while True: 11 | if s == target: 12 | break 13 | elif s < target and j < n: 14 | s += seq[j] 15 | j += 1 16 | elif s > target and i < n: 17 | s -= seq[i] 18 | i += 1 19 | 20 | return seq[i:j] 21 | 22 | 23 | @pytest.mark.parametrize('seq, target, expected', [ 24 | ([1, 2, 3, 4], -1, []), 25 | ([1, 2, 3, 4], 1, [1]), 26 | ([1, 2, 3, 4], 7, [3, 4]), 27 | ]) 28 | def test_find(seq, target, expected): 29 | assert find(seq, target) == expected 30 | -------------------------------------------------------------------------------- /target-number/README.md: -------------------------------------------------------------------------------- 1 | Target Number 2 | ============= 3 | 4 | Problem: Given a list of sorted integers in ascending order and a target number 5 | `k` which is an integer, determine whether it is possible to pick two integers 6 | from the list that add up to the target number. 7 | 8 | For example, 9 | 10 | list = [-5, 0, 2, 3, 7, 8], k = 9 11 | 12 | then `2 + 7 = 9`, hence the answer is `true`. If `k = 1` the answer is `false` 13 | as no two numbers from the list add up to `1`. 14 | 15 | Time complexity: Should be faster than *O(n2)*. 16 | 17 | Time limit: 15 min 18 | -------------------------------------------------------------------------------- /target-number/solution-sinwoobang.py: -------------------------------------------------------------------------------- 1 | """A solution using Set. Time Complexity is O(N).""" 2 | from typing import List, Set 3 | 4 | 5 | def is_possible(nums: List[int], k: int): 6 | nums_set: Set[int] = set(nums) # It takes O(n). 7 | for num in nums: # It takes O(n) as well. 8 | remain = k - num 9 | if remain in nums_set: # It takes O(1). 10 | return True 11 | return False 12 | 13 | 14 | def test_1(): 15 | nums = [-5, 0, 2, 3, 7, 8] 16 | k = 9 17 | assert is_possible(nums, k) 18 | 19 | 20 | def test_2(): 21 | nums = [-5, 0, 2, 3, 7, 8] 22 | k = 1 23 | assert not is_possible(nums, k) 24 | -------------------------------------------------------------------------------- /tic-tac-toe/README.md: -------------------------------------------------------------------------------- 1 | Tic-tac-toe 2 | =========== 3 | 4 | Problem: Given an `n`-by-`n` 5 | [Tic-tac-toe](https://en.wikipedia.org/wiki/Tic-tac-toe) board, write a 6 | function to determine whether there is a winner. 7 | 8 | A winner is a player who succeeded in placing `n` of their marks (`O` or `X`) 9 | in a horizontal, vertical, or diagonal row. 10 | 11 | Time limit: 40 min 12 | -------------------------------------------------------------------------------- /tic-tac-toe/solution.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | def judge(board) -> bool: 5 | """Make a judgement whether there is a winner. 6 | 7 | :param board: N-by-N array 8 | """ 9 | if not board: 10 | return False 11 | 12 | n = len(board) 13 | # Check for horizontal lines 14 | if any([judge_line([board[i][j] for j in range(n)]) for i in range(n)]): 15 | return True 16 | # Check for vertical lines 17 | if any([judge_line([board[i][j] for i in range(n)]) for j in range(n)]): 18 | return True 19 | # Check for diagonal lines 20 | if judge_line([board[i][i] for i in range(n)]): 21 | return True 22 | if judge_line([board[i][n - i - 1] for i in range(n)]): 23 | return True 24 | 25 | return False 26 | 27 | 28 | def judge_line(line) -> bool: 29 | return all([x == y for x, y in zip(line[:-1], line[1:])]) 30 | 31 | 32 | @pytest.mark.parametrize('board, expected', [ 33 | ([], False), 34 | ([ 35 | ['O'], 36 | ], True), 37 | ([ 38 | ['O', 'X'], 39 | ['X', 'O'], 40 | ], True), 41 | ([ 42 | ['O', 'X', 'O'], 43 | ['O', 'X', 'X'], 44 | ['X', 'X', 'O'], 45 | ], True), 46 | ([ 47 | ['O', 'X', 'O', 'X'], 48 | ['O', 'X', 'X', 'O'], 49 | ['X', 'O', 'O', 'X'], 50 | ['X', 'O', 'O', 'X'], 51 | ], False), 52 | ]) 53 | def test_judge(board, expected): 54 | actual = judge(board) 55 | assert expected == actual -------------------------------------------------------------------------------- /two-stack-queue/README.md: -------------------------------------------------------------------------------- 1 | Two Stacks As Queue 2 | =================== 3 | 4 | Problem: Implement a queue using two stacks. Then discuss how things will work 5 | out in a multithreading environment. 6 | 7 | You don't need to implement a stack yourself. 8 | 9 | Time limit: 10 min 10 | -------------------------------------------------------------------------------- /two-stack-queue/solution.py: -------------------------------------------------------------------------------- 1 | stack1, stack2 = [], [] 2 | 3 | 4 | def enqueue(x): 5 | stack1.append(x) 6 | 7 | 8 | def dequeue(): 9 | if stack2: 10 | return stack2.pop() 11 | 12 | while stack1: 13 | stack2.append(stack1.pop()) 14 | 15 | return stack2.pop() 16 | 17 | 18 | def test_1(): 19 | enqueue(1) 20 | enqueue(2) 21 | enqueue(3) 22 | 23 | assert dequeue() == 1 24 | assert dequeue() == 2 25 | assert dequeue() == 3 26 | 27 | 28 | def test_2(): 29 | enqueue(1) 30 | assert dequeue() == 1 31 | 32 | enqueue(2) 33 | enqueue(3) 34 | assert dequeue() == 2 35 | 36 | enqueue(4) 37 | assert dequeue() == 3 38 | assert dequeue() == 4 39 | --------------------------------------------------------------------------------