From 1a1bbb1e27d9bbe3b3fa3619b18d9f244b10c203 Mon Sep 17 00:00:00 2001 From: anebz Date: Tue, 6 Aug 2019 20:27:05 +0200 Subject: [PATCH] 2.6. palindrome --- 02. Linked lists/2.6. palindrome.md | 113 ++++++++++++++++++++++++++++ 02. Linked lists/README.md | 4 +- README.md | 9 +++ 3 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 02. Linked lists/2.6. palindrome.md diff --git a/02. Linked lists/2.6. palindrome.md b/02. Linked lists/2.6. palindrome.md new file mode 100644 index 0000000..ffc73c4 --- /dev/null +++ b/02. Linked lists/2.6. palindrome.md @@ -0,0 +1,113 @@ +# 2.6. Palindrome + +## Check if a linked list is a palindrome + +> 1 -> 2 -> 3 -> 2 -> 1, yes + +## First idea + +We need to know list length. Get a pointer to go to the end of the list, and another from the beginning, and get one pointer going forward and another going backward. Problem, singly linked list. How to go backward? + +Iteratively create doubly linked list? But that defeats the purpose of a single linked list. Rough algorithm idea: + +1. Iterate all list to know length +2. Calculate middle node, or middle point +3. Iterate to middle point, create reverse linked list +4. From middle point onwards, check if nums[i] == reverse_list[i-middle] + +```java +public void check_permutation(LinkedListNode a){ + // Obtain length + int len = 0; + LinkedListNode head = a; + while (head != null){ + head = head.next; + len += 1; + } + + // Obtain middle node or point + // Obtain reverse list + int middle = len/2; + LinkedListNode middle_node = a; + LinkedListNode reverse_list = null; + while(middle >= 0){ + middle_node = a.next; + reverse_list.data = a.data; + reverse_list = null; + reverse_list.next = a; + middle -= 1; + } + reverse_list = reverse_list.next; + + // Check if all elements match + while(middle_node != null){ + if (middle_node.data != reverse_list.data){ + return False; + } + middle_node = middle_node.next; + } + return True; +} +``` + +This has O(n + n/2 + n/2) = O(2n) time complexity, O(n) space complexity. But... should work. O(n) and O(n) isn't that bad. + +## Hint 1 + +> A palindrome is something which is the same way forwards and backwards, what if you reverse the linked list? + +Already reversed it, but only half. The other half isn't necessary. I think. + +## Hint 2 + +> Try using a stack + +A stack? Like a hash table? + +> Stack is a linear data structure which follows a particular order in which the operations are performed. The order may be LIFO(Last In First Out) or FILO(First In Last Out). + +I don't know how I could use this + +## Hint 3 + +> Assume you have the length of the linked list, can you implement this recursively? + +I would need to go backwards from the last node, and I don't know how to do that. As per my implementation, I can only start comparing once I have saved all the reverse half-list. + +## Hint 4 + +> In the recursive approach, the middle is the base case: isPermutation(middle) is true. The node x to the immediate left of the middle: what can that node do to check if x -> middle -> y forms a palindrome= Now suppose that checks out, what about the previous node a? If x -> middle -> y is a palindrome, how can it check that a -> x -> middle -> y -> b is a palindrome? + +Getting lost here... My problem is, how can we iterate backwards if this is a single linked list. If x -> middle -> y is a palindrome, then we only need to check `a==b` for the next step, no need to check everything again. But how do I come to `x` from `middle` is my question. If I have to iterate from head to this node each time, it's not computationally effective at all! + +## Hint 5 + +> There are ways to return multiple values, you can do this with a new class + +Whattt + +## Solution + +### Reverse and compare + +Reverse whole list, then compare only first half of both. Code somewhat similar to mine + +### Iterative approach + +Reverse the front half of the list, with a stack. We need to push the first half of the elements onto a stack, 2 ways to do this depending on whether or not we know the size of the linked list. + +If we do, iterate through the first half of the elements in a standard for loop, pushing each element onto a stack. Careful when the length of the linked list is odd. + +If we don't, iterate through the linked list using the fast runner/slow runner technique. At each step in the loop, push the data from the slow runner onto a stack. When the fast runner hits the end, the slow runner will have reached the middle. By then, the stack will have all the elements from the front of the linked list, but in reverse order. + +Then simply iterate through the rest of the linked list, at each iteration compare the node to the top of the stack. + +> This is what I was trying to do, but without knowing how to use stacks. + +Stacks can be managed with push and pop. Code in [Github CTCI repo](https://github.com/careercup/CtCI-6th-Edition/blob/master/Java/Ch%2002.%20Linked%20Lists/Q2_06_Palindrome/QuestionB.java). + +### Recursive approach + +First we need to know when we've reached the middle element. We can do this by passing `length - 2` to each recursive function, if we do this N/2 times, we'll reach the middle. We still need to compare the node `i` to node `n-i`, how to do that? + +The long explanation about the algorithm can be seen in book p219. diff --git a/02. Linked lists/README.md b/02. Linked lists/README.md index 5d1eff9..773d27e 100644 --- a/02. Linked lists/README.md +++ b/02. Linked lists/README.md @@ -60,9 +60,9 @@ Node deleteNode(Node head, int d){ Sometimes two pointers are used, you iterate through the linked list with two pointers simultaneously, with one ahead of the other. The fast pointer might be ahead by a fixed amount, or hopping multiple nodes for each node that the 'slow' node iterates through. -If we have a linked list a1 -> a2 -> an -> b1 -> b2 -> bn and we want to rearrange it to a1 -> b1 -> a2 -> b2 ..., and we don't know how long the list is but we know its length is an even number, we could have one pointer p1 move every two elements for every one move that p2 does. When p1 reaches the end of the list, p2 will be at midpoint. Then, move p1 back to the front and on each iteration, p2 selects an element and inserts if after p1. +If we have a linked list a1 -> a2 -> an -> b1 -> b2 -> bn and we want to rearrange it to a1 -> b1 -> a2 -> b2 ..., and we don't know how long the list is but we know its length is an even number, we could have one pointer p1 move every two elements for every one move that p2 does. When p1 reaches the end of the list, p2 will be at midpoint. Then, move p1 back to the front and on each iteration, p2 selects an element and inserts it after p1. -> Question: then why do we iterate to the end bzw midpoint of the list? Just start rearranging from the very beginning. +> Question: then why do we iterate to the end bzw midpoint of the list? Just start rearranging from the very beginning. But we need p2 to reach the end, still that can be done with just p2, no need to move p1 as well. ## Recursive problems diff --git a/README.md b/README.md index 1ff19a0..0654013 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,15 @@ * Magic index (chapter 8) * Search in sorted matrix (chapter 10) +## Chapter 2 Linked lists + +* Remove duplicates +* Return kth to last +* Delete middle node +* Partition list +* Sum lists +* Check if palindrome + ## Chapter 7 Object-oriented design * Hash tables