diff --git a/README.md b/README.md index d0ab75b..7164c0e 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ For a glimpse into my journey and the projects I've been involved in, feel free - Two-Pointer Approach - Reversing Linked Lists - Merge Two Sorted Linked Lists + - Reorder Linked Lists - Developer Tools - My Development Environment: kitty, zsh, Neovim, tmux, and lazygit diff --git a/content/contents.md b/content/contents.md index 3b89ffc..5443319 100644 --- a/content/contents.md +++ b/content/contents.md @@ -43,5 +43,6 @@ summary: "Index of all content" * Two-Pointer Approach * Reversing Linked Lists * Merge Two Sorted Linked Lists + * Reorder Linked Lists - Developer Tools - My Development Environment: kitty, zsh, Neovim, tmux, and lazygit diff --git a/content/posts/dsa/reorder-linked-list/index.md b/content/posts/dsa/reorder-linked-list/index.md new file mode 100644 index 0000000..3427014 --- /dev/null +++ b/content/posts/dsa/reorder-linked-list/index.md @@ -0,0 +1,420 @@ +--- +author: "Avnish" +title: "Reorder Linked Lists" +date: "2024-02-15" +description: "Implement a reorderList function that takes the head of a linked list (node(0) -> node(1) -> node(2) -> .. -> node(n-1) -> node(n)) as input and reorders its nodes in the form node(0) -> node(n) -> node(1) -> node(n-1) -> node(2) -> .. ." +tags: ["data-structures", "linked-lists", "go", "neetcode-150", "leetcode-medium"] +categories: ["Data Structures"] +series: ["Data Structures and Algorithms"] +aliases: + ["reorder-linked-lists"] +ShowToc: true +TocOpen: false +comments: false +cover: + image: "reorder-linked-lists-cover.png" + linkFullImages: true + alt: "The reorderList function will return the head of the reordered linked list given a linked list as input" + caption: "" + relative: false + responsiveImages: false +math: true +--- + +# Problem Statement +We have to implement the `reorderList` function that takes the `head` node of a linked list as input and reorders its +nodes in the format specified below. + +Given an input list like the following: + +$$Node_0 \rightarrow Node_1 \rightarrow Node_2 \rightarrow \dots \rightarrow Node_{(n-2)} \rightarrow Node_{(n-1)} \rightarrow Node_n$$ + +the `reorderList` function should reorder its nodes as: + +$$Node_0 \rightarrow Node_n \rightarrow Node_1 \rightarrow Node_{(n-1)} \rightarrow Node_2 \rightarrow Node_{(n-2)} \rightarrow \dots$$ + +

Problem statement for the reorderList

+ +# Brute Force Solution +To solve this problem we use two iterators: one starting from the `head` of the input list and another +starting from the `head` of the reversed input list. + +We pick values alternatively from both iterators and create a new +linked list. The loop will continue until the length of the length of the new linked list is equal to the length of +the input list. The nodes in the new linked list will be ordered as specified in the problem statement. + +

Brute-force solution for the reorderList

+ + +## Psuedo-code for the Brute Force Solution +```text +reversed_linked_list = reverseList(linked_list) +forward_iter = linked_list.head +reverse_iter = reversed_linked_list.head +reorderedLinkedList = LinkedList() + +while(length(reorderedLinkedList)!=length(linked_list)){ + reorderedLinkedList.insert(forward_iter) + if(length(reorderedLinkedList)==length(linked_list)) + break + + reorderedLinkedList.insert(reverse_iter) + if(length(reorderedLinkedList)==length(linked_list)) + break + + forward_iter=forward_iter.next + reversed_linked_list=reverse_iter.next +} + +return reorderedLinkedList +``` + +## Time Complexity Analysis +### Best Case Scenario +With this brute-force solution, we have to iterate over the complete linked list, even in the best-case scenario. +The time complexity of the brute-force solution will be $O(n)$ (for iterating over the `reorderedList`) times +$O(n)$ (for calculating the current length of `reorderedList`) which will result in the total time complexity of $O(n^2)$. + +### Worst Case Scenario +The time complexity of the worst-case scenario will also be $O(n^2)$. + +## Space Complexity Analysis +The brute-force solution is using additional $O(2n)$ memory space to store the reversed and reordered linked list. + +## Code for Brute Force Solution +```Go +package main + +import "fmt" + +type ListNode struct { + Val int + Next *ListNode +} + +func Display(ln *ListNode){ + temp := ln + for(temp!=nil){ + fmt.Printf("%d->", temp.Val) + temp=temp.Next + } + fmt.Println() +} + +func Length(ln *ListNode)(int){ + + // Since this function iterates the complete linked list + // the time complexity of this function is O(n) + // where n is the length of the linked list + temp := ln + length := 0 + for(temp!=nil){ + length += 1 + temp=temp.Next + } + return length +} + +func insertAtFront(ln *ListNode, value int)(*ListNode){ + + // Time complexity of inserting a value at the head of the + // linked list is O(1) + newNode := &(ListNode{Val: value}) + newNode.Next = ln + return newNode +} + +func insertAtEnd(ln *ListNode, value int)(*ListNode){ + + // Time complexity of inserting a value at the end of the + // linked list is O(n) + if(ln==nil){ + ln = &(ListNode{Val: value}) + } else { + temp := ln + for(temp.Next!=nil){ + temp = temp.Next + } + temp.Next = &(ListNode{Val: value}) + } + return ln +} + +func reverseList(ln *ListNode)(*ListNode){ + + // Time complexity of reversing a linked list is O(n) + // where n is the length of the linked list + var reversedList *ListNode + temp := ln + for(temp!=nil){ + reversedList = insertAtFront(reversedList, temp.Val) + temp=temp.Next + } + return reversedList +} + +func reorderList(head *ListNode){ + forwardIter := head + reverseIter := reverseList(head) + inputListLength := Length(head) + var reorderedList *ListNode + + // The following loop will be executed n times + // where n is the length of the linked list + for(Length(reorderedList)!=inputListLength){ + + // Inserting a value at the end of a linked list will take O(n) time + reorderedList = insertAtEnd(reorderedList, forwardIter.Val) + + // Validating the length of a linked list will take O(n) time as well + if(Length(reorderedList)==inputListLength){ + break + } + + // Time Complexity: O(n) + reorderedList = insertAtEnd(reorderedList, reverseIter.Val) + + // Time Complexity: O(n) + if(Length(reorderedList)==inputListLength){ + break + } + + forwardIter = forwardIter.Next + reverseIter = reverseIter.Next + } + + // Copying the values of reorderedList to the input linked list + for(head!=nil){ + head.Val = reorderedList.Val + head=head.Next + reorderedList=reorderedList.Next + } +} + +func main(){ + var ln *ListNode + ln = insertAtEnd(ln, 1) + ln = insertAtEnd(ln, 2) + ln = insertAtEnd(ln, 3) + ln = insertAtEnd(ln, 4) + ln = insertAtEnd(ln, 5) + fmt.Println("Input Linked List:") + Display(ln) + + reorderList(ln) + fmt.Println("Reordered Linked List:") + Display(ln) +} + +// Output +// Input Linked List: +// 1->2->3->4->5-> +// Reordered Linked List: +// 1->5->2->4->3-> +``` + +# Optimized Solution +To solve this problem in linear time while also reducing the space complexity (by performing operations in-place) we +have to switch to the Fast-Slow Pointer Approach. + +We have to break the input linked list from the middle +(or the `middle.Next` node in the case of an odd number of nodes). Then we will insert the nodes from the second half of +the linked list in between the nodes of the first half. However, the second half of the linked list has to be reversed to +achieve the reordering format specified in the problem statement. + +First, we will iterate over the input linked list using the `fast` and `slow` pointers. + +

Optimized solution for the reorderList

+ +At the end of the iteration, the `fast` pointer will be at the end of the linked list and the `slow` pointer will be at +the middle node. + +Then we will break the input linked list into two halves from the `slow.Next` node. + +

Optimized solution for the reorderList

+ +Since the reordering form requires the last node to be placed in between the first and second node of the list +we have to reverse the second half of the input linked list. + +

Optimized solution for the reorderList

+ +Finally, we will iterate over the first half of the input linked list while also inserting the nodes from the second half +(reversed) in between. + +

Optimized solution for the reorderList

+ +## Psuedo code for the Optimized Solution +```text +fast = linked_list.head +slow = linked_list.head +while(fast.next!=nil or fast!=nil){ + fast = fast.next.next + slow = slow.next +} + +temp = slow.next +slow.next = nil +slow = temp + +reversed_half_list = reverse(slow) + +temp = linked_list.head +reverse_temp = reversed_half_list.head +while(temp!=nil){ + temp2 = temp.next + temp.next = reverse_temp + reverse_temp.next = temp2 + temp = temp2 +} +return linked_list.head +``` + +## Time Complexity Analysis +### Best Case Scenario +The time complexity of iterating over the input linked list using `fast` and `slow` pointers will be $O(n/2)$ +(where $n$ is the size of the linked list) because the `fast` pointer will skip over every other node. + +Reversing the second half of the input linked list will also take $O(n/2)$ time. + +Since we are iterating over only the first half of the linked list in the last loop its time complexity will also be $O(n/2)$. + +So the total time complexity of the optimized solution in the best-case scenario is $O(3n/2)$ which could be +generalized to $O(n)$. + +### Worst Case Scenario +All the steps in the optimized solution will be executed regardless of the characteristics of the input +linked list. So the time complexity for the worst-case scenario will also be $O(n)$. + +## Space Complexity Analysis +Since we are performing operations in place on the input linked list, no additional memory space is required +by the optimized solution. Thus, the total space complexity of the optimized solution is $O(1)$. + +## Code for Optimized Solution +```Go +package main + +import "fmt" + +type ListNode struct { + Val int + Next *ListNode +} + +func Display(ln *ListNode){ + temp := ln + for(temp!=nil){ + fmt.Printf("%d->", temp.Val) + temp = temp.Next + } + fmt.Println() +} + +func insertAtFront(ln *ListNode, value int)(*ListNode){ + newNode := &(ListNode{Val: value}) + newNode.Next = ln + return newNode +} + +func insertAtEnd(ln *ListNode, value int)(*ListNode){ + if(ln==nil){ + ln = &(ListNode{Val: value}) + } else { + temp := ln + for(temp.Next!=nil){ + temp=temp.Next + } + temp.Next = &(ListNode{Val: value}) + } + return ln +} + +func reverseList(ln *ListNode)(*ListNode){ + var reverseList *ListNode + temp := ln + for(temp!=nil){ + reverseList = insertAtFront(reverseList, temp.Val) + temp=temp.Next + } + return reverseList +} + +func reorderList(head *ListNode){ + + // Iterating over the input linked list + // using the fast and slow pointer + // Time Complexity: O(n/2) since the fast pointer + // will skip over every other node + fast := head + slow := head + for(fast.Next!=nil){ + if(fast.Next.Next!=nil){ + + // When the fast pointer finishes iteration + // the slow pointer will be at the middle node + fast = fast.Next.Next + slow = slow.Next + } else { + break + } + } + + // Breaking the input linked list + // after the middle node + temp := slow.Next + slow.Next = nil + slow = temp + + // Reversing the 2nd half of the linked list + // Time Complexity: O(n/2), where n is the size + // of the input linked list + reversedSecond := reverseList(slow) + + // Iterating over the 1st half of the linked list + // and inserting nodes from the reversed 2nd half + // Time Complexity: O(n/2) + temp = head + for(reversedSecond!=nil){ + + // Storing next values of temp and reversedSecond + // in temporary variables + temp2 := temp.Next + tempReversedSlow := reversedSecond.Next + + // Reassigning Next nodes of temp and reversedSecond + temp.Next = reversedSecond + reversedSecond.Next = temp2 + + temp = temp2 + reversedSecond = tempReversedSlow + } +} + +func main(){ + var ln *ListNode + ln = insertAtEnd(ln, 1) + ln = insertAtEnd(ln, 2) + ln = insertAtEnd(ln, 3) + ln = insertAtEnd(ln, 4) + ln = insertAtEnd(ln, 5) + fmt.Println("Input Linked List:") + Display(ln) + + reorderList(ln) + fmt.Println("Reordered Linked List:") + Display(ln) +} + +// Output +// Input Linked List: +// 1->2->3->4->5-> +// Reordered Linked List: +// 1->5->2->4->3-> +``` + +
+ +Thank you for taking the time to read this blog post! If you found this content valuable and would like to stay updated with my latest posts consider subscribing to my RSS Feed. + +# Resources +143. Reorder List +Linkedin Interview Question - Reorder List - Leetcode 143 - Python diff --git a/content/posts/dsa/reorder-linked-list/reorder-linked-lists-brute-force.png b/content/posts/dsa/reorder-linked-list/reorder-linked-lists-brute-force.png new file mode 100644 index 0000000..eb52ae3 Binary files /dev/null and b/content/posts/dsa/reorder-linked-list/reorder-linked-lists-brute-force.png differ diff --git a/content/posts/dsa/reorder-linked-list/reorder-linked-lists-cover.png b/content/posts/dsa/reorder-linked-list/reorder-linked-lists-cover.png new file mode 100644 index 0000000..a978435 Binary files /dev/null and b/content/posts/dsa/reorder-linked-list/reorder-linked-lists-cover.png differ diff --git a/content/posts/dsa/reorder-linked-list/reorder-linked-lists-optimized1.png b/content/posts/dsa/reorder-linked-list/reorder-linked-lists-optimized1.png new file mode 100644 index 0000000..359170d Binary files /dev/null and b/content/posts/dsa/reorder-linked-list/reorder-linked-lists-optimized1.png differ diff --git a/content/posts/dsa/reorder-linked-list/reorder-linked-lists-optimized2.png b/content/posts/dsa/reorder-linked-list/reorder-linked-lists-optimized2.png new file mode 100644 index 0000000..9633312 Binary files /dev/null and b/content/posts/dsa/reorder-linked-list/reorder-linked-lists-optimized2.png differ diff --git a/content/posts/dsa/reorder-linked-list/reorder-linked-lists-optimized3.png b/content/posts/dsa/reorder-linked-list/reorder-linked-lists-optimized3.png new file mode 100644 index 0000000..dba6d33 Binary files /dev/null and b/content/posts/dsa/reorder-linked-list/reorder-linked-lists-optimized3.png differ diff --git a/content/posts/dsa/reorder-linked-list/reorder-linked-lists-optimized4.png b/content/posts/dsa/reorder-linked-list/reorder-linked-lists-optimized4.png new file mode 100644 index 0000000..1771d49 Binary files /dev/null and b/content/posts/dsa/reorder-linked-list/reorder-linked-lists-optimized4.png differ diff --git a/content/posts/dsa/reorder-linked-list/reorder-linked-lists-problem.png b/content/posts/dsa/reorder-linked-list/reorder-linked-lists-problem.png new file mode 100644 index 0000000..aded1bf Binary files /dev/null and b/content/posts/dsa/reorder-linked-list/reorder-linked-lists-problem.png differ