Skip to content

Commit 8d3fbb9

Browse files
committed
feat(data-structures, linkedlist): reverse k groups
1 parent db9e20c commit 8d3fbb9

42 files changed

Lines changed: 357 additions & 159 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

algorithms/dynamic_programming/palindromic_substring/longest_palindromic_substring.py

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,47 @@ def longest_palindromic_substring(phrase: str) -> str:
3131
dp[i][i + 1] = True
3232
result = [i, i + i]
3333

34-
for diff in range(2, n):
35-
for i in range(n - diff):
36-
j = i + diff
34+
# Substrings of length greater than 2
35+
for length in range(2, n):
36+
for i in range(n - length):
37+
j = i + length
3738
if phrase[i] == phrase[j] and dp[i + 1][j - 1]:
3839
dp[i][j] = True
3940
result = [i, j]
4041

4142
i, j = result
4243
return phrase[i : j + 1]
44+
45+
46+
def longest_palindromic_substring_2(s):
47+
# To store the starting and ending indexes of LPS
48+
res = [0, 0]
49+
50+
n = len(s)
51+
52+
# Initialize a lookup table of dimensions len(s) * len(s)
53+
dp = [[False for _ in range(n)] for _ in range(n)]
54+
55+
# Base case: A string with one letter is always a palindrome
56+
for i in range(n):
57+
dp[i][i] = True
58+
59+
# Base case: Substrings of length 2
60+
for i in range(n - 1):
61+
dp[i][i + 1] = s[i] == s[i + 1] # Check if two characters are equal
62+
if dp[i][i + 1]:
63+
res = [i, i + 1] # Update the resultant array
64+
65+
# Substrings of lengths greater than 2
66+
for length in range(3, n + 1):
67+
i = 0
68+
# Checking every possible substring of any specific length
69+
for j in range(length - 1, len(s)): # Iterate over possible ending indexes
70+
dp[i][j] = dp[i + 1][j - 1] and (s[i] == s[j])
71+
if dp[i][j]:
72+
res = [i, j]
73+
i += 1
74+
75+
i, j = res
76+
# Return the longest palindromic substring
77+
return s[i : j + 1]

datastructures/arrays/non_overlapping_intervals/README.md renamed to algorithms/intervals/non_overlapping_intervals/README.md

File renamed without changes.

datastructures/arrays/non_overlapping_intervals/__init__.py renamed to algorithms/intervals/non_overlapping_intervals/__init__.py

File renamed without changes.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import unittest
2+
from typing import List
3+
from parameterized import parameterized
4+
from algorithms.intervals.non_overlapping_intervals import erase_overlap_intervals
5+
6+
ERASE_OVERLAP_INTERVALS_TEST_CASES = [
7+
([[1, 2], [2, 3], [3, 4], [1, 3]], 1),
8+
([[1, 2], [1, 2], [1, 2]], 2),
9+
([[1, 2], [2, 3]], 0),
10+
([[3, 6], [1, 4], [9, 11], [5, 8]], 1),
11+
([[6, 10], [5, 9], [12, 15], [6, 10], [1, 3], [4, 6]], 2),
12+
([[1, 2], [2, 4], [3, 6], [5, 10]], 1),
13+
([[1, 5], [1, 5], [2, 8], [7, 9], [10, 12]], 2),
14+
([[3, 5], [5, 10], [10, 15], [12, 17], [16, 20], [21, 23], [22, 25], [23, 27]], 2),
15+
([[4, 8], [8, 14], [15, 17], [16, 17]], 1),
16+
([[10, 20], [20, 30], [30, 40], [40, 50], [50, 60]], 0),
17+
]
18+
19+
20+
class NonOverlappingIntervalsTestCase(unittest.TestCase):
21+
@parameterized.expand(ERASE_OVERLAP_INTERVALS_TEST_CASES)
22+
def test_erase_non_overlapping_intervals(
23+
self, intervals: List[List[int]], expected: int
24+
):
25+
actual = erase_overlap_intervals(intervals)
26+
self.assertEqual(expected, actual)
27+
28+
29+
if __name__ == "__main__":
30+
unittest.main()

datastructures/arrays/non_overlapping_intervals/test_non_overlapping_intervals.py

Lines changed: 0 additions & 30 deletions
This file was deleted.

datastructures/linked_lists/linked_list.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -581,9 +581,13 @@ def rotate(self, k: int):
581581
raise NotImplementedError("Not yet implemented")
582582

583583
@abstractmethod
584-
def reverse_groups(self, k: int):
584+
def reverse_groups(self, k: int) -> Optional[Node]:
585585
"""
586586
Reverses every k groups of a linked list
587+
Args:
588+
k(int): number of groups to reverse
589+
Returns:
590+
Node: head node of linked list
587591
"""
588592
raise NotImplementedError("Not yet implemented")
589593

datastructures/linked_lists/singly_linked_list/README.md

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -764,3 +764,157 @@ node will be processed at most one time.
764764

765765
The space complexity of this solution is O(1), since we are using a constant number of additional variables to maintain
766766
the connections between the nodes during the reversal process.
767+
768+
---
769+
770+
# Reverse Nodes in k-Group
771+
772+
The task is to reverse the nodes in groups of k in a given linked list, where k is a positive integer, and at most the
773+
length of the linked list. If any remaining nodes are not part of a group of k, they should remain in their original
774+
order.
775+
776+
It is not allowed to change the values of the nodes in the linked list. Only the order of the nodes can be modified.
777+
778+
> Note: Use only O(1) extra memory space
779+
780+
## Constraints
781+
782+
Let `n` bt the number of nodes in a linked list
783+
- 1 <= `k` <= `n` <= 500
784+
- 0 <= `Node.value` <= 1000
785+
786+
## Solution
787+
788+
- [Naive Approach](#naive-approach)
789+
- [Optimized Approach Using In-Place Manipulation of Linked List](#optimized-approach-using-in-place-manipulation-of-a-linked-list)
790+
791+
### Naive Approach
792+
793+
A naive approach would be to use another data structure—like a stack—to reverse the nodes of the linked list and then
794+
create a new linked list with reversed nodes. Here’s how the algorithm works:
795+
796+
- We iterate the linked list.
797+
- We push the k group of nodes to the stack.
798+
- We pop all k numbers of nodes from the stack and add the nodes to a new linked list. When we do this, the stack will
799+
give us the reversed nodes in the k group.
800+
- We repeat the above steps for every group of size k present in our linked list.
801+
- In the end, if there are less than k nodes left in the original linked list, we’ll point the tail of the reversed
802+
linked list to the remaining nodes of the original linked list.
803+
804+
The time complexity of this solution is O(n), since we traverse the linked list once. However, the space complexity is
805+
O(n+k), where n is the length of the linked list to store the reversed elements and k is the length of the stack. If a
806+
linked list contains thousands of nodes, we need to allocate a lot of memory resources to solve this problem. Let’s see
807+
if we can use the in-place linked list manipulation pattern to reduce the space complexity of our solution.
808+
809+
### Optimized approach using in-place manipulation of a linked list
810+
811+
This approach optimizes space by reversing groups of k nodes directly within the linked list, treating each group as a
812+
mini-linked list for in-place reversal. The approach progresses by first identifying contiguous groups of exactly k nodes.
813+
Upon finding such a group, it reverses the nodes within the group in place, ensuring an efficient reorganization without
814+
using extra memory. After each reversal, the algorithm reattaches the reversed group segment back to the body of the list,
815+
maintaining the overall remaining structure. This process is repeated until it encounters a segment with fewer than k
816+
nodes.
817+
818+
![Solution Reverse K Groups 1](./images/solutions/singly_linked_list_reverse_nodes_in_k_group_solution_1.png)
819+
![Solution Reverse K Groups 2](./images/solutions/singly_linked_list_reverse_nodes_in_k_group_solution_2.png)
820+
![Solution Reverse K Groups 3](./images/solutions/singly_linked_list_reverse_nodes_in_k_group_solution_3.png)
821+
![Solution Reverse K Groups 4](./images/solutions/singly_linked_list_reverse_nodes_in_k_group_solution_4.png)
822+
![Solution Reverse K Groups 5](./images/solutions/singly_linked_list_reverse_nodes_in_k_group_solution_5.png)
823+
![Solution Reverse K Groups 6](./images/solutions/singly_linked_list_reverse_nodes_in_k_group_solution_6.png)
824+
![Solution Reverse K Groups 7](./images/solutions/singly_linked_list_reverse_nodes_in_k_group_solution_7.png)
825+
![Solution Reverse K Groups 8](./images/solutions/singly_linked_list_reverse_nodes_in_k_group_solution_8.png)
826+
![Solution Reverse K Groups 9](./images/solutions/singly_linked_list_reverse_nodes_in_k_group_solution_9.png)
827+
![Solution Reverse K Groups 10](./images/solutions/singly_linked_list_reverse_nodes_in_k_group_solution_10.png)
828+
![Solution Reverse K Groups 11](./images/solutions/singly_linked_list_reverse_nodes_in_k_group_solution_11.png)
829+
![Solution Reverse K Groups 12](./images/solutions/singly_linked_list_reverse_nodes_in_k_group_solution_12.png)
830+
![Solution Reverse K Groups 13](./images/solutions/singly_linked_list_reverse_nodes_in_k_group_solution_13.png)
831+
![Solution Reverse K Groups 14](./images/solutions/singly_linked_list_reverse_nodes_in_k_group_solution_14.png)
832+
![Solution Reverse K Groups 15](./images/solutions/singly_linked_list_reverse_nodes_in_k_group_solution_15.png)
833+
![Solution Reverse K Groups 16](./images/solutions/singly_linked_list_reverse_nodes_in_k_group_solution_16.png)
834+
![Solution Reverse K Groups 16](./images/solutions/singly_linked_list_reverse_nodes_in_k_group_solution_17.png)
835+
![Solution Reverse K Groups 17](./images/solutions/singly_linked_list_reverse_nodes_in_k_group_solution_18.png)
836+
![Solution Reverse K Groups 18](./images/solutions/singly_linked_list_reverse_nodes_in_k_group_solution_19.png)
837+
![Solution Reverse K Groups 20](./images/solutions/singly_linked_list_reverse_nodes_in_k_group_solution_20.png)
838+
![Solution Reverse K Groups 21](./images/solutions/singly_linked_list_reverse_nodes_in_k_group_solution_21.png)
839+
![Solution Reverse K Groups 22](./images/solutions/singly_linked_list_reverse_nodes_in_k_group_solution_22.png)
840+
![Solution Reverse K Groups 23](./images/solutions/singly_linked_list_reverse_nodes_in_k_group_solution_23.png)
841+
![Solution Reverse K Groups 24](./images/solutions/singly_linked_list_reverse_nodes_in_k_group_solution_24.png)
842+
![Solution Reverse K Groups 25](./images/solutions/singly_linked_list_reverse_nodes_in_k_group_solution_25.png)
843+
![Solution Reverse K Groups 26](./images/solutions/singly_linked_list_reverse_nodes_in_k_group_solution_26.png)
844+
![Solution Reverse K Groups 27](./images/solutions/singly_linked_list_reverse_nodes_in_k_group_solution_27.png)
845+
![Solution Reverse K Groups 28](./images/solutions/singly_linked_list_reverse_nodes_in_k_group_solution_28.png)
846+
![Solution Reverse K Groups 29](./images/solutions/singly_linked_list_reverse_nodes_in_k_group_solution_29.png)
847+
![Solution Reverse K Groups 30](./images/solutions/singly_linked_list_reverse_nodes_in_k_group_solution_30.png)
848+
![Solution Reverse K Groups 31](./images/solutions/singly_linked_list_reverse_nodes_in_k_group_solution_31.png)
849+
850+
#### Step-by-step solution construction
851+
852+
We will first traverse the linked list and check which groups of k nodes can be reversed. Here is how the algorithm works:
853+
854+
- We initialize a node, `dummy`, and attach it to the start of the linked list, i.e., by setting its next pointer equal to
855+
the head.
856+
- We set a pointer, `ptr`, equal to the `dummy` node. We will use this pointer to traverse the linked list.
857+
- We traverse the linked list till `ptr` becomes NULL:
858+
- We initialize a pointer, tracker, to ptr. This pointer will be used to keep track of the number of nodes in the
859+
current group in the linked list.
860+
- We use a nested loop to try to move `tracker` _k_ nodes forward in the linked list. If tracker becomes NULL before
861+
moving _k_ nodes forward, the end of the linked list has been reached and the current group can not be traversed,
862+
since it contains less than k nodes. Therefore, we break out of the nested loop. Otherwise, the current group contains
863+
k nodes and tracker will point to the kth node of the current group.
864+
- After the completion of the nested loop, we check if `tracker` points to NULL:
865+
- If it does, we’ve reached the end of the linked list. The current group contains less than k nodes and cannot be
866+
reversed. Therefore, we break out of the outer loop, and the algorithm ends.
867+
- If it does not, the current group contains `k` nodes and can therefore be reversed.
868+
869+
The next step is to reverse the first group of `k` nodes. Here is how the algorithm works:
870+
871+
- For the case where tracker does not point to NULL, we declare three pointers:
872+
- current
873+
- previous
874+
- next
875+
- We call the `reverse_linked_list` function, which reverses the current group of nodes and updates the above three
876+
pointers by returning their values.
877+
- After the reversal, we have a fragmented group that has been separated from the rest of the list. The `previous` pointer
878+
now points to the first node of the reversed group while the `current` and `next` pointers now point to the first
879+
node of the next group.
880+
- We break out of the outer loop to end the algorithm once the first group has been reversed.
881+
882+
After reversing the first group of k nodes, we need to reattach it to the rest of the linked list. Here is how the
883+
algorithm works:
884+
885+
- We first need to access the last node in the reversed group. The `ptr` pointer is currently pointing to the node
886+
immediately before the last node of the reversed group. We initialize a new pointer, `last_node_of_reversed_group`,
887+
and set it equal to the next node of `ptr`. This node now points to the last node of the reversed group.
888+
- We now need to link the last node of the reversed group to the first node of the linked list coming after it. The
889+
`current` pointer is currently pointing to the first node of the next group. We set the next node of
890+
`last_node_of_reversed_group` to the `current` pointer.
891+
- We now need to link the first node of the reversed group to the last node of the linked list that comes before it.
892+
The `previous` node is currently pointing to the first node of the reversed group. We set the next node of `ptr` equal
893+
to the previous pointer.
894+
- Lastly, we need to set the `ptr` pointer equal to the last node of the reversed group, which resets its position so
895+
that we can attempt to reverse the next group. We do this by setting the `ptr` pointer equal to the
896+
`last_node_of_reversed_group` pointer.
897+
- We break out of the outer loop to end the algorithm once the first reversed group has been reattached to the linked
898+
list.
899+
900+
Finally, the last step is to repeat the above process for all groups of k nodes. This is done by simply not breaking
901+
out of the outer loop once the first group has been reversed and attached. After the linked list has been traversed,
902+
i.e., ptr becomes NULL, we return the next node of dummy, which contains the reversed linked list attached to it.
903+
904+
#### Solution Summary
905+
906+
To recap, the solution to this problem can be divided into the following four main parts:
907+
908+
- Check if there are `k` nodes present in the current group.
909+
- If the current group contains `k` nodes, reverse it.
910+
- Reattach the reversed group to the rest of the linked list.
911+
- Repeat the process above until there are less than `k` nodes left in the linked list.
912+
913+
#### Time Complexity
914+
915+
The time complexity of this solution is O(n), where n is the number of nodes in the list
916+
917+
#### Space complexity
918+
919+
The space complexity of this solution is O(1), since we'll use a constant number of additional variables to maintain the
920+
connections between the nodes during reversal
16.8 KB
Loading
27.2 KB
Loading
26.3 KB
Loading

0 commit comments

Comments
 (0)