Start learning free

LESSON 315 OF 442 · LINKED LISTS

Palindrome Linked List in Scala: Copy to an ArrayBuffer

Practice O(n) Read the full explanation

Given the head of a singly linked list, return true if it is a palindrome, or false otherwise.

 

Example 1:

Input: head = [1,2,2,1]
Output: true

Example 2:

Input: head = [1,2]
Output: false

 

Constraints:

  • The number of nodes in the list is in the range [1, 105].
  • 0 <= Node.val <= 9

 

Follow up: This lesson's solution uses O(n) extra space: it stores the list's values (or, in a recursive version, keeps one call per node on the stack). As an extra challenge beyond this lesson, could you do it in O(n) time and O(1) extra space?
STEP 1 OF 11
    Ctrl + Enter to check

    SCALA · LINKED LISTS

    Palindrome Linked List in Scala, explained

    Problem overview

    The method isPalindrome receives head, the first node of a singly linked list of digits, and returns true if the values read the same from the front and from the back. The list has between 1 and 100,000 nodes, and each value is between 0 and 9.

    For [1, 2, 2, 1] the first and last values are both 1, and the two in the middle are both 2, so the answer is true. For [1, 2] the first value is 1 and the last is 2, so the answer is false.

    Approach: make the list indexable

    A palindrome check compares the first value with the last, the second with the second to last, and so on. On an array that is two indices moving toward each other. On a singly linked list it is awkward: each node knows only its successor, so there is no way to step back from the end.

    The simple fix is to walk the list once and copy the values into a structure that can be read at any position, then run the two-index comparison on the copy. In Scala, ArrayBuffer is the usual growable array: appending is cheap, and reading by position is direct.

    The statement's follow-up asks for O(1) extra space, which needs a different technique: find the middle, reverse the second half in place, and compare the halves. This lesson takes the simpler route and spends O(n) memory on the copy. Time is O(n) either way.

    The comparison loop stops when the two indices meet or cross. Every value has then been checked against its mirror. In a list with an odd length, the middle value is its own mirror, so nothing has to be checked for it.

    Step by step

    1. Create an empty ArrayBuffer[Int] named vals. The lesson uses its full name, scala.collection.mutable.ArrayBuffer, so no import is needed. A val is enough, because the buffer object is changed but the variable is never reassigned.
    2. Declare cur with var, starting at head. It walks the list and changes on every step.
    3. Loop while cur != null. A null link marks the end of the list.
    4. Append the node's value with vals += cur.`val`. The field is named val, which is a keyword in Scala, so the name goes in backticks.
    5. Advance with cur = cur.next. Without this line the loop never ends.
    6. Set left to 0. This is the index of the first value.
    7. Set right to vals.length - 1. This is the index of the last value.
    8. Loop while left < right. When the indices are equal or have crossed, there is nothing left to compare.
    9. Return false on a mismatch. If vals(left) differs from vals(right), the list can't be a palindrome, and one bad pair is enough. Scala reads an element with parentheses, not square brackets.
    10. Move inward by adding 1 to left and subtracting 1 from right.
    11. End with true. Every pair matched, and the last expression of a method is its result.

    The solution in Scala

    class Solution {
        def isPalindrome(head: ListNode): Boolean = {
            val vals = scala.collection.mutable.ArrayBuffer[Int]()
            var cur = head
            while (cur != null) {
                vals += cur.`val`
                cur = cur.next
            }
            var left = 0
            var right = vals.length - 1
            while (left < right) {
                if (vals(left) != vals(right)) return false
                left += 1
                right -= 1
            }
            true
        }
    }
    

    The Scala details worth a closer look:

    • Backticks around val. cur.`val` reads a field whose name is a reserved word. The backticks tell the compiler that it is an identifier. Any other field name, such as next, needs nothing.
    • += on a buffer. For a mutable collection held in a val, vals += x calls the buffer's own append and changes it in place. The same operator on an immutable List would need a var, because it would build a new list and reassign the variable.
    • vals(i) is a method call. Round brackets on a buffer or array call apply. An index outside 0 until vals.length throws an IndexOutOfBoundsException. Here the loop condition keeps both indices inside the buffer.
    • != on Ints compares numbers. That is what we want for values, unlike the node comparison in the cycle lesson, which needs identity.
    • return false inside a while. A value produced halfway through a loop can only leave the method with return. The plain true at the end is the result when the loop finishes.

    Complexity

    Time: O(n). The first loop visits each node once, and appending to an ArrayBuffer takes constant time on average. The second loop makes at most n / 2 comparisons.

    Space: O(n). The buffer holds one value per node. That misses the follow-up's O(1) space goal. Reversing the second half of the list in place reaches it, at the cost of more pointer work and of changing the list.

    Edge cases and common mistakes

    • One node. left is 0 and right is 0, so the loop doesn't run and the method returns true. A single value is a palindrome.
    • Even and odd lengths. [1, 2, 2, 1] compares two pairs. [1, 2, 1] compares one pair, and the middle 2 is never compared. Using left <= right would also be correct for odd lengths, since a value equals itself, but it is not needed.
    • Comparing the node's value, not the node. The buffer holds Ints, so the comparison is between numbers. Storing nodes and comparing them would compare identities and almost always answer false.
    • Forgetting cur = cur.next. The first loop would spin forever and the buffer would grow until memory runs out.
    • Stopping the second loop too late. With left != right as the condition, an even-length list has indices that cross without ever being equal, so the loop keeps going and sooner or later vals(right) reads index -1, which throws. Use left < right.
    • A mismatch at the very first pair. [1, 2, 3, 2, 2] returns false: the first value is 1 and the last is 2, so the first comparison already fails and the loop never goes further.

    Testing

    • [1, 2, 2, 1] gives true.
    • [1, 2] gives false.
    • [1] gives true.
    • [1, 2, 1] gives true.
    • [1, 2, 3, 2, 2] gives false.
    Keep going with ScalaAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free