Tag: leetcode month

  • leetcode day 16: lowest bad version

    Given a function isBadVersion and a range of versions from 1 to N, find the lowest-numbered version that is “bad” as efficiently as possible.

    This is another binary search, and I had a little less trouble with it. A few more and I’ll be able to knock one out right the first time. Since there weren’t any pointers or indexes or arrays to manage, this was easier. Still got tripped up on the order of testing, but when I sat down and delineated the terminal cases by hand I was able to easily code it.

    Performance was excellent: 100% on CPU time, 95% on memory. Would be a win in an interview.

    /** 
     * Forward declaration of isBadVersion API.
     * @param   version   your guess about first bad version
     * @return true if current version is bad 
     *	   false if current version is good
     * func isBadVersion(version int) bool;
     */
    
    func firstBadVersion(n int) int {
        // Binary search it.
        // n is candidate.
        lowBound := 1
        highBound := n
    
        if isBadVersion(lowBound) {
            // short-circuit: if lowest possible version is bad,
            // no search is needed.
            return lowBound
        }
    
        // Now search between the bounds for the switchover point. Low is good,
        // high is bad.
        for {
            fmt.Println("Bad version between", lowBound, highBound)
            midpoint := (highBound+lowBound) / 2
            isBad := isBadVersion(midpoint)
            if isBad {
                highBound = midpoint
                if midpoint == lowBound+1 {
                     // midpoint is 1st bad above lowbound (known good)
                    return midpoint
                }
            } else {
                lowBound = midpoint
                if highBound == midpoint+1 {
                    // midpoint is good, 1 above is bad, so that's it
                    return highBound
                }
            }
        }
    }

    Weirdly, removing the debug made it slower. 81st percentile CPU.

    Which pretty much goes to show that trying to rate efficiency of code via leetcode is probably a bad idea.

  • Leetcode day 15: a queue from two stacks

    An old favorite, which I got at a Google interview. Not understanding the Google mindset at the time (2007 or so), I had my engineer hat on first, and pointed out that if you’re going to build a data structure, build the damn data structure. It’s ludicrous to build two stacks when you could build one queue in approximately the same amount of time.

    However, the real purpose was “do you understand that pushing onto one stack and then popping items off onto the other reverses their order?”. Sure. OK. Fine. You could ask me “what would happen if…”, but no, it needs to be a no-one-with-any-brains-would-ever-do-this programming problem. (Again, this is a “let’s talk about your algorithm choices in our next 1-1” problem.)

    Anyway, yeah. So I tried with arrays again to start and it was a huge PITA. I tried that for about ten minutes and said, forget this, I’m building it with linked lists.

    So we have a Node that is added at the head to push, and delinked to its tail to pop. Easy-peasy.

    The push operation just adds to the incoming stack, and doesn’t do anything else until we have our first pop, and which point we call the core routine here, Cycle. Cycle pops items one at a time off the in stack and pushes them to the out stack. Now a pop pulls the first item that was pushed, as required, since we’ve just reversed the in stack. I did have one error, caught in the submit, in that you don’t just check for the in stack being empty, but you also need to check for the out stack not being empty. If you don’t, you end up queueing an item out of order. Always wait for the output to be empty before actually cycling.

    I did have to refresh my memory on adding a method to a struct – it’s func (this *Node) Cycle – but otherwise this was almost as much a breeze as the tree inversion.

    I do need to do some exercises on managing arrays better, though — you can’t always use a linked list, especially if you need actual indexing.

    Overall, pleased with the solution: 82nd percentile runtime, but 18th percentile memory. Might be better if I kept the popped nodes on a free list.

    type Node struct {
        Val int
        Next *Node
    }
    
    type MyQueue struct {
        In *Node
        Out *Node
        free *Node
    }
    
    func Constructor() MyQueue {
        return MyQueue{
            In: nil,
            Out: nil,
        }
    }
    
    func(this *MyQueue) Cycle() {
        fmt.Println(">>>cycling in to out")
        if this.In == nil {
            // Nothing to cycle
            fmt.Println(">>cycle empty in")
            return
        }
        if this.Out != nil {
            // Don't cycle yet
            return
        }
        // Pop items off the in stack, push on the out stack
        for {
            current := this.In
            this.In = this.In.Next // Pop In
            fmt.Println("cycle>> pop in", current.Val)
            
            current.Next = this.Out // Push Out
            this.Out = current
    
            current = this.In
            if current == nil {
                break
            }
        }
        // Clear in stack
        fmt.Println("cycle>> clear in")
        this.In = nil
    }
    
    func (this *MyQueue) Push(x int)  {
        // Append to in stack. Leave there until we cycle.
        n := &Node{
            Val: x,
            Next: this.In,
        }
        this.In = n
        fmt.Println(">>push on in", x)
    }
    
    
    func (this *MyQueue) Pop() int {
        if this.Empty() {
            panic("popped empty stack")
        }
        if this.Out == nil {
            this.Cycle()
        }
        value := this.Peek()
        fmt.Println(">>pop Out")
        this.Out = this.Out.Next
        return value
    }
    
    
    func (this *MyQueue) Peek() int {
        if this.Empty() {
            panic("peek on empty stack")
        }
        this.Cycle()
        fmt.Println("peek>>")
        return this.Out.Val
    }
    
    
    func (this *MyQueue) Empty() bool {
        fmt.Println("check for empty")
        return this.In == nil && this.Out == nil    
    }
    
    
    /**
     * Your MyQueue object will be instantiated and called as such:
     * obj := Constructor();
     * obj.Push(x);
     * param_2 := obj.Pop();
     * param_3 := obj.Peek();
     * param_4 := obj.Empty();
     */

    Editorial note: other solutions submitted really stretch the definition of stack – a lot of them do do appends and slices, but of a single array, which is kind of not implementing two stacks at all.

    Note 2: adding a free queue of nodes improves memory usage by 1%. Not worth it.

  • leetcode day 13: linked list loop

    Given a linked list, determine if it has a loop.

    I started out with the simplest possible thing that could work: when looking to see if you have more than N of a thing, use a map.

    **
     * Definition for singly-linked list.
     * type ListNode struct {
     *     Val int
     *     Next *ListNode
     * }
     */
    func hasCycle(head *ListNode) bool {
        seen := make(map[*ListNode]bool)
        for scan := head; scan != nil; scan = scan.Next{
             _, ok := seen[scan] 
             if ok {
                return true
            }
            seen[scan] = true
        }
        return false
    }

    Small, simple, but not very fast. 14th percentile in speed, 8th percentile in memory.

    I vaguely remembered that there was a pointer-chasing way to do this, and that’s the fast and small way. (This falls into the “do you know the trick” category, which I kinda hate.)

    func hasCycle(head *ListNode) bool {
        slow := head
        fast := slow
        
        if head == nil {
            return false
        }
        
        for {
            // if we'd run off the end, stop.
            if slow.Next == nil || fast.Next == nil || fast.Next.Next == nil {
                return false
            }
    
            slow = slow.Next
            fast = fast.Next.Next
    
            if fast == slow {
                return true
            }
        }
        return false
    }
    

    Considerably faster: 95th percentile in speed, 45th in memory, which is interesting.

    Now we start getting into “I would fire you if you did this” solutions.

    How about if we just overwrite the value with an impossible one, and if we see the impossible value, return true (because it couldn’t be there unless we put it there)? It’s slower, which is to be expected, since we’re modifying the nodes and looking at them, not just looking at pointers. But surprisingly, the memory usage is still around 43%. And the list contents are trashed.

    /**
     * Definition for singly-linked list.
     * type ListNode struct {
     *     Val int
     *     Next *ListNode
     * }
     */
    const invalidValue = 99999999
    
    func hasCycle(head *ListNode) bool {
        // Destructive version.
        scan := head
        
        if head == nil {
            return false
        }
        
        for {
            // if we'd run off the end, stop.
            if scan.Next == nil {
                return false
            }
            if scan.Val == invalidValue {
                return true
            }
            scan.Val = invalidValue
    
            scan = scan.Next
        }
        return false
    }

    So what gets us better memory usage? How about breaking the links, and pointing them to na-na land, and stopping if we get a pointer to na-na land?

    /**
     * Definition for singly-linked list.
     * type ListNode struct {
     *     Val int
     *     Next *ListNode
     * }
     */
    
    func hasCycle(head *ListNode) bool {
        // Another destructive version.
        scan := head
        
        // Create an off-in-nowhere node.
        tumbolia := &ListNode{9999999, nil}
        
        if head == nil {
            return false
        }
        
        for {
            if scan == tumbolia {
                // we're in na-na land
                return true
            }
    
            // if we'd run off the end, stop.
            if scan.Next == nil {
                return false
            }
    
            // point the next of this node to na-na land.
            here := scan
            scan = scan.Next
            here.Next = tumbolia
        }
        return false
    }

    No major improvement. 90th percentile in speed, 43rd percentile in memory. List structure is trashed.

    So overall, the Floyd two-pointer is the winner; would love to see something that uses less memory, but haven’t found anything yet.

  • leetcode day 13: balanced tree, major fail

    I had a really hard time getting my head around this problem, which contributed to major flailing on my part trying to get it sorted. I had several almost-right solutions, but I kept not getting the recursion right. I eventually sorted it out to “I need to check the subtree heights” as the right attack, but didn’t finally sort it until the 10th try or so.

    This wold absolutely have been a fail in an interview. I’m getting much better with pointers – I had no trouble getting the recursions to work, and never had a nil pointer problem – but I just could not get the concept clear enough in my head, and that lead to my just not getting it right in the code.

    After a lot of flailing I finally came up with a height-checking algorithm that was the best shot at it I could do, but I had to finally check with the solutions to spot my error, which was treating the left and right subtrees separately when they needed to be unified.

    Final version after the fix did okay in the rankings: top 84% in speed and top 81% in memory, but the path to getting it was really bad. I need to spend a lot more time on tree-crawling.

    /**
     * Definition for a binary tree node.
     * type TreeNode struct {
     *     Val int
     *     Left *TreeNode
     *     Right *TreeNode
     * }
     */
    func isBalanced(root *TreeNode) bool {
        if root == nil || (root.Left == nil && root.Right == nil) {
            return true
        }
        _, which := heightCheck(root)
        return which
    }
    
    func heightCheck(root *TreeNode) (int, bool) {
        if root == nil {
            return 0, true
        }
     
        lH, lB := heightCheck(root.Left)
        rH, rB := heightCheck(root.Right)
    
        // I was way overcomplicatng this, tracking the
        // branches separately and not sending along a
        // give-up flag. Doing that helped a lot.
        if !lB || !rB  || abs(lH - rH) > 1 {
            return -1, false
        }
    
        return max(lH, rH) + 1, true
    }
    
    func abs(a int) int {
        if a > 0 {
            return a
        }
        return -a
    }
    
    func maxDepth(root *TreeNode) int {
        if root == nil {
            return 0
        }
    
        left := maxDepth(root.Left)
        right := maxDepth(root.Right)
    
        return max(left, right)+1
    }
    
    
    func max(a,b int) int {
        if a>b {
            return a
        }
        return b
    }

    Interestingly, if one adds a second recursion shortcut check for leaves, the memory usage gets much better: 99th percentile, but the execution time gets much worse: 54th percentile.

  • leetcode month day 3: invert binary tree

    The classic whiteboard problem.

    I chose the “busy software engineer” approach: I know an algorithm that will work and is trivial to implement. Implement that.

    To remind everyone what inverting a tree is: if criterion for insertion is left branch < right, then then output tree should have left branch > right. The dead simple algorithm is top-down: save the left, assign the right to the left, assign the saved to the right, recurse.

    It’s clear, it’s simple, and fast to code. I literally had a working solution in 2 runs (forgot the null root case on the first run).

    /**
     * Definition for a binary tree node.
     * type TreeNode struct {
     *     Val int
     *     Left *TreeNode
     *     Right *TreeNode
     * }
     */
    func invertTree(root *TreeNode) *TreeNode {
        if root == nil {
            return nil
        }
        
        invertTree(root.Left)
        invertTree(root.Right)
    
        t := root.Left
        root.Left = root.Right
        root.Right = t
        
        return root
    }

    About as simple as you can possibly get. Scorewise, it’s fast: top 77%. Memorywise, it’s pretty poor: bottom 8%.

    I’m betting a pointer crawl would use less memory but wouldn’t necessarily be that much faster. Let’s see.

    First note, still writing append as if this were Scala. Sigh.

    This try ends up in an infinite loop; I’m mismanaging the stack somehow. (See why letting Go do it is better?) I’m declaring this good enough and moving on. I expect I could write a version where I manage the stack myself, but this is why we invented higher-level languages.

    Unless our trees are always incredibly deep, this is the right way to do this. Even skipping the recursion for nil subtrees doesn’t really make it faster.

    On the other hand, I solved the initial recursive version in about 5 minutes, so I’m getting my feet back under me.

  • leetcode month day 3: palindrome

    I’m perceiving a pattern in my failures here and it is 100% “did you absolutely understand the requirements”.

    Detecting a palindromic string was the easy part: just put an index at the end and the beginning, and march them toward the middle. If they meet or pass each other, then we succeeded. If at any point the characters at the indexes don’t match, we fail.

    The problematic part was the cleanup of the initial string. Lowercasing wasn’t bad, though I had to look up where the function lived (it’s strings.ToLower()). Filtering the input was harder. I had to go check Stack Overflow and found this code, which is very nice and will go in my personal toolbox:

    func cleanup(str string) string {
        return strings.Map(func(r rune) rune {
            if !unicode.IsDigit(r) && !unicode.IsLetter(r) {
                return -1
            }
            return r
        }, str)
    }

    The strings.Map function is really nice for “do something for every rune in a string”, especially since the special -1 return value says “drop this character”. Can do just about any transliteration you want with that! The unicode.IsDigit function highlights my “you did not read the instructions” error: I assumed that it would be alpha only, and that was incorrect; it was alphanumeric input.

    I did spot a useful optimization: if the string is less than two characters long, by definition it’s a palindrome, so we can skip scanning it altogether.

    Otherwise the algorithm I coded to scan was right the first time.

    How’d I do comparatively? Meh. Runtime in the bottom 50%, memory in the top 60%. I think the lowercase followed by the cleanup is the culprit, and doing it all in one loop would be faster. I don’t think it’s as clear.

    I tried a couple variations, and all of them are worse:

    • Reverse the string and compare it to itself
    • Inline the cleanup

    This just may be something that Go kind of sucks at doing.

    func isPalindrome(s string) bool {
        // lowercase, skim out all non-alpha characters
        s = strings.ToLower(s)
        s = spaceMap(s)
    
        if len(s) < 2 {
            return true
        }
        begin := 0
        end := len(s) - 1
    
        for {
            if begin > end {
                return true
            }
            if begin == end {
                return true
            }
            if s[begin] != s[end] {
                return false
            }
            begin++
            end--
        }
    }
    
    func spaceMap(str string) string {
        return strings.Map(func(r rune) rune {
            if !unicode.IsDigit(r) && !unicode.IsLetter(r) {
                return -1
            }
            return r
        }, str)
    }
  • leetcode month day 3: “best time to buy and sell”

    Didn’t do so well on this one on the first pass.

    You’re given an ordered array of prices, and are asked to find the maximum inter-day profit. Constraints: the buy must be before the sell (first price array index < second price array index), and profit is 0 if there is no positive delta.

    I originally did a brute force solution that essentially came down to a double loop. Worked fine for small examples, but failed on time for long ones.

    So I went and took a shower, and got half of it: if I find the minimum value, then no price after that can possibly have a bigger delta. However, it’s possible that there was a bigger delta preceding that, and I got stuck there for a while, wondering about iterating backward, but couldn’t see a definite siimple algorithm.

    I got a hint that I could use a sliding window, and that gave me the right solution.

    Start at index 0 as the tentative minimum.
    Walk a second index forward.
    If the walking index finds a new minimum, move the minimum pointer.
    Otherwise, calculate the delta between the current minimum and the walking index price.
    If the delta is > the old max delta, save it.

    My original insight would eliminate whatever part of the array that followed the global minimum, but would have missed possible local maximum deltas (e.g., [10, 100, 1, 5] would have a true max delta of 90; scanning only after the global minimum would only have found a delta of 4). The sliding window finds that first delta of 90, and then keeps it, even though we find a new minimum at 1.

    func maxProfit(prices []int) int {
        // iterate over the array from the start.
        // take the current item as the start price.
        // find the max price after this one that leads to a profit, or 0.
        // repeat for each day up to the last-1.
        // if the profit for a given iteration is > the current, save it instead.
        maxProfit := 0
        tentativeMinIndex := 0
    
        for i := tentativeMinIndex; i < len(prices); i++ {
            if prices[i] < prices[tentativeMinIndex] {
                tentativeMinIndex = i
                continue
            }
            // haven't moved minimum, calc profit
            thisProfit := prices[i] - prices[tentativeMinIndex]
            if thisProfit > maxProfit {
                maxProfit = thisProfit
            }
        }
        return maxProfit
    }

    How’d I do? Bad on the initial O(N^2) iteration, but fab on the sliding window version: top 99% in speed, top 93% in memory.

    Taking a shower helped, but I won’t be able to do that in an interview, alas. (Another good argument for working at home.)

    Next is palindromes, and we’ll be back to fun with Go strings again.

  • leetcode month, day 2: Fenceposted by “valid parens”

    Today’s challenge is Valid parens. And it reminded me of several of the things that I don’t particularly like in Go.

    Problem statement boils down to “here’s a string of brackets: parens, square brackets, braces. Tell us if they match properly (true) or don’t (false)”. Algorthmically this is dead simple:

    Start with an empty stack.
    for each character in the string:
      if it's an open bracket, push it.
      if its a close bracket:
        if the stack is empty, return false.
        if the top of the stack isn't the matching open, return false.
        pop the stack.
    
    When we're done, return true if the stack is empty, false otherwise.

    In Perl or Python, I’d have a stack pointer, and move it up and down an array as I pushed and popped characters by altering the stack pointer and assigning to the appropriate point in the array. If the stack pointer went below 0, I’ve gotten too many close parens, and I return false.

    This approach doesn’t work well at all with Go, because Go arrays and slices Do Not Work Like That.

    To push onto an existing array, we need to use a := append(a, new). Just assigning to an index outside of the slice’s current boundaries will panic.

    We could still use the stack pointer to move backward, but then we’d have to have more complex code to decide if we need to append or just move the pointer. (Side note: doing it that way would probably use less memory — the working version of my solution used more memory than 90% of the other solutions). Instead, we just use the slice notation to pop the stack instead, with a := a[:len(a)-1].

    My original iterations with a real stack pointer failed miserably because I wasn’t properly tracking the length of the array, and was perpetually getting the stack pointer position wrong, causing the code to panic. It was only after completely discarding the stack pointer that I got it to finally work.

    Items of note:

    • I remembered I’d need to use range() to iterate over the string, but forgot that I would have to string() the characters I was getting so they were strings instead of runes. I wasted a good chunk of time trying to mess with runes before I realized that I needed string() instead. Runes are considerably more second-class citizens.
    • Still running afoul of using make() properly. Finally figured out the syntax to create an empty string array, but it took some Googling to get it.
    • I decided to make the code less complex by creating a pairs map that mapped left brackets to right ones. This meant I could check pairs[left] == right for whether I’d matched the left and right brackets. It also meant that if we ever added more bracket pairs in the future, it’d be be easier to implement them.
    • I got fenceposted by a[len(a)-1] accessing the last element, and a[:len([a)-1] dropping the last element. I naively expected that since a[len(a)-1] accessed the last element, I’d want a[:len(a)-2] to drop that last element, but that takes one too many, running me into another fencepost. Yes, it’s good that it’s symmetric, and now I’ll remember, but I definitely had to look it up to figure out both of them.
    • Forgot the check for “did we close all the opens”. I probably would not have missed it if I was making my own test cases, but it wasn’t in the tests. Note to self: watch out for poor initial test cases. There is an opportunity to add more, I think?

    So how’d I do overall? We saw I was in the bottom 10% in memory usage, boo, but runtime? “Beats 100.00% of users with Go”.

    I’ll definitely take that as a win, but it’s definitely obvious I need to keep practicing and getting these idioms back under my belt.

    func isValid(s string) bool {
        // We'll run through the string, doing the following:
        // - if we see an open bracket, we stack it.
        // - if we see a close bracket, we pop the stack. If the
        //   brackets match, we continue, otherwise we fail.
        // - when we run out of brackets, the stack must be empty or we fail.
    
        stack := make([]string,0)
    
        pairs := map[string]string{
            "(": ")", 
            "[": "]", 
            "{": "}",
        }
    
        for i := range s {
            char := string(s[i])
           _, ok := pairs[char]
           if ok {
               // left, push it
               stack = append(stack, char)
           } else {
               // right, stack empty?
               if (len(stack) == 0) {
                   fmt.Println("pop of empty stack")
                   return false
               }
               // Not empty. match?
               if (pairs[stack[len(stack)-1]] != char) {
                   fmt.Println ("mismatch")
                   // mismatched bracket, fail
                   return false
               }
               // Match. Pop stack.
               stack = stack[:len(stack)-1]
           }
        }
        // Check for "did we close all the open brackets".
        return len(stack) == 0
    }

    That’s one for today, and I need to clean the house, so I’ll call it a day.

  • leetcode month, day 1: Twosum and Add Two Numbers

    Well, these were fun, and definitely showed me I’m not ready to walk into an interview at the moment!

    Twosum

    Twosum is an easy challenge: you are given an array of numbers and a target value; your job is to find the only two numbers at different indexes in the array that sum to the target value and return those two indexes.

    The brute-force strategy just repeatedly walks through the array, trying to find the right pair of numbers. This ends up being O(n^2) because we essentially run over the upper triangle of a square of the elements of the array> If in the example below were looking for numbers that sum to 9, we’d run over the numbers four times finally finding it on the fourth pass, and doing 4+3+2+1 = 10 checks. (We can save operations by precomputing the second term in the sum: subtract the first search value from the target sum, and just compare that against the value in the array at that index instead of summing the two.)

    >1  2  3  4  5
     1 >2  3  4  5
     1  2 >3  4  5
     1  2  3 >4  5

    But the old Perl adage “when someone says search, consider a hash” comes in handy here. We can precompute exactly the number we want, so if we can make the values the keys of a hash (and we can, as we know that there is only one pair that sums to the target value), then we can make the indexes the values.

    We walk through the array twice:

    • The first iteration builds the hash by taking each value and inserting it as the key with the index as its value. And since we’re doing one iteration over the whole array anyway at this point, we can check each item as we insert it to see if it hits the target with the first item!
    • If we don’t luck out during the insert, then we iterate over items 2 to N, calculating the value we’d need to hit the target, and then doing a hash lookup to see if it’s there The hash lookup is O(1), and the pass over the array (including the load) is also O(1), so we’ve made a pretty good reduction in overall runtime.

    Things I’d forgotten in the last year of pretty much nothing but Scala:

    • The only loop structure in Go is the for loop.
    • var, not val. val is Scala, and as one tries for as much immutable data as possible in Scala, my fingers are trained to type val at every turn.
    • Go array initializer syntax. Square brackets, then the type, then the values in curly braces.
    • I remembered I needed to make() a map, but used Scala syntax instead of Go syntax trying to specify the types.
    • Go does not consider [2]int to be the same as []int. Fair.
    • Gotta have the parens for every function. Perl and Scala made me sloppy about that.

    Things discovered:

    • Adding the “try to catch it in the load pass” made a significant speed difference when the first term was at index 0.
    • Putting the first index in the array initializer for the result array instead of assigning it vastly increased the speed of the search loop — I went from the top 75% to the top 95% with that one change.
    • The hash solution uses more memory; I was at the 15th percentile on memory, but I’ll definitely take being faster than 95% of the other solutions as better.
    • I missed the “must be different indexes” restriction on my first pass; fortunately the second test case was for 6 with [3, 4, 2], and I got a test fail for [0,0].

    Here’s the final version. I do comment even when writing these because I can line out the code with comments and put the actual code itself in later. Writing down the assumptions helps clarify them as well (thanks loads, ADHD).

    func twoSum(nums []int, target int) []int {
        // Given that
        // - there is only one solution, then all entries must be unique
        // - therefore they can be used as keys to a hash
        // - this means we can iterate O(1) over the array for term 1
        //   and lookup term 2 in the hash, getting its index. No need
        //   for a linear search for it.
    
        // Fill the hash...and see if we can catch the solution
        // during this pass, since we have to do it anyway.
        locations := make(map[int]int)
        prematch := target - nums[0]
        for i := 0; i < len(nums); i++ {
            if i > 0 && nums[i] == prematch {
                return []int{0, i}
            }
            locations[nums[i]] = i
        }
    
        // scan the nums, looking for a match.
        // first match wins.
        for i := 0; i < len(nums); i++ {
            result := []int{i, -1}
            term2 := target - nums[i]
            j, ok := locations[term2]
            if ok {
                // Disqualify same-entry match. Missed this first time,
                // thank you test cases.
                if i == j {
                    continue
                }
                // Found
                result[1] = j
                return result
            }
        }
        // This is a "should not get here"; meant to show me if I've totally
        // blown the loop.
        panic("did not find a solution")
    }

    Add two numbers

    This one was recommended by leetcode as a “hot” problem, and it looked like fun, so I did it. It’s not in the Grind 75 list, but it’s a nice pointer manipulation problem, and I was a bit stale on pointers.

    The function is passed two linked lists; each list has an integer from 0-9 and a pointer to the next digit. The digits are presented in right-to-left order. The function is to take these two lists, and return a new list that represents the sum of the two numbers. Of course, the lists can be different lengths; if they are, then the list that runs out first is treated as if it were returning zeroes for the purposes of the sum.

    It was instantly clear that the challenge here would be maintaining the scan pointers for the two numbers and the result, and that trying to do bookkeeping for all of it in the loop would suck. So my approach was to construct a function to return a closure that would follow the rules for reading the numbers off the list:

    • If the scan pointer is null, return 0 and false
    • If the scan pointer is not null, return the node’s value and true, advancing the pointer to the next node.

    This meant that the list management was dead simple:

    first  := iterator(L1)
    second := iterator(L2)
    
    fVal, fState := first()
    sVal, sState := second()

    Each call to the first and second closures returns the next value to use in the sum; when both are false, the sum is complete.

    So all I had to manage in the loop was the scan pointer for the sum, and the carry from the addition operation: adding it in, clearing it, and recalculating it after the current operation.

    Things learned/relearned:

    • Had to remember when I needed * and when I didn’t for variable types.
    • The first run segfaulted. I realized that I’d mismanaged the scan pointer; I needed to save the root node, and then scan forward while doing the operations.
    • Once I fixed that, I found out I wasn’t clearing the carry. Whoops. Easy to fix.
    • The closures worked perfectly.
    • The “end the loop” and “restart the loop” keywords are break and continue. Trivial, but I had to go look it up.

    I did make one major mistake: I missed that the output was supposed to be another list, and started calculating the sum as an integer. This wasn’t too terribly off from the desired solution; I had figured out that I’d need the carry tracking, and I had a power variable that I was multiplying by 10 to switch columns in the output variable, but that probably wasted 5 or 10 minutes and might have made the difference between a pass and a fail in an interview.

    It was pretty easy to switch to the list building, but lesson hammered home again: be sure you read the problem statement correctly and know what the output is. Confirming with the interviewer that I got the details right is probably a good idea in a timed problem.

    **
     * Definition for singly-linked list.
     * type ListNode struct {
     *     Val int
     *     Next *ListNode
     * }
     */
    func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode {
        // The tricky bit with this one is going to be watching out
        // for the end of the list.
    
        // the dumbest and easiest way is to have a bool for each
        // list; when one list has no more entries, we set its bool
        // to false and return a zero value for it.
    
        // when both lists return a false value, we're done.
        first := iterator(l1)
        second := iterator(l2)
        var currentDigit *ListNode
    		var root *ListNode
    		carry := 0
    
        for {
            fVal, fState := first()
            sVal, sState := second()
    
            if (!fState && !sState) {
                // run off the end of both. Stop loop, finalize sum.
    						fmt.Println("Done")
                break
            }
            // At least one still returning a value. (The other returns 0
            // if there's no value.)
            // Sum the digits and the curent carry; if > 9,
            // take mod 10 and set the carry to 1. (no two
            // digits can sum to > 18).
            digitSum := fVal + sVal + carry
            carry = 0
            if digitSum > 9 {
                carry = 1
                digitSum = digitSum - 10
            }
            // Add space for a new digit, append it, continue.
            if currentDigit != nil {
                currentDigit.Next = &ListNode{digitSum, nil}
    		    currentDigit = currentDigit.Next
            } else {
    		    // Create and save root digit
                currentDigit = &ListNode{digitSum, nil}
    			root = currentDigit
            }
        }
        if (carry != 0) {
            // last addition had a carry we need to keep
            currentDigit.Next = &ListNode{carry, nil}
        }
        return root
    }
    
    func iterator(l *ListNode) func()(int, bool) {
        // Close over the pointer.
        p := l
        return func()(int, bool) {
            if p == nil {
                // Reached end. Keep returning 0 and false.
                return 0, false
            } else {
                // Capture next digit, advance pointer,
                // return next digit and true. If new pointer
                // is nil, we'll just return 0 and end-of-list signal
                // forever.
                v := p.Val
                p = p.Next 
                return v, true
            }
        }
    } 

    So how’d I do overall in comparison? Pretty fuckin’ good. I was in the top 95% on speed, and the top 91% in memory use. I suspect that managing all the bookkeeping in the loop might make it a tad faster (no call overhead for the closures), but at the cost of significantly more complexity. This solution is fast and small, and I’ll take it.

    Conclusions

    • My Go is pretty good. I didn’t spend a huge amount of time chasing bugs, and had it in a couple tries. I have a good grasp of the concepts and know mostly how to do things.
    • My Go is fairly stale. I don’t remember how to say things!
    • I had to look up a lot of things that I should have just remembered, like len() for array length (not .len, that’s Scala!). I need this practice!

    Tomorrow I’ll start off with Valid Parentheses.

  • Flexing the muscles: November leetcode challenge

    Most people do a novel for NaNoWriMo, and that’s a great thing.

    I am not a great fiction writer; maybe I’d be better if I practiced, but right now, writing code makes more money, so I’m going to spend the month of November working through the Grind 75 leetcode practice set.

    I would very much prefer a Perl job, but there just aren’t that many places now that really want someone whose primary is Perl. Python’s fine, but it’s mostly tied up in LLMs and AI at the moment, neither of which I actually find very interesting. (They seem to be more “build a model and hope it does the work you wanted” as opposed to “write the code to do the job you want done”, which I find much more satisfying.)

    I haven’t done much with Rust yet, and I think it’d be a fun language to work in, but I don’t see a lot of demand for it yet. Scala is fun to write but I’d rather jump out a window than use the JVM toolchain again. (This also crosses off Java, Dart, and Flutter for me as well.) I have not in general found C++ to be that much fun, and Javascript is what it is. I can write it, I just don’t enjoy it.

    So it comes down to two languages, really: Go and Swift. I enjoy coding in both (for different reasons), but at the moment I’m seeing more Go jobs than Swift ones, though that may just be my settings. Certainly when you need a Mac/iOS programmer, you need someone who at least understands Swift.

    So I’ll probably end up trying it with both, and seeing how it goes. Onward!