Skip to content

Commit

Permalink
Reverse Nodes in k-Group
Browse files Browse the repository at this point in the history
  • Loading branch information
dksifoua committed Sep 5, 2024
1 parent 094a515 commit 70afefa
Show file tree
Hide file tree
Showing 5 changed files with 269 additions and 1 deletion.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
| 0021 | Easy | Merge Two Sorted Lists | Linked List, Recursion | [solution](./docs/0021-Merge-Two-Sorted-Lists.md) |
| 0022 | Medium | Generate Parentheses | String, Dynamic Programming, Backtracking | [solution](./docs/0022-Generate-Parentheses.md) |
| 0023 | Hard | Merge K sorted Lists | Linked List, Divide and Conquer, Heap (Priority Queue), Merge Sort | [solution](./docs/0023-Merge-K-Sorted-Lists.md) |
| 0025 | Hard | Reverse Nodes in k-Group | Linked List, Recursion | [solution](./docs/0025-Reverse-Nodes-in-k-Group.md) |
| 0033 | Medium | Search in Rotated Sorted Array | Array, Binary Search | [solution](./docs/0033-Search-In-Rotated-Sorted-Array.md) |
| 0036 | Medium | Valid Sudoku | Array, HashTable, Matrix | [solution](./docs/0036-Valid-Sudoku.md) |
| 0042 | Hard | Trapping Rain Water | Array, Two Pointers, Dynamic Programming, Stack | [solution](./docs/0042-Trapping-Rain-Water.md) |
Expand Down
105 changes: 105 additions & 0 deletions docs/0025-Reverse-Nodes-in-k-Group.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# [Reverse Nodes in k-Group](https://leetcode.com/problems/reverse-nodes-in-k-group/description/)

## Intuition

The goal is to reverse the nodes of a linked list in groups of size `k`. If the remaining nodes at the end of the list
are fewer than `k`, they should remain in their original order. We can accomplish this by recursively or iteratively
reversing each group of `k` nodes while keeping track of the list structure to connect each reversed group back to the
rest of the list.

## Approach

### Recursive

1. **Calculate the List Length:** Traverse the list once to find its length. This helps determine when to stop reversing
groups if the remaining nodes are fewer than `k`.
2. **Recursive Reversal in Groups of `k`:** Use a helper method `reverse` to handle the reversal of each k-sized group.
For each group of k nodes:
- Reverse the first k nodes by rearranging their pointers.
- After reversing, the last node of the reversed group should point to the result of recursively reversing the
remaining nodes (or the next group).
- If the remaining nodes are fewer than `k`, leave them as is.
3. **Helper Method (Reverse):** The helper function reverse performs the reversal of `k` nodes:
- Initialize two pointers, previous and current, where previous points to the current head and current is the next
node.
- For each node in the group, adjust the pointers to reverse the order.
- When the group is fully reversed, connect the last node of this group to the next reversed group (or the
remaining nodes).
4. **Return the New Head:** The new head of the list will be the head of the first reversed group.

### Iterative

1. **Calculate the Length of the List:** First, determine the length of the linked list to check if there are enough
nodes for each group reversal. This also ensures that groups with fewer than `k` nodes at the end of the list can be
left as is.
2. **Iterative Reversal in Groups of `k`:**
- Initialize pointers: `reversedHead` to track the new head of the list after reversing groups, `previous` to track
the end of the last reversed group, and `current` as the starting node of each group.
- Use a loop to process each group of `k` nodes as long as there are at least `k` nodes left.
- For each group:
- Use the helper method reverse to reverse the `k nodes starting from current.
- The `reverse` method returns a map containing:
- The new head of the reversed group (`head`).
- The last node of the reversed group (`previous`).
- The starting node for the next group (`current`).
- Connect the previous group’s tail to the head of the newly reversed group.
- Update previous and current to prepare for the next group.
3. **Return the New Head:** Once all groups are processed, reversedHead will point to the head of the newly arranged
list with k-group reversals.

#### Helper Methods:

- **`computeLength`:** Calculates the total number of nodes in the list.
- **`reverse`:** Reverses `k` nodes starting from a given head and returns a map with pointers to maintain the linked
structure.

## Complexity

### Recursive

- **Time Complexity: `O(n)`**, where `n` is the number of nodes in the linked list. Each node is visited once during the
reversal process.
- **Space Complexity: `O(n/k)`** for the recursive stack, as each recursive call processes `k` nodes. In the worst
case (all nodes are reversed in groups), the stack will hold approximately `n/k` frames.

### Iterative

- **Time Complexity: `O(n)`**, where `n` is the total number of nodes in the list. We iterate through each node once.
- **Space Complexity: `O(1)`**, ignoring the map creation, as we modify pointers in place without additional data
structures that grow with input size.

## Code

- [Java](../src/main/java/io/dksifoua/leetcode/reversenodesinkgroup/Solution.java)

## Summary

### Recursive

The recursive solution elegantly handles the problem using recursion. Here are some key points:

- **Recursive Approach:** The use of recursion simplifies the logic for handling multiple groups.
- **In-place Reversal:** The reversal is done in-place, which is memory efficient.
- **Edge Case Handling:** The solution correctly handles edge cases like empty lists, single-node lists, and when `k=1`.
- **Length Calculation:** Pre-calculating the length allows for efficient decision-making about when to stop reversing.

Overall, it is an excellent solution that effectively balances readability with efficiency, making it a robust approach
to this challenging problem.

### Iterative

The iterative solution offers several advantages:

- **Iterative Approach:** It avoids the potential stack overflow issues that could occur with a recursive solution for
very large lists.
- **In-place Reversal:** The reversal is done in-place, which is memory efficient.
- **Clear Structure:** The separation of the main logic and the group reversal into different methods enhances
readability.
- **Edge Case Handling:** The solution correctly handles all edge cases.

Compared to the recursive solution:

- This iterative approach has better space complexity (`O(1)` vs `O(n/k)`).
- It may be slightly more complex to read but avoids potential stack overflow for very large lists.
- Both solutions have the same time complexity of O(n).

104 changes: 104 additions & 0 deletions src/main/java/io/dksifoua/leetcode/reversenodesinkgroup/Solution.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package io.dksifoua.leetcode.reversenodesinkgroup;

import io.dksifoua.leetcode.utils.ListNode;

import java.util.HashMap;
import java.util.Map;

public class Solution {

public ListNode reverseKGroup(ListNode head, int k) {
int length = this.computeLength(head);

if (head == null || head.getNext() == null || k == 1 || k > length) {
return head;
}

return this.reverse(head, k, length);
}

public ListNode reverseKGroupOptimalSpace(ListNode head, int k) {
int length = this.computeLength(head);

if (head == null || head.getNext() == null || k == 1 || k > length) {
return head;
}

int left = length;
ListNode reversedHead = null;
ListNode previous = null, current = head;
while (left >= k) {
Map<String, ListNode> map = this.reverse(current, k);

if (reversedHead == null) {
reversedHead = map.get("head");
} else {
previous.setNext(map.get("head"));
}
previous = map.get("previous");
current = map.get("current");

left -= k;
}

return reversedHead;
}

private int computeLength(ListNode head) {
int length = 0;
ListNode node = head;
while (node != null) {
length += 1;
node = node.getNext();
}

return length;
}

private Map<String, ListNode> reverse(ListNode head, int k) {
if (head == null) {
return null;
}

int i = 1;
ListNode previous = head, current = head.getNext();
while (current != null && i < k) {
previous.setNext(current.getNext());
current.setNext(head);

head = current;

current = previous.getNext();
i += 1;
}

Map<String, ListNode> map = new HashMap<>();
map.put("head", head);
map.put("previous", previous);
map.put("current", current);

return map;
}

private ListNode reverse(ListNode head, int k, int left) {
if (head == null || left < k) {
return head;
}

int i = 1;
ListNode previous = head, current = head.getNext();
while (current != null && i < k) {
previous.setNext(current.getNext());
current.setNext(head);

head = current;

current = previous.getNext();
i += 1;
}

previous.setNext(this.reverse(current, k, left - k));

return head;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package io.dksifoua.leetcode.minimumdepthofbinarytree;

import io.dksifoua.leetcode.utils.TreeNode;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package io.dksifoua.leetcode.reversenodesinkgroup;

import io.dksifoua.leetcode.utils.ListNode;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;

public class SolutionTest {

final Solution solution = new Solution();

@Test
void test1() {
ListNode head = ListNode.build(new int[] { 1, 2, 3, 4, 5 });
assertArrayEquals(new int[] { 2, 1, 4, 3, 5 }, solution.reverseKGroup(head, 2).toArray());
}

@Test
void test1OptimalSpace() {
ListNode head = ListNode.build(new int[] { 1, 2, 3, 4, 5 });
assertArrayEquals(new int[] { 2, 1, 4, 3, 5 }, solution.reverseKGroupOptimalSpace(head, 2).toArray());
}

@Test
void test2() {
ListNode head = ListNode.build(new int[] { 1, 2, 3, 4, 5 });
assertArrayEquals(new int[] { 3, 2, 1, 4, 5 }, solution.reverseKGroup(head, 3).toArray());
}

@Test
void test2OptimalSpace() {
ListNode head = ListNode.build(new int[] { 1, 2, 3, 4, 5 });
assertArrayEquals(new int[] { 3, 2, 1, 4, 5 }, solution.reverseKGroupOptimalSpace(head, 3).toArray());
}

@Test
void test3() {
ListNode head = ListNode.build(new int[] { 1 });
assertArrayEquals(new int[] { 1 }, solution.reverseKGroup(head, 3).toArray());
}

@Test
void test3OptimalSpace() {
ListNode head = ListNode.build(new int[] { 1 });
assertArrayEquals(new int[] { 1 }, solution.reverseKGroupOptimalSpace(head, 3).toArray());
}

@Test
void test4() {
ListNode head = ListNode.build(new int[] { 1, 2, 3, 4, 5 });
assertArrayEquals(new int[] { 1, 2, 3, 4, 5 }, solution.reverseKGroup(head, 6).toArray());
}

@Test
void test4OptimalSpace() {
ListNode head = ListNode.build(new int[] { 1, 2, 3, 4, 5 });
assertArrayEquals(new int[] { 1, 2, 3, 4, 5 }, solution.reverseKGroupOptimalSpace(head, 6).toArray());
}
}

0 comments on commit 70afefa

Please sign in to comment.