diff --git a/DIRECTORY.md b/DIRECTORY.md index 797ce5f7..7ef5db18 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -255,13 +255,16 @@ * Singly Linked List * [Node](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/linked_lists/singly_linked_list/node.py) * [Single Linked List](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/linked_lists/singly_linked_list/single_linked_list.py) + * [Single Linked List Utils](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/linked_lists/singly_linked_list/single_linked_list_utils.py) * [Test Singly Linked Delete](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/linked_lists/singly_linked_list/test_singly_linked_delete.py) * [Test Singly Linked List](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/linked_lists/singly_linked_list/test_singly_linked_list.py) * [Test Singly Linked List Nth To Last](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/linked_lists/singly_linked_list/test_singly_linked_list_nth_to_last.py) * [Test Singly Linked List Pairwise Swap](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/linked_lists/singly_linked_list/test_singly_linked_list_pairwise_swap.py) * [Test Singly Linked List Remove Duplicates](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/linked_lists/singly_linked_list/test_singly_linked_list_remove_duplicates.py) * [Test Singly Linked List Remove Nth Last Node](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/linked_lists/singly_linked_list/test_singly_linked_list_remove_nth_last_node.py) + * [Test Singly Linked List Reorder List](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/linked_lists/singly_linked_list/test_singly_linked_list_reorder_list.py) * [Test Singly Linked List Rotate](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/linked_lists/singly_linked_list/test_singly_linked_list_rotate.py) + * [Test Singly Linked Merge And Weave](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/linked_lists/singly_linked_list/test_singly_linked_merge_and_weave.py) * [Test Singly Linked Move Tail To Head](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/linked_lists/singly_linked_list/test_singly_linked_move_tail_to_head.py) * [Test Singly Linked Palindrome](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/linked_lists/singly_linked_list/test_singly_linked_palindrome.py) * [Test Singly Linked Revese](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/linked_lists/singly_linked_list/test_singly_linked_revese.py) diff --git a/datastructures/linked_lists/__init__.py b/datastructures/linked_lists/__init__.py index 1262c6bc..677b16a0 100755 --- a/datastructures/linked_lists/__init__.py +++ b/datastructures/linked_lists/__init__.py @@ -715,3 +715,36 @@ def pairs_with_sum(self, target: T) -> List[Tuple[Node, Node]]: List: list of pairs """ raise NotImplementedError("not yet implemented") + + @staticmethod + def reverse_list(head: Node) -> Optional[Node]: + """ + Reverses a linked list given the head node + Args: + head Node: the head node of the linked list + Returns: + Optional[Node]: the new head node of the reversed linked list + """ + if head is None or head.next is None: + return head + + # track previous node, so we can point our next pointer to it + previous = None + # track node to loop through + current_node = head + + while current_node: + # track the next node to not lose it while adjusting pointers + nxt = current_node.next + + # set the next pointer to the node behind it, previous + current_node.next = previous + + # adjust the new previous node to the current node for subsequent loops + previous = current_node + + # move our node pointer up to the next node in front of it + current_node = nxt + + # return the new tail of the k-group which is our head + return previous diff --git a/datastructures/linked_lists/singly_linked_list/README.md b/datastructures/linked_lists/singly_linked_list/README.md index 26f17b84..634f10f0 100755 --- a/datastructures/linked_lists/singly_linked_list/README.md +++ b/datastructures/linked_lists/singly_linked_list/README.md @@ -592,3 +592,29 @@ Here `n` is the number of nodes in the linked list - Stack --- + +## Reorder List + +Given the head of a singly linked list, reorder the list as if it were folded on itself. For example, if the list is +represented as follows: + +L0 -> L1 -> L2 -> L3 -> L4 -> L5 + +The reordered list should be: + +L0 -> L5 -> L1 -> L4 -> L2 -> L3 + +You don’t need to modify the values in the list’s nodes; only the links between nodes need to be changed. + +### Constraints + +- The range of number of nodes in the list is [1, 500] +- -5000 <= `node.value` <= 5000 + +### Examples + +![Example 1](./images/examples/singly_linked_list_reorder_list_example_1.png) +![Example 2](./images/examples/singly_linked_list_reorder_list_example_2.png) +![Example 3](./images/examples/singly_linked_list_reorder_list_example_3.png) +![Example 4](./images/examples/singly_linked_list_reorder_list_example_4.png) +![Example 5](./images/examples/singly_linked_list_reorder_list_example_5.png) diff --git a/datastructures/linked_lists/singly_linked_list/images/examples/singly_linked_list_reorder_list_example_1.png b/datastructures/linked_lists/singly_linked_list/images/examples/singly_linked_list_reorder_list_example_1.png new file mode 100644 index 00000000..f55a659e Binary files /dev/null and b/datastructures/linked_lists/singly_linked_list/images/examples/singly_linked_list_reorder_list_example_1.png differ diff --git a/datastructures/linked_lists/singly_linked_list/images/examples/singly_linked_list_reorder_list_example_2.png b/datastructures/linked_lists/singly_linked_list/images/examples/singly_linked_list_reorder_list_example_2.png new file mode 100644 index 00000000..099bf27d Binary files /dev/null and b/datastructures/linked_lists/singly_linked_list/images/examples/singly_linked_list_reorder_list_example_2.png differ diff --git a/datastructures/linked_lists/singly_linked_list/images/examples/singly_linked_list_reorder_list_example_3.png b/datastructures/linked_lists/singly_linked_list/images/examples/singly_linked_list_reorder_list_example_3.png new file mode 100644 index 00000000..4b0f2c11 Binary files /dev/null and b/datastructures/linked_lists/singly_linked_list/images/examples/singly_linked_list_reorder_list_example_3.png differ diff --git a/datastructures/linked_lists/singly_linked_list/images/examples/singly_linked_list_reorder_list_example_4.png b/datastructures/linked_lists/singly_linked_list/images/examples/singly_linked_list_reorder_list_example_4.png new file mode 100644 index 00000000..3492edb6 Binary files /dev/null and b/datastructures/linked_lists/singly_linked_list/images/examples/singly_linked_list_reorder_list_example_4.png differ diff --git a/datastructures/linked_lists/singly_linked_list/images/examples/singly_linked_list_reorder_list_example_5.png b/datastructures/linked_lists/singly_linked_list/images/examples/singly_linked_list_reorder_list_example_5.png new file mode 100644 index 00000000..7fe1af9e Binary files /dev/null and b/datastructures/linked_lists/singly_linked_list/images/examples/singly_linked_list_reorder_list_example_5.png differ diff --git a/datastructures/linked_lists/singly_linked_list/single_linked_list.py b/datastructures/linked_lists/singly_linked_list/single_linked_list.py index f22cb2f5..76250bae 100755 --- a/datastructures/linked_lists/singly_linked_list/single_linked_list.py +++ b/datastructures/linked_lists/singly_linked_list/single_linked_list.py @@ -4,6 +4,10 @@ from datastructures.linked_lists.singly_linked_list.node import SingleNode from datastructures.linked_lists import LinkedList, T, Node from datastructures.linked_lists.exceptions import EmptyLinkedList +from datastructures.linked_lists.singly_linked_list.single_linked_list_utils import ( + reverse_list, + merge_and_weave, +) class SinglyLinkedList(LinkedList): @@ -983,6 +987,36 @@ def reverse_list(head_node: SingleNode) -> SingleNode: def remove_tail(self): pass + def reorder_list(self) -> Optional[SingleNode]: + """ + Reorders the linked list in place. + Returns: + head node of reversed linked list + """ + # return early if there is no head node + if self.head is None: + return None + + # first split the linked list into two halves. To do this without knowing the length of the linked list beforehand + # we must first find the middle node. This uses the slow & fast pointer approach + middle_node = self.middle_node() + + # Store the second half head node + second_half_head = middle_node.next + # cut the connection between the first half and the second half + middle_node.next = None + + # Now, we need to reverse the second half of the linked list in place + # The reversal step involves taking the second half of the linked list and reversing it in place + # for example, if the linked list is 1 -> 2 -> 3 -> 4 -> 5, the second half is 3 -> 4 -> 5 + # after reversing, it becomes 5 -> 4 -> 3 + reversed_second_half = self.reverse_list(second_half_head) + + # now we can merge and weave the first half and the reversed second half + reordered_list = merge_and_weave(self.head, reversed_second_half) + + return reordered_list + def kth_to_last_node(self, k: int) -> Optional[SingleNode]: """ Gets the Kth to the last node. diff --git a/datastructures/linked_lists/singly_linked_list/single_linked_list_utils.py b/datastructures/linked_lists/singly_linked_list/single_linked_list_utils.py new file mode 100644 index 00000000..d0170e05 --- /dev/null +++ b/datastructures/linked_lists/singly_linked_list/single_linked_list_utils.py @@ -0,0 +1,66 @@ +from typing import Optional +from datastructures.linked_lists.singly_linked_list.node import SingleNode + + +def merge_and_weave( + first_half_head: SingleNode, second_half_head: SingleNode +) -> Optional[SingleNode]: + """ + Merges and weaves the first half and the reversed second half of the linked list in place. + Args: + first_half_head: head node of the first half of the linked list + second_half_head: head node of the reversed second half of the linked list + Returns: + head node of the merged and weaved linked list + """ + if first_half_head is None or second_half_head is None: + return None + + p1 = first_half_head + p2 = second_half_head + + while p2: + # save the pointer 1 next node to not loose it + p1_next = p1.next + p2_next = p2.next + + # now we can move the pointers + p1.next = p2 + p2.next = p1_next + + p1, p2 = p1_next, p2_next + + return first_half_head + + +def reverse_list(head: SingleNode) -> Optional[SingleNode]: + """ + Reverses a linked list given the head node + Args: + head Node: the head node of the linked list + Returns: + Optional[Node]: the new head node of the reversed linked list + """ + if head is None or head.next is None: + return head + + # track previous node, so we can point our next pointer to it + previous = None + # track node to loop through + current_node = head + + while current_node: + # track the next node to not lose it while adjusting pointers + nxt = current_node.next + + # set the next pointer to the node behind it, previous + current_node.next = previous + + # adjust the new previous node to the current node for subsequent loops + previous = current_node + + # move our node pointer up to the next node in front of it + current_node = nxt + + # return the new tail of the k-group which is our head + return previous diff --git a/datastructures/linked_lists/singly_linked_list/test_singly_linked_list_reorder_list.py b/datastructures/linked_lists/singly_linked_list/test_singly_linked_list_reorder_list.py new file mode 100644 index 00000000..7dc5deb7 --- /dev/null +++ b/datastructures/linked_lists/singly_linked_list/test_singly_linked_list_reorder_list.py @@ -0,0 +1,48 @@ +import unittest +from typing import List +from parameterized import parameterized +from datastructures.linked_lists.singly_linked_list.single_linked_list import ( + SinglyLinkedList, +) + +TEST_CASES = [ + ([1, 2, 3, 4, 5], [1, 5, 2, 4, 3]), + ([1, 2, 3, 4], [1, 4, 2, 3]), + ([1, 1, 2, 2, 3, -1, 10, 12], [1, 12, 1, 10, 2, -1, 2, 3]), + ([10, 20, -22, 21, -12], [10, -12, 20, 21, -22]), + ([1, 3, 5, 7, 9, 10, 8, 6, 4, 2], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), + ([1, 2, 3, 4, 5, 6], [1, 6, 2, 5, 3, 4]), + ( + [7, 0, 10, 13, 12, 19, 1, 3, 6, 7, 4, 2, 11], + [7, 11, 0, 2, 10, 4, 13, 7, 12, 6, 19, 3, 1], + ), + ([0, 8, 3, 1, 2, 7], [0, 7, 8, 2, 3, 1]), + ([7, 4, 6, 1, 5, 8], [7, 8, 4, 5, 6, 1]), + ([9, 0, 8, 2], [9, 2, 0, 8]), + ([6, 8, 7], [6, 7, 8]), +] + + +class ReorderListTestCase(unittest.TestCase): + @parameterized.expand(TEST_CASES) + def test_reorder_list(self, input_list: List[int], expected_output: List[int]): + linked_list = SinglyLinkedList() + for data in input_list: + linked_list.append(data) + + actual = linked_list.reorder_list() + self.assertIsNotNone(actual) + + # since the head node is returned, we want the values of the linked list and not just the head node + # to check that the actual reordering worked + actual_list: List[int] = [] + curr = actual + while curr: + actual_list.append(curr.data) + curr = curr.next + + self.assertEqual(expected_output, actual_list) + + +if __name__ == "__main__": + unittest.main() diff --git a/datastructures/linked_lists/singly_linked_list/test_singly_linked_merge_and_weave.py b/datastructures/linked_lists/singly_linked_list/test_singly_linked_merge_and_weave.py new file mode 100644 index 00000000..3cd5fdc0 --- /dev/null +++ b/datastructures/linked_lists/singly_linked_list/test_singly_linked_merge_and_weave.py @@ -0,0 +1,53 @@ +from typing import List +import unittest +from parameterized import parameterized +from datastructures.linked_lists.singly_linked_list.single_linked_list import ( + SinglyLinkedList, +) +from datastructures.linked_lists.singly_linked_list.single_linked_list_utils import ( + merge_and_weave, +) + +TEST_CASES = [ + ([1, 2, 3], [6, 5, 4], [1, 6, 2, 5, 3, 4]), + ([1, 2, 3], [4, 5], [1, 4, 2, 5, 3]), +] + + +class MergeAndWeaveSinglyLinkedListTestCase(unittest.TestCase): + def test_return_none_for_empty_linked_list(self): + """should return none for an empty linked list when attempting to reverse""" + linked_list_one = SinglyLinkedList() + linked_list_two = SinglyLinkedList() + + actual = merge_and_weave(linked_list_one.head, linked_list_two.head) + self.assertIsNone(actual) + + @parameterized.expand(TEST_CASES) + def test_merge_and_weave( + self, list_one_data: List[int], list_two_data: List[int], expected: List[int] + ): + linked_list_one = SinglyLinkedList() + # add the data to the linked list + for d in list_one_data: + linked_list_one.append(d) + + linked_list_two = SinglyLinkedList() + # add the data to the linked list + for d in list_two_data: + linked_list_two.append(d) + + actual = merge_and_weave(linked_list_one.head, linked_list_two.head) + + actual_list: List[int] = [] + curr = actual + while curr: + actual_list.append(curr.data) + curr = curr.next + + # perform assertion + self.assertEqual(expected, actual_list) + + +if __name__ == "__main__": + unittest.main() diff --git a/datastructures/linked_lists/singly_linked_list/test_singly_linked_revese.py b/datastructures/linked_lists/singly_linked_list/test_singly_linked_revese.py index 814c8cf6..43ff4a59 100644 --- a/datastructures/linked_lists/singly_linked_list/test_singly_linked_revese.py +++ b/datastructures/linked_lists/singly_linked_list/test_singly_linked_revese.py @@ -4,6 +4,34 @@ from datastructures.linked_lists.singly_linked_list.single_linked_list import ( SinglyLinkedList, ) +from datastructures.linked_lists.singly_linked_list.single_linked_list_utils import ( + reverse_list, +) + +TEST_CASES = [ + ([1, 2, 3, 4, 5, 4, 3, 2, 1], 1, 9, [1, 2, 3, 4, 5, 4, 3, 2, 1]), + ([1, 2, 3, 4, 5], 2, 4, [1, 4, 3, 2, 5]), + ([1, 2, 3, 4, 5], 1, 5, [5, 4, 3, 2, 1]), + ( + [103, 7, 10, -9, 105, 67, 31, 63], + 1, + 8, + [63, 31, 67, 105, -9, 10, 7, 103], + ), + ( + [103, 7, 10, -9, 105, 67, 31, 63], + 1, + 8, + [63, 31, 67, 105, -9, 10, 7, 103], + ), + ([-499, 399, -299, 199, -99, 9], 3, 5, [-499, 399, -99, 199, -299, 9]), + ([7, -9, 2, -10, 1, -8, 6], 2, 5, [7, 1, -10, 2, -9, -8, 6]), + ([6, 8, 7], 1, 2, [8, 6, 7]), + ([9, 0, 8, 2], 2, 4, [9, 2, 8, 0]), + ([7, 4, 6, 1, 5, 8], 2, 5, [7, 5, 1, 6, 4, 8]), + ([3, 7, 12, 2, 5, 1], 3, 6, [3, 7, 1, 5, 2, 12]), + ([3, 6, 7, 4, 2], 2, 4, [3, 4, 7, 6, 2]), +] class ReverseBetweenSinglyLinkedListTestCase(unittest.TestCase): @@ -11,8 +39,9 @@ def test_return_none_for_empty_linked_list(self): """should return none for an empty linked list when attempting to reverse""" linked_list = SinglyLinkedList() - actual = linked_list.reverse_between(0, 1) - self.assertIsNone(actual) + with self.assertRaises(ValueError): + actual = linked_list.reverse_between(0, 1) + self.assertIsNone(actual) def test_raises_exception_for_invalid_arguments(self): """should raise an exception for invalid left and right arguments""" @@ -21,32 +50,7 @@ def test_raises_exception_for_invalid_arguments(self): with self.assertRaises(ValueError): linked_list.reverse_between(10, 9) - @parameterized.expand( - [ - ([1, 2, 3, 4, 5, 4, 3, 2, 1], 1, 9, [1, 2, 3, 4, 5, 4, 3, 2, 1]), - ([1, 2, 3, 4, 5], 2, 4, [1, 4, 3, 2, 5]), - ([1, 2, 3, 4, 5], 1, 5, [5, 4, 3, 2, 1]), - ( - [103, 7, 10, -9, 105, 67, 31, 63], - 1, - 8, - [63, 31, 67, 105, -9, 10, 7, 103], - ), - ( - [103, 7, 10, -9, 105, 67, 31, 63], - 1, - 8, - [63, 31, 67, 105, -9, 10, 7, 103], - ), - ([-499, 399, -299, 199, -99, 9], 3, 5, [-499, 399, -99, 199, -299, 9]), - ([7, -9, 2, -10, 1, -8, 6], 2, 5, [7, 1, -10, 2, -9, -8, 6]), - ([6, 8, 7], 1, 2, [8, 6, 7]), - ([9, 0, 8, 2], 2, 4, [9, 2, 8, 0]), - ([7, 4, 6, 1, 5, 8], 2, 5, [7, 5, 1, 6, 4, 8]), - ([3, 7, 12, 2, 5, 1], 3, 6, [3, 7, 1, 5, 2, 12]), - ([3, 6, 7, 4, 2], 2, 4, [3, 4, 7, 6, 2]), - ] - ) + @parameterized.expand(TEST_CASES) def test_reverse_between( self, data: List[int], left: int, right: int, expected: List[int] ): @@ -70,32 +74,7 @@ def test_reverse_between( # perform assertion self.assertEqual(expected, actual_list) - @parameterized.expand( - [ - ([1, 2, 3, 4, 5, 4, 3, 2, 1], 1, 9, [1, 2, 3, 4, 5, 4, 3, 2, 1]), - ([1, 2, 3, 4, 5], 2, 4, [1, 4, 3, 2, 5]), - ([1, 2, 3, 4, 5], 1, 5, [5, 4, 3, 2, 1]), - ( - [103, 7, 10, -9, 105, 67, 31, 63], - 1, - 8, - [63, 31, 67, 105, -9, 10, 7, 103], - ), - ( - [103, 7, 10, -9, 105, 67, 31, 63], - 1, - 8, - [63, 31, 67, 105, -9, 10, 7, 103], - ), - ([-499, 399, -299, 199, -99, 9], 3, 5, [-499, 399, -99, 199, -299, 9]), - ([7, -9, 2, -10, 1, -8, 6], 2, 5, [7, 1, -10, 2, -9, -8, 6]), - ([6, 8, 7], 1, 2, [8, 6, 7]), - ([9, 0, 8, 2], 2, 4, [9, 2, 8, 0]), - ([7, 4, 6, 1, 5, 8], 2, 5, [7, 5, 1, 6, 4, 8]), - ([3, 7, 12, 2, 5, 1], 3, 6, [3, 7, 1, 5, 2, 12]), - ([3, 6, 7, 4, 2], 2, 4, [3, 4, 7, 6, 2]), - ] - ) + @parameterized.expand(TEST_CASES) def test_reverse_between_with_dummy( self, data: List[int], left: int, right: int, expected: List[int] ): @@ -119,6 +98,42 @@ def test_reverse_between_with_dummy( # perform assertion self.assertEqual(expected, actual_list) + def test_reverse_linked_list(self): + data = [1, 2, 3] + expected = [3, 2, 1] + linked_list = SinglyLinkedList() + # add the data to the linked list + for d in data: + linked_list.append(d) + + actual = linked_list.reverse() + + actual_list: List[int] = [] + curr = actual + while curr: + actual_list.append(curr.data) + curr = curr.next + + self.assertEqual(expected, actual_list) + + def test_reverse_linked_list_static(self): + data = [1, 2, 3] + expected = [3, 2, 1] + linked_list = SinglyLinkedList() + # add the data to the linked list + for d in data: + linked_list.append(d) + + actual = reverse_list(linked_list.head) + + actual_list: List[int] = [] + curr = actual + while curr: + actual_list.append(curr.data) + curr = curr.next + + self.assertEqual(expected, actual_list) + if __name__ == "__main__": unittest.main()