2807. Insert Greatest Common Divisors in Linked List
Problem Description
You are given a linked list where each node contains an integer value. Your task is to modify the linked list by inserting new nodes between every pair of adjacent nodes.
For each pair of adjacent nodes in the original list, you need to:
- Calculate the greatest common divisor (GCD) of their values
- Create a new node with this GCD value
- Insert this new node between the two adjacent nodes
The greatest common divisor of two numbers is the largest positive integer that divides both numbers evenly. For example, the GCD of 12 and 8 is 4, since 4 is the largest number that divides both 12 and 8 without a remainder.
The solution uses two pointers to traverse the list:
pre
points to the current node being processedcur
points to the next node in the original list
The algorithm works by:
- Starting with
pre
at the head andcur
at the second node - While
cur
exists:- Calculate
x = gcd(pre.val, cur.val)
- Create a new node with value
x
and insert it betweenpre
andcur
- Move forward: set
pre = cur
andcur = cur.next
- Calculate
- Return the modified linked list
For example, if the original list is [18] -> [6] -> [10] -> [3]
, the result would be [18] -> [6] -> [6] -> [2] -> [10] -> [1] -> [3]
, where 6 is the GCD of 18 and 6, 2 is the GCD of 6 and 10, and 1 is the GCD of 10 and 3.
Intuition
The key insight is that we need to traverse the linked list while maintaining references to consecutive pairs of nodes. Since we're inserting nodes between existing ones, we need to be careful not to lose track of the original nodes.
Think of it like walking along a chain where you need to add new links between every existing pair of links. You need two hands - one to hold the current link and another to hold the next link. After inserting a new link between them, you move forward by shifting your hands to the next pair.
The natural approach is to use two pointers:
- We can't just use a single pointer because we need access to both nodes in each pair to calculate their GCD
- We need
pre
to maintain our connection to the previous part of the list - We need
cur
to know where to connect after inserting the new node
Why do we update pointers as pre = cur
and cur = cur.next
? After inserting a GCD node between pre
and cur
, the next pair to process is cur
and whatever comes after cur
. So cur
becomes our new pre
, and cur.next
becomes our new cur
.
The beauty of this approach is that we're modifying the list in-place during a single pass. We don't need extra space for a new list or need to traverse multiple times. Each insertion is done locally between two nodes, and we systematically move through the entire list.
The gcd
function is used directly from Python's math library (imported as from math import gcd
), which efficiently calculates the greatest common divisor using Euclid's algorithm.
Learn more about Linked List and Math patterns.
Solution Approach
The implementation follows a straightforward simulation approach using two pointers to traverse and modify the linked list.
Algorithm Steps:
-
Initialize two pointers: Set
pre = head
(pointing to the first node) andcur = head.next
(pointing to the second node). This establishes our initial pair of adjacent nodes. -
Traverse the list: Use a
while cur:
loop to process each pair of adjacent nodes until we reach the end of the list. -
For each pair of nodes:
- Calculate the GCD:
x = gcd(pre.val, cur.val)
- Create a new node with value
x
and insert it betweenpre
andcur
:pre.next = ListNode(x, cur)
- The new node's value is
x
- The new node's next pointer points to
cur
pre.next
now points to this new node
- The new node's value is
- Calculate the GCD:
-
Move to the next pair: Update
pre = cur
andcur = cur.next
. This shifts our window forward - what was the second node in the pair becomes the first node of the next pair. -
Return the modified list: Return
head
which still points to the beginning of the now-modified list.
Key Implementation Details:
-
The insertion
pre.next = ListNode(x, cur)
effectively places the new GCD node betweenpre
andcur
by:- Making
pre
point to the new node - Making the new node point to
cur
- Making
-
The pointer updates
pre, cur = cur, cur.next
ensure we skip over the newly inserted node and continue processing original adjacent pairs. -
The loop naturally terminates when
cur
becomesNone
, which happens after processing the last pair of original nodes.
Time Complexity: O(n)
where n
is the number of nodes in the original list, as we visit each node once.
Space Complexity: O(1)
if we don't count the output nodes. We only use two pointer variables regardless of input size.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example with the linked list: [12] -> [8] -> [6]
Initial Setup:
head
points to node with value 12pre = head
(points to 12)cur = head.next
(points to 8)
First Iteration:
- We have the pair (12, 8)
- Calculate GCD:
gcd(12, 8) = 4
- Create new node with value 4 and insert it between pre and cur:
pre.next = ListNode(4, cur)
- This makes:
[12] -> [4] -> [8] -> [6]
- Update pointers:
pre = cur
(now points to 8),cur = cur.next
(now points to 6)
Second Iteration:
- We have the pair (8, 6)
- Calculate GCD:
gcd(8, 6) = 2
- Create new node with value 2 and insert it between pre and cur:
pre.next = ListNode(2, cur)
- This makes:
[12] -> [4] -> [8] -> [2] -> [6]
- Update pointers:
pre = cur
(now points to 6),cur = cur.next
(now None)
Termination:
cur
is None, so the while loop exits- Return
head
, which points to the modified list
Final Result: [12] -> [4] -> [8] -> [2] -> [6]
Notice how we only process pairs from the original list. The newly inserted node with value 4 is never used in a GCD calculation - we skip over it by moving our pointers to the next original pair (8, 6).
Solution Implementation
1# Definition for singly-linked list.
2# class ListNode:
3# def __init__(self, val=0, next=None):
4# self.val = val
5# self.next = next
6
7from math import gcd
8from typing import Optional
9
10class Solution:
11 def insertGreatestCommonDivisors(
12 self, head: Optional[ListNode]
13 ) -> Optional[ListNode]:
14 """
15 Insert nodes with GCD values between each pair of adjacent nodes in the linked list.
16
17 Args:
18 head: The head of the input linked list
19
20 Returns:
21 The head of the modified linked list with GCD nodes inserted
22 """
23 # Initialize two pointers to traverse the list
24 # prev_node points to the current node being processed
25 # curr_node points to the next node in the original list
26 prev_node, curr_node = head, head.next
27
28 # Iterate through the linked list while there are more nodes to process
29 while curr_node:
30 # Calculate the GCD of the values in the two adjacent nodes
31 gcd_value = gcd(prev_node.val, curr_node.val)
32
33 # Create a new node with the GCD value and insert it between prev_node and curr_node
34 # The new node's next pointer points to curr_node
35 prev_node.next = ListNode(gcd_value, curr_node)
36
37 # Move both pointers forward in the list
38 # prev_node moves to curr_node (skipping the newly inserted GCD node)
39 # curr_node moves to the next node in the original list
40 prev_node, curr_node = curr_node, curr_node.next
41
42 # Return the head of the modified linked list
43 return head
44
1/**
2 * Definition for singly-linked list.
3 * public class ListNode {
4 * int val;
5 * ListNode next;
6 * ListNode() {}
7 * ListNode(int val) { this.val = val; }
8 * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
9 * }
10 */
11class Solution {
12 /**
13 * Inserts a new node with the GCD value between every pair of adjacent nodes in the linked list.
14 *
15 * @param head The head of the linked list
16 * @return The modified linked list with GCD nodes inserted
17 */
18 public ListNode insertGreatestCommonDivisors(ListNode head) {
19 // Handle edge case: if list has only one node, return as is
20 if (head == null || head.next == null) {
21 return head;
22 }
23
24 // Traverse the list with two pointers: previous and current
25 ListNode previous = head;
26 ListNode current = head.next;
27
28 while (current != null) {
29 // Calculate GCD of the two adjacent nodes' values
30 int gcdValue = gcd(previous.val, current.val);
31
32 // Create a new node with the GCD value and insert it between previous and current
33 ListNode gcdNode = new ListNode(gcdValue, current);
34 previous.next = gcdNode;
35
36 // Move previous pointer to current node (skip the newly inserted GCD node)
37 previous = current;
38
39 // Move current pointer to the next node
40 current = current.next;
41 }
42
43 return head;
44 }
45
46 /**
47 * Calculates the Greatest Common Divisor (GCD) of two integers using Euclidean algorithm.
48 *
49 * @param a The first integer
50 * @param b The second integer
51 * @return The GCD of a and b
52 */
53 private int gcd(int a, int b) {
54 // Base case: when b becomes 0, a is the GCD
55 if (b == 0) {
56 return a;
57 }
58
59 // Recursive case: GCD(a, b) = GCD(b, a mod b)
60 return gcd(b, a % b);
61 }
62}
63
1/**
2 * Definition for singly-linked list.
3 * struct ListNode {
4 * int val;
5 * ListNode *next;
6 * ListNode() : val(0), next(nullptr) {}
7 * ListNode(int x) : val(x), next(nullptr) {}
8 * ListNode(int x, ListNode *next) : val(x), next(next) {}
9 * };
10 */
11class Solution {
12public:
13 /**
14 * Inserts a new node with the GCD value between every pair of adjacent nodes
15 * @param head: pointer to the head of the linked list
16 * @return: pointer to the modified linked list head
17 */
18 ListNode* insertGreatestCommonDivisors(ListNode* head) {
19 // Handle edge case: empty list or single node
20 if (!head || !head->next) {
21 return head;
22 }
23
24 // Initialize pointers for traversal
25 ListNode* previous = head;
26 ListNode* current = head->next;
27
28 // Traverse the list and insert GCD nodes between adjacent pairs
29 while (current) {
30 // Calculate GCD of adjacent nodes' values
31 int gcdValue = std::gcd(previous->val, current->val);
32
33 // Create new node with GCD value and insert it between previous and current
34 ListNode* gcdNode = new ListNode(gcdValue, current);
35 previous->next = gcdNode;
36
37 // Move to the next pair of original nodes
38 previous = current;
39 current = current->next;
40 }
41
42 return head;
43 }
44};
45
1/**
2 * Definition for singly-linked list node
3 */
4class ListNode {
5 val: number;
6 next: ListNode | null;
7
8 constructor(val?: number, next?: ListNode | null) {
9 this.val = (val === undefined ? 0 : val);
10 this.next = (next === undefined ? null : next);
11 }
12}
13
14/**
15 * Inserts a new node with the GCD value between every pair of adjacent nodes
16 * @param head - The head of the linked list
17 * @returns The modified linked list with GCD nodes inserted
18 */
19function insertGreatestCommonDivisors(head: ListNode | null): ListNode | null {
20 // Handle edge case: empty list or single node
21 if (!head || !head.next) {
22 return head;
23 }
24
25 // Traverse the list and insert GCD nodes between adjacent pairs
26 let previousNode: ListNode = head;
27 let currentNode: ListNode | null = head.next;
28
29 while (currentNode) {
30 // Calculate GCD of the two adjacent nodes
31 const gcdValue: number = gcd(previousNode.val, currentNode.val);
32
33 // Create and insert new node with GCD value between previous and current
34 const gcdNode: ListNode = new ListNode(gcdValue, currentNode);
35 previousNode.next = gcdNode;
36
37 // Move to the next pair of original nodes
38 previousNode = currentNode;
39 currentNode = currentNode.next;
40 }
41
42 return head;
43}
44
45/**
46 * Calculates the Greatest Common Divisor (GCD) of two numbers using Euclidean algorithm
47 * @param a - First number
48 * @param b - Second number
49 * @returns The GCD of a and b
50 */
51function gcd(a: number, b: number): number {
52 // Base case: when b becomes 0, a is the GCD
53 if (b === 0) {
54 return a;
55 }
56
57 // Recursive case: GCD(a, b) = GCD(b, a mod b)
58 return gcd(b, a % b);
59}
60
Time and Space Complexity
Time Complexity: O(n × log M)
The algorithm traverses the linked list once, visiting each node exactly once, which takes O(n)
time where n
is the number of nodes in the linked list. At each node, we compute the GCD (Greatest Common Divisor) between two node values using the gcd
function. The GCD operation using Euclidean algorithm has a time complexity of O(log M)
where M
is the maximum value among the two numbers being compared (or the maximum value in the entire linked list). Since we perform the GCD operation for each adjacent pair of nodes (which is n-1
times), the overall time complexity is O(n × log M)
.
Space Complexity: O(1)
(excluding the output)
The algorithm uses only a constant amount of extra space with two pointer variables pre
and cur
to traverse the linked list. The new nodes created are part of the result linked list and are not counted as auxiliary space. If we were to count the space for the newly created nodes, it would be O(n)
since we insert n-1
new nodes between the existing nodes. However, following the convention stated in the reference answer where the space consumption of the result linked list is ignored, the space complexity is O(1)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Handling Single Node or Empty List
One common pitfall is not properly handling edge cases where the linked list has only one node or is empty. The current implementation would fail if head
is None
or if the list has only one node, as head.next
would either cause an AttributeError or be None
, making the while loop never execute.
Problem Example:
- Input:
None
(empty list) → Code crashes with AttributeError - Input:
[5]
(single node) → Works but should be explicitly handled
Solution: Add a guard clause at the beginning:
def insertGreatestCommonDivisors(self, head: Optional[ListNode]) -> Optional[ListNode]:
# Handle edge cases: empty list or single node
if not head or not head.next:
return head
prev_node, curr_node = head, head.next
# ... rest of the code
2. Incorrect Pointer Movement
A common mistake is updating pointers incorrectly, such as:
# WRONG: This would skip nodes
prev_node = prev_node.next.next # This would jump over the GCD node
curr_node = curr_node.next
or
# WRONG: This would process the newly inserted node
prev_node = prev_node.next # This points to the GCD node, not the original node
curr_node = curr_node.next
Solution:
Always move prev_node
to where curr_node
was, ensuring we process original adjacent pairs:
# CORRECT
prev_node, curr_node = curr_node, curr_node.next
3. Creating Circular References
When inserting nodes, be careful not to create circular references:
# WRONG: Could create a cycle if not careful
new_node = ListNode(gcd_value)
new_node.next = curr_node
prev_node.next = new_node
# If we accidentally set curr_node.next = new_node somewhere, we'd have a cycle
Solution: Use the ListNode constructor that sets both value and next pointer atomically:
# CORRECT and safer
prev_node.next = ListNode(gcd_value, curr_node)
4. GCD Implementation Assumptions
The code uses math.gcd()
which handles negative numbers and zero correctly in Python 3.5+, but if implementing GCD manually or using older Python versions, edge cases need consideration:
- GCD of 0 and any number n is |n|
- GCD of negative numbers should return positive result
Solution:
Either use math.gcd()
(recommended) or implement a robust GCD function:
def gcd(a, b):
a, b = abs(a), abs(b)
while b:
a, b = b, a % b
return a if a else 1 # or handle 0,0 case as needed
In a binary min heap, the minimum element can be found in:
Recommended Readings
Linked List Cycle Given a linked list with potentially a loop determine whether the linked list from the first node contains a cycle in it For bonus points do this with constant space Parameters nodes The first node of a linked list with potentially a loop Result Whether there is a loop contained
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!