Lists
These notes cover lists.
Singly-Linked Lists
Here is a basic linked list, storing integers:
Each box is a node with a pointer to the next node (called the node's pointee), and the last node (above, the box containing 1) has a null pointee. We call the first node the head and the last node the tail. Linked lists have some notion of "ordering" in the sense that each node points to the next. They do not, however, have any inherent indices, as arrays do. Accordingly (and this is true for other connected data structures), the data stored within a linked list are spread across memory. As such, they do not perform nearly as well as arrays when it comes to both caching and read time. In particular, lists do not have constant time access — we must traverse the list to read.
With lists, however, we can insert in (assuming a correct implementation). We set create a new node containing the data 6, then traverse the list until we reach the node just before the point of insertion:
Then we have point to node-5's pointee (), followed by node-5 changing its pointee to ().
For the puzzles that follow, solutions use the following structure for a node:
node definition. Where is a data type such that and return either true or false, and is a node pointer type, a is a product type where and
Problems
List to Array
Given the head of a linked list write an algorithm that returns the values stored in as an array.
This problem asks for a simple traversal. As we're asked to traverse the entire list, the approach below has a time complexity of and a space complexity of
list-to-array
- Input: the head of a linked list
- Output: an array containing the values stored in
- init
- while
start loop body
-
end loop body
- return
We can also implement this recursively:
recursive-list-to-array
- Input: the head of a linked list
- Output: an array containing the values stored in
- init
- function
- if then return
- else
- return
With the recursive approach, it's important to avoid situations of pass-by-value. For some languages (e.g., JavaScript), passing the entire array (rather than a reference or pointer to ) in a recursive call requires copying each value in to a new array This results in a time complexity of
Sum List
Given the head of a linked list containing integers, write an algorithm that returns the total sum of all values in the linked list.
Like list to array, computing the sum can be done iteratively:
listsum
- Input: the head of a linked list containing integers.
- Output: the sum total of each integer in
- init
- while
begin loop body
-
end loop body
- return
This algorithm has a time complexity of and a space complexity of A recursive implementation would also run on time, but with the added cost of space complexity, due to the call stack.
listsum
- Input: the head of a linked list containing integers.
- Output: the sum total of each integer in
- if then return
- else
Sequential Search
Given the head of a linked list and a target write an algorithm that returns true (or 1) if is in the linked list, and false (or 0) otherwise. The target is of a type where is meaningful.
This is another problem handled easily by traversal. The algorithm below has a time complexity of order — since we must traverse the list — and the space complexity of order
seqfind
- Input: the head of a linked list and a target.
- Output: true if false otherwise.
- init
- while
begin loop body
- if then return
-
end loop body
- return
As usual, we can also implement this recursively:
seqfind
- Input: the head of a linked list and a target.
- Output: true if false otherwise.
- if return
- else if then return
- else
ith Element
Given the head of a linked list and index write an algorithm that returns the list element at the specified index. If there is no such element, return null.
There are many ways to go about this problem, some of which are tempting but wrong. A common mistake is an algorithm like the following:
getith
- Input: the head of a linked list and an index
- Output: the element at index
- init
- while
begin loop body
-
end loop body
- return
The problem with this approach is that there's no guard against In the event that occurs — which is well within reason to expect — the very last line will return an error (hopefully; not all compilers or transpilers are so forgiving about dereferencing ). A correct approach would look like the following:
getith
- Input: the head of a linked list and an index
- Output: the element at index
- init
- while
begin loop body
- if return
-
end loop body
- return
This algorithm has a space complexity of order and a time complexity of order
getith
- Input: the head of a linked list and an index
- Output: the element at index
- if return
- else if return
- else return
The recursive solution has a time complexity of but unlike the iterative solution, has a space complexity of
Reverse List
Given the head of a linked list write an algorithm that returns with its nodes reversed.
Before we reverse we return immediately if Otherwise, we continue. Suppose the list were:
We begin with three pointers, (for previous), (for current), and (for next). Initially, points to nothing, points to and points to Then, so long as (1) Assign to 's next (make current point to previous). (2) Assign to (move previous to current). (3) Assign to (move current to next). And (4) Assign 's next to (move forward).
Once we've gone through the entire loop, all that's left is
The iterative solution has a time complexity of order and a space complexity of
reverse
- Input: the head of a linked list
- Output the head of a reversed
- if return
- else
- while
- return
A recursive approach is much more concise:
reverse
- Input: the head of a linked list
- Output the head of a reversed
- function
- if return
The recursive approach has a time complexity of order but while it is noticeably simpler, it does have a space complexity of order
Zipper List
Given two list heads and write an algorithm that zips their respective lists: The two lists merged into single linked list, where their nodes alternate. Should one be longer than the other, the final list must terminate with the longer's remaining nodes. Return the head of the zipped list.
ziplist
- Input: two list heads and
- Output: a zipped list
- init
- while
begin loop body
- if
- else
-
end loop body
- if
- if then
- if then
- return
Suppose and correspond to the following lists.
We're asked to construct the list:
It's easier to view this problem in terms of sets. We're given two posets:
We then have two functions and The former returns a current element in the latter, Because the final list is an alternates, we call and in alternum:
Once this is done, we append the elements of whatever remains, yielding the list:
This algorithm's time complexity is of order where and are the lengths of the two lists respectively. The use of stems from iterating only in so far as the shortest list.
A recursive approach has a time complexity of order and a space complexity of order
ziplist
- Input: two list heads and
- Output: a zipped list
- if return
- if return
- if return
- return
List Merge
Let and be the heads of two nonempty lists and respectively, where has a length and has a length not necessarily equal. The elements of and are integers, sorted in increasing order. Write an algorithm that merges and the two lists together into single sorted linked list whose elements are the elements of and sorted in increasing order. The algorithm must merge-in-place and the head of
This algorithm has a time complexity of order and a space complexity of order
sortedmerge
- Input: a list head and a list head wherein elements are sorted in increasing order.
- Output: the head of a list comprising nodes of and wherein elements are sorted in increasing order.
- init
- if
- then
- else
- while
- if then
- else
- if then
- if then
- if then
- return
Univalue List
Given an integer list head write an algorithm that returns true if the list contains exactly one unique value (i.e., all the list's nodes store the same integer), and false otherwise. The list is guaranteed to be non-empty.
The approach here is fairly straightforward. We set initialize a pointer with the head then iterate through the list. If the data stored in 's pointee is not equal to the data stored in 's pointee (the first integer in the list), then we immediately return false, as there are multiple unique values. This algorithm has a time complexity of order and space complexity of order
univalue
- Input: the head of a list
- Output: a Boolean
- init
- while
begin loop body
- if then
- return
-
end loop body
- if then
- return
Remove Node
Let be a head of a linked list and an integer. Write an algorithm that deletes the node containing in place, and returns If there are multiple instances of remove only the first instance.
This problem is conducive to a three-pointer approach. We use three pointers, (a tailer), (a passenger), and (a driver), where is always ahead of and is always ahead of Initially, points to points to the head and points to (the head's next pointer). Once established, we traverse, so long as isn't equal to the target value There are two possible cases with this approach. First, the target value could be stored in in which case we never transition from our initial state:
The second case is everything else — where we do transition states:
In the first case, is nothing more than a null pointer, so it isn't of any use. This causes us to branch at the end of the loop. The algorithm below has a time complexity of order and a space complexity of
remove-node
- Input: a list head
- Output: the list head
- init
- while
- if then
- else
- return