Skip to content
/ code Public

πŸ‘¨πŸ»β€πŸ’» A Hands-on Approach to Hacking Coding Interviews

Notifications You must be signed in to change notification settings

ofou/code

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Code

This guide has taken me...

This is my hands-on approach to hacking the Coding Interview:

  1. Tracking coding time with Wakatime or Git. (What you can't measure, you can't improve)
  2. Solve coding problems on a daily basis and commit every problem (min 30m/day)
  3. Read technical books, and reimplement key data structures and algorithms in a high-level language such as python and when needed in a low-level one like c
  4. Take notes, make videos, or in-depth tutorials, teaching is one of the best ways to learn
  5. Give back to the open source community, helping projects is a great way to test your knowledge in the wild. Start with Exercism's tracks, FreeCodeCamp's curriculum, CodeCrafters's projects, CodeAcademy's docs, or even this repo. πŸ™‚β€β†”οΈ
Optional: Enhance your learning abilities
  • Thinking, Fast and Slow by Daniel Kahneman

    • Explores two systems of thinking: fast (intuitive) and slow (deliberative), revealing how they shape our judgments and decisions. Kahneman offers insights into avoiding cognitive biases and making better choices by understanding these mental processes.
  • Deep Work by Cal Newport

    • Argues that the ability to focus without distraction on cognitively demanding tasks is increasingly valuable in our economy. Newport provides strategies for cultivating deep work habits to achieve focused success in a world full of shallow distractions.
  • Mindset: The New Psychology of Success by Carol S. Dweck

    • Introduces the concept of "fixed" versus "growth" mindsets, demonstrating how believing in the potential to develop abilities leads to greater achievement and fulfillment. Dweck offers practical strategies for fostering a growth mindset in various life domains.
  • Ultralearning by Scott Young

    • Presents a blueprint for aggressive self-directed learning, outlining nine principles of ultralearning illustrated through stories of remarkable achievements. Young shows how focused, intense, and strategic learning can lead to rapid skill acquisition.
  • The Art of Learning: An Inner Journey to Optimal Performance by Josh Waitzkin

    • Drawing from his experiences as a chess prodigy and martial arts champion, Waitzkin shares insights on achieving peak performance through embracing challenges, maintaining a growth mindset, and developing resilience in any field.
  • A Mind for Numbers: How to Excel at Math and Science (Even If You Flunked Algebra) by Barbara Oakley

    • Offers practical advice for succeeding in math and science, based on Oakley's personal journey of overcoming difficulties in these subjects. Covers effective study techniques, memory strategies, and the importance of practice in developing mathematical and scientific skills.
  • How We Learn: The Surprising Truth About When, Where, and Why It Happens by Benedict Carey

    • Gets into cognitive science research to reveal the mechanics of learning, debunking common study myths and providing evidence-based techniques to enhance memory, understanding, and problem-solving skills.
  • The Art of Doing Science and Engineering: Learning to Learn by Richard Hamming

    • A renowned mathematician and computer scientist shares insights into scientific and engineering discovery, emphasizing the crucial roles of curiosity, creativity, and persistence in tackling complex problems and making significant contributions to these fields.

Explore Top Interview Coding Problems

So the first step will be doing the following exercises from these platforms:

  • Khan Academy: Introductory exercises to get you started with the basics of algorithms. Great intro using Javascript & Python. The materials were developed by Dartmouth College professors Tom Cormen & Devin Balkcom, the first is the same author as the famous book Introduction to Algorithms.
  • Exercism: Great open source platform to master any programming language itself, from the basics to advanced topics. Mentorship is free.
  • HackerRank: They have Preparation Kits ranging from 1-week, 1-month to 3-months. Companies such as Amazon use the platform to interview candidates.
  • LeetCode: Popular coding platform, the place-to-go to prepare for technical interviews. They just released a systematic program for excelling at interviews called 75.
    • NeetCode: This is a new coding website based on a LeetCode problems selection. The cool thing is that they have code walkthroughs for each problem on Youtube and solutions in many languages. Great to match with LeetCode 75.
  • Advent of Code: This is a yearly event that takes place in December. It's a great way to practice coding problems in a fun game.
  • Coding Interview Prep: The Coding Interview Prep by freeCodeCamp contains hundreds of coding challenges that test your knowledge of algorithms, data structures, and mathematics.
  • Deep-ML: This is an open-source machine learning challenge platform designed to help users improve their skills in algorithm development and AI applications.
  • Tensor Puzzles: This is a collection of 21 tensor puzzles. Like chess puzzles these are not meant to simulate the complexity of a real program, but to practice in a simplified environment. Each puzzle asks you to reimplement one function in the NumPy standard library without magic.

Also consult Visualgo for visualizing data structures and algorithms through animation. Other great resources to consider are: High Load, The Euler Project (free), and Rosetta Code (free).

Platform Track Time Problems
Khan Academy Intro to algorithms - 25
Exercism Python _ 131
HackerRank Interview Prep Kit _ 104
LeetCode 75 Study Plan _ 75
NeetCode NeetCode 1 150
Advent of Code Yearly _ 252
Deep ML Problems - 26
Tensor Puzzles Problems - 21

If you're into reading very technical documents β€”hardcore modeβ€” you can check out the Python docs, C C23 Language Standard, and CUDA Programming Guide, or CUDA Best Practices. These resources are more advanced, but they're excellent for learning about the languages in depth, reimplementing some of the functions, and becoming very familiar with the core concepts from the very source.

Guide to Data Structures and Algorithms

Personally, what I did was to start brushing up on the basics of data structures and algorithms through easy reading Grokking Algorithms and Grokking Data Structures. Both are pretty updated books from last year, and I just wanted to get a bit more familiar with the ideas.

Arrays seems like the most fundamental data structure, so the first thing you should be able to do it's to work with that.

Arrays

There are two types of arrays: static and dynamic. Static arrays have a fixed size, which means the size must be defined at compile time and cannot be changed during runtime. Dynamic arrays, on the other hand, can grow and shrink in size as needed during program execution. Understanding the difference between these two types is crucial for efficient memory management and performance optimization.

class DynamicArray:
"""
A dynamic array class with list-like interface.
>>> arr = DynamicArray()
>>> len(arr)
0
"""
def __init__(self, capacity=1):
"""
Initialize the dynamic array with an initial capacity.
>>> arr = DynamicArray()
>>> arr.capacity
1
>>> len(arr)
0
"""
self.capacity = capacity
self.length = 0
self.array = [None] * self.capacity
def __len__(self):
"""
Return the number of elements.
>>> arr = DynamicArray()
>>> arr.append(1)
>>> len(arr)
1
"""
return self.length
def __getitem__(self, index):
"""
Retrieve an item by index or slice.
>>> arr = DynamicArray()
>>> arr.extend([1, 2, 3])
>>> arr[0]
1
>>> arr[1:3]
[2, 3]
"""
if isinstance(index, slice):
start, stop, step = index.indices(self.length)
return [self.array[i] for i in range(start, stop, step)]
if not (0 <= index < self.length):
raise IndexError("Index out of bounds")
return self.array[index]
def __setitem__(self, index, value):
"""
Set the item(s) at the given index or slice.
>>> arr = DynamicArray()
>>> arr.extend([1, 2, 3])
>>> arr[1] = 10
>>> arr[1]
10
"""
if isinstance(index, slice):
start, stop, step = index.indices(self.length)
if hasattr(value, "__iter__"):
vals = list(value)
if step == 1 and (stop - start) == len(vals):
for i, v in enumerate(vals):
self.array[start + i] = v
else:
raise NotImplementedError(
"Advanced slice assignment not fully implemented"
)
else:
raise ValueError("Can only assign an iterable to a slice")
else:
if not (0 <= index < self.length):
raise IndexError("Index out of bounds")
self.array[index] = value
def _resize(self, new_capacity):
"""
Resize the underlying array storage (internal method).
>>> arr = DynamicArray()
>>> arr._resize(5)
>>> arr.capacity
5
"""
new_array = [None] * new_capacity
for i in range(self.length):
new_array[i] = self.array[i]
self.array = new_array
self.capacity = new_capacity
def append(self, item):
"""
Append an item to the end of the array.
>>> arr = DynamicArray()
>>> arr.append(99)
>>> arr[0]
99
>>> len(arr)
1
"""
if self.length == self.capacity:
self._resize(2 * self.capacity)
self.array[self.length] = item
self.length += 1
def extend(self, iterable):
"""
Extend the array with items from the given iterable.
>>> arr = DynamicArray()
>>> arr.extend([1, 2, 3])
>>> list(arr)
[1, 2, 3]
"""
for item in iterable:
self.append(item)
def pop(self, index=None):
"""
Remove and return item at index, default last.
>>> arr = DynamicArray()
>>> arr.extend([1,2,3])
>>> arr.pop()
3
>>> arr.pop(0)
1
>>> list(arr)
[2]
"""
if self.length == 0:
raise IndexError("pop from empty list")
if index is None:
index = self.length - 1
if not 0 <= index < self.length:
raise IndexError("Index out of bounds")
item = self.array[index]
for i in range(index, self.length - 1):
self.array[i] = self.array[i + 1]
self.array[self.length - 1] = None
self.length -= 1
if self.length <= self.capacity // 4 and self.capacity > 1:
self._resize(max(self.capacity // 2, 1))
return item
def remove(self, value):
"""
Remove first occurrence of value.
>>> arr = DynamicArray()
>>> arr.extend([1,2,3,2])
>>> arr.remove(2)
>>> list(arr)
[1, 3, 2]
"""
idx = self.find(value)
if idx == -1:
raise ValueError("list.remove(x): x not in list")
self.pop(idx)
def clear(self):
"""
Clear the array.
>>> arr = DynamicArray()
>>> arr.extend([1,2,3])
>>> arr.clear()
>>> len(arr)
0
"""
self.capacity = 1
self.length = 0
self.array = [None] * self.capacity
def count(self, value):
"""
Return the number of occurrences of value.
>>> arr = DynamicArray()
>>> arr.extend([2,2,3])
>>> arr.count(2)
2
"""
c = 0
for i in range(self.length):
if self.array[i] == value:
c += 1
return c
def index(self, value, start=0, end=None):
"""
Return the index of the first occurrence of value.
>>> arr = DynamicArray()
>>> arr.extend([5,6,7])
>>> arr.index(6)
1
"""
if end is None or end > self.length:
end = self.length
for i in range(start, end):
if self.array[i] == value:
return i
raise ValueError("list.index(x): x not in list")
def __str__(self):
"""
User-friendly string representation.
>>> arr = DynamicArray()
>>> arr.extend([1,2])
>>> str(arr)
'[1, 2]'
"""
elements = [str(self.array[i]) for i in range(self.length)]
return "[" + ", ".join(elements) + "]"
def find(self, item):
"""
Return the index of item or -1 if not found.
>>> arr = DynamicArray()
>>> arr.extend([4,5,6])
>>> arr.find(5)
1
>>> arr.find(999)
-1
"""
for i in range(self.length):
if self.array[i] == item:
return i
return -1
def insert(self, index, item):
"""
Insert an item at the given index.
>>> arr = DynamicArray()
>>> arr.extend([1,2,3])
>>> arr.insert(1, 99)
>>> list(arr)
[1, 99, 2, 3]
"""
if not 0 <= index <= self.length:
raise IndexError("Index out of bounds")
if self.length == self.capacity:
self._resize(self.capacity * 2)
for i in range(self.length, index, -1):
self.array[i] = self.array[i - 1]
self.array[index] = item
self.length += 1
def reverse(self):
"""
Reverse the array in-place.
>>> arr = DynamicArray()
>>> arr.extend([1,2,3])
>>> arr.reverse()
>>> list(arr)
[3, 2, 1]
"""
for i in range(self.length // 2):
self.array[i], self.array[self.length - 1 - i] = (
self.array[self.length - 1 - i],
self.array[i],
)

Use Case: Static arrays are ideal when memory is constrained and the maximum size is known in advance, such as embedded systems or when implementing fixed-size buffers. The size limitation provides safety against buffer overflows.

Use Case: Dynamic arrays are ideal when the size of the data is unknown or changes frequently. This implementation shows how Python lists work under the hood, automatically resizing when capacity is reached.

Binary search

Binary search is a divide-and-conquer algorithm with O(log n) complexity that finds elements in sorted arrays by repeatedly halving the search interval. It's a fast and efficient way to search for elements in large datasets, first or last occurrence, or even the closest element to a target value.

def binary_search(array, target):
"""
Binary search algorithm to find the target element in the array.
Time complexity: O(log n)
Space complexity: O(1)
>>> binary_search([1, 2, 3, 4, 5], 3)
2
>>> binary_search([1, 2, 3, 4, 5], 6)
-1
"""
left, right = 0, len(array) - 1
while left <= right:
mid = (left + right) // 2 # or low + (high - low) // 2
if array[mid] == target:
return mid
elif array[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1

Footnotes

  1. This is based on LeetCode problems for time tracking and might be overlapping with the LeetCode 75 track. ↩

  2. 25 problems / year, but it runs from 2015 up to now. The goal is to solve all the problems. ↩