Understanding the Basics
Before you can excel at programming problems, you’ll need to understand the basics of programming. An Introduction to Computer Science course at your school will give you that knowledge, but if you need another option, Harvard offers a free online version of their CS50 course here.
Data Structures & Algorithms
Once you understand coding principles, you can move on to Data Structures (which handle/shape information in various ways) and Algorithms (sets of repeatable operations that perform some task). Your school will likely offer both of these courses, and we recommend taking them as soon as possible. Our DS&A Cheat Sheet is not meant to teach you the concepts but instead to supplement courses and give you a brief reminder about each important topic.
Finding Patterns
There are thousands of programming problems online, but only a few dozen “types” or “patterns” of problems. So, if you become familiar with most/all of the patterns, you’ll be in great shape to face any question that comes your way. The first thing you should do when you read a problem is think about problems you’ve already seen that are similar in any way. Remembering techniques you already know is the key here, as solving a tough problem is nearly impossible to do without having previously seen a similar one. We’ll cover the different types of problems you can expect to face in the next section.
Debugging
When you’re practicing and your code isn’t working, don’t just Google or ChatGPT the answer. You won’t be able to do that during your interview, so you should practice debugging in these types of situations. There are three main reasons why your code is failing: it’s just wrong, you’re missing an edge case, and it’s taking too long.
1. It’s Just Wrong
Firstly, your code may just be wrong. If this is the case, you’ll see that many/all test cases failed. You’ll want to re-read the problem description and your code to check for mistakes. You can also compare the output that your code returned to the correct output to see if there are obvious errors. If none of that works, walk through your code with an input that is failing, and track what your function is doing. Eventually, you’ll find the place where there’s a mistake.
2. You're Missing an Edge Case
Secondly, you might be missing an edge case, which will cause only one or two test cases to fail. Usually these edge cases will be hidden inputs, but if they’re not, you can add an “if” statement at the top of your code that directly returns the correct answer for a particular edge case. Otherwise, you’ll want to consider all possible inputs, especially empty inputs, negative inputs, zero inputs, etc. If there are two inputs to the function, think about what your code will do if one is much longer than the other, or if both are empty, etc. As you get more experienced with these problems, you’ll find that remembering edge cases becomes second nature.
3. Your Code Is Taking Too Long
Thirdly, your code might be taking too long to run, and if that’s the case you’ll see an error that says “time limit exceeded.” Usually, this means that one of the test cases uses a massive input, and your code isn’t efficient enough. If there is a way to solve the problem in O(n) time, Leetcode will usually include a test case that causes the “time limit exceeded” error if your code takes longer than O(n). When you see this happen, consider more efficient ways to design the solution. Think about Algorithms like Binary Search and Merge Sort or Data Structures like Heaps and Hashmaps. There will be a solution that gets your code below the time limit, you just have to find it.
If all else fails, start printing things out. You won’t have a debugger during interviews, so you have to get used to solving problems without one. When your code gets complex, it can be daunting to figure out which line is broken. By printing a variable throughout your code, you can identify where your algorithm goes off track. Debugging like this is a great skill to learn because Software Engineers constantly use it (for good reason!).
Speed
One of the most important aspects of Leetcode questions is speed. Time is recorded on automated assessments, and live interviewers often want to see you complete multiple questions in 45-60 minutes. The more you practice, the quicker you’ll be able to digest the problem, choose the right strategy to solve it, and write the code. Here’s what you’re trying to avoid:
You see a question and you have a vague idea that binary search might be useful, but you’re not sure. You decide to try it, but you’re not quite sure how to code binary search. After 25 minutes and lots of debugging, you’re still getting an error for one edge case. You eventually solve this problem but run out of time and can’t complete the next one.
And here’s what you’re trying to do instead:
You see a question and immediately know that binary search is the best and most efficient way to solve it. You know exactly how to code binary search and write the code in 3 minutes. You missed one edge case, but you know what’s wrong and can fix it right away. You can move on to the next question with plenty of time to spare.
Time and Space Complexity
Time and space complexity are crucial when coding. Time complexity is a measurement of how long your code takes to run, given an input of size n (this could be integers in an array, items in a dictionary, etc.). Space complexity is how much memory your code uses, also given input size n. These measurements aren’t precise and don’t include constants (e.g. O(2n+5) is just O(n)). Similarly, lower order operations are ignored, so O(n2 + n) is just O(n2). Check out our Asymptotic Notation module for more. Additionally, this site has a full guide on time and space complexity, so read through it if you’re not familiar with this content.
If you’re stuck on a problem and think there might be a more efficient way to solve it, consider these tips:
- If your code ever does the same work twice, it can be improved.
- More code can sometimes be quicker. For example, writing three separate for loops that go through an array once is much better than writing a double for loop for that same array (O(n) compared to O(n2)).
- Searching in a sorted list can be done in O(lgn) with binary search
- Sorting any list can be done in O(nlgn) with mergesort, quicksort, etc.
- Sorting a list with a small range of values can be done in O(n) with counting sort
- Using different types of data structures affects both time and space complexity, so these must be weighed against each other. For the purposes of Leetcode questions, sacrificing space to save time is the better option.