“Know algorithms!” When you’re searching for jobs in tech, this is frequent advice. Most articles about algorithms and coding challenges seem to recommend practicing them so much that you‘ll recognize almost anything an interviewer might ask you. Practicing algorithms and explaining the solutions out loud are very important — you want to be able to explain your ideas — but problem solving in software and web development requires more than rote memorization.
Computer Science is a science of abstraction.
Being abstract is something profoundly different from being vague… The purpose of abstraction is not to be vague, but to create a new semantic level in which one can be absolutely precise.
Good code is abstract, so let’s apply that same logic to our problem solving! These steps are not specific and can be applied to most code challenges.
We only need a few oft-repeated steps to solve any code challenge.
- Understand the Problem
- Choose a general direction
- Identify what you can do
- Search for what you cannot do (and need to do)
- After finishing, try to improve it
We’ll use LeetCode.com’s problem 130, “Surrounded Regions” for examples. If you’re looking for the optimized answer, skip down to the section Try to Improve It at any time.
Understand the Problem
Thoroughly understand the problem before you dive in. Ask questions, if necessary! You can do it! They wouldn’t ask this if you couldn’t.
I chose this problem for many reasons. First of all, this question isn’t obvious. It’s not a question we have probably ever been asked before. Read it as many times as needed and study the expected input and output (if available).
This problem wants us to only leave Os that have a path to a border. “Surrounded Regions” of Os are converted to Xs.
Choose a general direction
When working on projects, this might mean deciding where the solution should go. Is this better suited for the frontend or the backend? In the frontend, is this the right component for that logic? In the backend, how can we use RESTful routes? Do we want that RESTful route to always perform this logic?
Identify What You Can Do
Do anything you can do that moves towards solving the problem, no matter how small. Even getting “Hello, World!” on the screen is better than a blank screen.
Especially if you’re feeling overwhelmed, make sure the code is behaving as expected. Console.log or print! Maybe we don’t know much, but we now know the input (if we didn’t before) and we know that we wrote our function correctly. Any confidence boost is welcome!
For the LeetCode problem, I used console.log to print the board, then its rows, and finally the characters in the rows — just to make sure I was iterating over the entire input correctly.
What can we do next? A huge, huge part of problem solving is breaking it down into smaller chunks. Even if it’s minuscule, what do you know how to do next?
I chose to see if I could write code to identify which Os were on the border.
return y===0 || y===board.length-1 || x===1 || x===board[y].length-1
Sometimes in coding, it’s easier or more helpful to do the opposite.
Great! What small step can we take next? Let’s check that Os neighbors to see if they are Os!
The intent was to eventually iterate over an array and add all of the O neighbors to that array, thus moving across the “board,” finding the group of Os that the original interior O belonged to. Then, if any of the Os were on the exterior, that original O could stay. However, this got confusing and ugly quickly, so there must be a better way. If you’re feeling confused during your own projects, it’s time to circle back to the steps Understand the Problem and Choose a General Direction, or maybe a more specific direction, now. Here, we will continue and I will explain my problem solving chronologically.
Search for What You Cannot Do
If you have an idea but are not sure how to implement it, search for the answer online! Maybe you haven’t learned a certain function of your computer language yet, or maybe someone has a piece of the puzzle you can borrow!
In this problem, I searched for iterating over a growing array and found that functions assume the source data is not mutated, so that was a no-go. My own experiments within LeetCode yielded similar results — using “array.forEach…” never continued with the information that was added (.push()) onto the array, but “for(let i=0;…” occasionally did. I didn’t understand why the “for” loops were inconsistent, so I labeled that approach unlikely to succeed.
In short, you can learn a ton from this step, and that inspired me to combine a few of the above ideas.
First, I iterated over each character on the board and put the Os that were on the border in an array to be iterated over later. I put the coordinates of the Os in the array because they were necessary for moving across the board and changing the character, if necessary. Those coordinates had to be in a string to be found by .includes(), so I separated them by a dash just in case some test boards had indices in double digits.
Then, I used a “for” loop to iterate over the border Os and add all of their neighboring Os to the same array (still in the crazy string format). For some reason, I was still having trouble with recursion, so I used a “for” loop, instead. This “for” loop worked with the expansion of its source array, gathering all of the Os with any degree of separation from the border. It checked to make sure not to add them twice, otherwise we would be in an infinite loop.
Lastly, the code iterated over the board characters again and changed each O to an X if its coordinates were not in the borderVeins array.
Try to Improve It
Success! I clicked “submit” and saw “Accepted.” Yes! And it also said “Faster than 15% of submissions” and “Your memory usage beats 32% of submissions.” We can do better!
Play around with the code! Try to make it more efficient.
After that, look at some other people’s answers! Both LeetCode and CodeWars have other answers available. I clicked on the answer with the most votes and looked at it just long enough to see that
- They iterated over the code multiple times, changing border Os and their groups to 1s and then back to Os at the end
- They used recursion!
I carefully studied the recursion, even though it was in C++ — You can learn about other languages like this on LeetCode, too! Keeping those details in mind and aiming not to directly copy the entire answer, I headed back to my code to optimize.
Ta-da! The code looks much cleaner, and LeetCode tells us that it also runs faster with less memory usage. Wonderful!
The best reward is proving the little negative voices in your head wrong. You did it! It was just imposter syndrome. You can solve these, as long as you keep believing you can. Stick to these steps, with an emphasis on taking small steps that you know you can accomplish, and go solve more problems!