Home > Reflections | ⏮️ ⏭️
2024-06-02
🧠 Education
🧠🏰 What makes something memorable?
a-simple-way-to-learn-complex-skills
🏋️ Practice
Revisiting this problem from yesterday.
🎉 Yesterday, while I was happy to get the correct solution on the first try (minus a single “typo” that I found very quickly)
(which I can thank manual testing for… I found so many bugs in the initial implementation)
⌛ It took 60 minutes.
⚡ In an interview, I’ll need to complete a problem in something closer to 15 minutes.
So how can we improve?
Well, what took so long?
The problem is very fiddly.
There are null pointers to check and next pointers to rewire…
It takes a lot of focus and working memory to keep track of all of that state while coding up a solution AND while testing the solution.
So rather than doing everything in a single pass…
We can split the problem into simpler sub problems
In this case, we
1. collect the nodes of each list into its own array
This handles all of the next-pointer dereferencing (and associated null checks)
It’s also nicely extractable into its own helper.
We implement it in isolation, which is easier (and less error-prone) than implementing the whole problem
And we use it in 2 places in the larger problem.
Importantly, we also return a more well-typed object to work with.
An array of ListNodes, none of which are null.
2. merge-sort the arrays
This is the same basic logic that was at the core of yesterday’s solution.
But it’s easier because there aren’t any null checks to worry about.
(Although, checking for an empty array is similar.. but we still have fewer checks to worry about)
3. link the nodes in the merge-sorted array
Given an array of nodes, it’s very simple to link one to the next.
🥁 Results
🎉 16:29
Dramatically faster.
Initial implementation was faster (<7m).
Testing was much faster (<10m).
It’s amazing how much faster testing is when we don’t find any bugs…
Also, I didn’t test the helper functions.
Perhaps it’s valuable to do so (assuming we have the time) but a simpler implementation is easier to be confident in.
Takeaways
- Consider alternative solutions
- Aim to decompose problems into simpler, easier sub-problems.
Problem statement
You are given the heads of two sorted linked lists
list1
andlist2
.
Merge the two lists into one sorted list. The list should be made by splicing together the nodes of the first two lists.
Return the head of the merged linked list.
Solution
🏋 More Practice
LeetCode 2486. Append Characters to String to Make Subsequence
Problem Statement
You are given two strings
s
andt
consisting of only lowercase English letters.
Return the minimum number of characters that need to be appended to the end ofs
so thatt
becomes a subsequence ofs
.
A subsequence is a string that can be derived from another string by deleting some or no characters without changing the order of the remaining characters.
🪞 Reflections
- I didn’t immediately understand the problem, but rather than dwell on the statement for too long, I moved to the examples to clarify my understanding
- I attempted to guess the answers to the examples before looking at the provided answers
- Solving the example problems in my head gave me some intuition for an algorithmic approach
- I didn’t consider any alternative approaches.
Is this bad?
I’m not sure.
It seemed clear that this approach was optimal in terms of worst case algorithmic complexity (if reading the input is O(N), and you must read the whole input to solve the problem, you can’t do any better than O(N)).
The approach also seemed simple enough to implement.
Based on these factors, I decided that my time was probably best spent getting right into the implementation rather than trying to imagine alternative approaches.
It seemed to work out in this example, but maybe I just got lucky.
In general, it’s probably still worth attempting to consider alternative approaches, but I’m not really sure if it’s always worth the time in an interview context. - I’m finding this method of manual testing to be pretty effective:
- Write out my manual tests as comments describing the state of key variables as I walk through the code
- Write the state for changed variables under the line of code that changes them.
- I don’t know why, but it seems more intuitive for me for the comments to be under the line of code.
- When I started using this technique a few days ago, I tried writing the comment above the line of code responsible for the state change, but when referencing previously written lines of state, I found myself erroneously looking below the lines of code naturally, which is what prompted me to change.
- Prefix the comment with the test case number
- When in a loop, append the loop number to the test number
- write the name of the variable = state
- explicitly writing the name of the variable in the comment avoids later confusion
- and copy/paste doesn’t take much time (especially with vim keybindings:
yyp
to copy and paste, then edit as necessary)
- for non-primitive objects, an intuitive short-hand seems to be a good enough representation
- e.g.
[c,b,a]
is a good enough representation of a stack containing 3 single-character strings
- e.g.
- if a single line changes multiple variables, annotate the new state of both variables on the same line, separated by a space
- Definitely test. Always test. Practicing the annotation technique makes it faster and I always find bugs, sometimes quite a few of them, and typically pretty quickly.
- I’m not focused on it right now because I’ve already kinda sold myself on it, but the planning step is also incredibly valuable. Think about what to do before doing it. Writing it down clarifies thought and extends our memory.