Remove consecutive nodes in Single-Linked List - java

Can anyone help me figure it out? I create a method called remainingNodes() in the SingleLinkedList class to count remaining nodes after removing all consecutive nodes of same value in a single-linked list. However it didn't work the way it should have worked:
If there are multiple ways to remove nodes, remove the leftmost node.
[1,2,2,3,3,1] -> [1,3,3,1] -> [1,1] ->[] returns 0
[1,2,3,4,5,6] returns 6
[1,2,3,2,2,1] -> [1,2,3,1] returns 4
[1,2,2,2,3,1] -> [1,2,3,1] returns 4
public int remainingNodes(){
Node curr = start;
while (curr != null && curr.next != null) {
while(curr.value == curr.next.value){
curr.next = curr.next.next;
}
curr = curr.next;
}
int count = 0;
curr = start;
while (curr != null) {
count++;
curr = curr.next;
}
return count;
}
I can just only remove one node of a consecutive pair and I also know it is because I only add the next node of my current node = the next node of the next node of current. However, I don't really know what else should I add to get it worked like my expectation.

You have two main problems:
Missing null-pointer check for curr.next = curr.next.next;
No attempt to go backward or restart when you find a duplicate.
The first fix is easy. Change
while (curr != null && curr.next != null) {
while(curr.value == curr.next.value){
curr.next = curr.next.next;
}
curr = curr.next;
}
To
while (curr != null && curr.next != null) {
while(curr.next != null && curr.value == curr.next.value){
curr.next = curr.next.next;
}
curr = curr.next;
}
The second problem is more involved. Let's look at your logic with the example [1 1]. If I understand you correctly, you want it to convert the list to [], and return 0. I will use notation -> to mean "references" and NodeK(value, next) to refer to nodes in memory.
Initialize: start -> Node1; curr -> Node1. Node1(1, Node2). Node2(1, null).
Outer loop check if curr and curr.next are not null. They are not.
Inner loop if check curr.value (1) is the same as curr.next.value (1). They are.
Inner loop sets curr.next to curr.next.next. This redefines Node1 as Node1(1, null). curr still references Node1.
Inner loop stops because curr.next is null.
Outer loop sets curr to null.
Outer loop stops because curr is null.
The result is that start -> Node1(1, null). So the list is [1] and the return value will be 1.
It looks like you've found some code online that replaces runs of duplicates to a single item. What you're looking for is logic that removes all of the entries, so that there are none left.
To do that, you need to look an extra level deep:
if (start == null) {
return 0;
}
// Begin by checking if the start reference needs to change.
// Keep executing this until no changes are made.
boolean changed = false;
while (!changed) {
changed = false;
Node curr = start.next;
while (curr != null && start.value == curr.value) {
curr = curr.next;
changed = true;
}
// If the start needs to change, update it to the first node whose value was different.
if (changed) {
start = curr;
}
}
// Then proceed with the rest of the list in a similar manner.
changed = false;
while (!changed) {
Node previous = start;
while (previous != null) {
changed = false;
anchor = previous.next;
curr = anchor.next;
while (curr != null && anchor.value == curr.value) {
curr = curr.next;
changed = true;
}
if (changed) {
previous.next = curr;
}
}
}
That should get you started at least. There's some null checks that need to happen here, but the basic idea is what you need. You need to keep a reference to the previous node (or the start node) because you're getting rid of all of the duplicates.
Another bit that's missing is that in the case of inputs like [1 2 2 1] where you expect the result to be [], you need to run the whole algorithm again from the start if there's any changes. You're probably better off taking the idea and putting it into a function, so that you can call it repeatedly, and so that the start case and previous cases don't have so much repetition.

First of all, I would set aside the counting of the remaining nodes after nodes have been removed, as that is a trivial exercise. Let's focus on the node removals.
As you have discovered, this algorithm will need to be able to step back to nodes that were already visited in order to still remove them if needed. The problem is that when you have already walked to a next neighbor node, there is no more reference to the node you came from.
An intuitive way to solve this is to use recursion, such that the recursive call does the job for the nodes after the current head node, and then after the recursion comes back the head can be compared with the first node in the list that was returned. If the same, both nodes are removed.
Here is how that would look:
private Node recurRemove(Node head) {
if (head != null) {
head.next = recurRemove(head.next);
if (head.next != null && head.next.value == head.value) {
head = head.next.next; // Remove a twin
}
}
return head;
}
public int size() {
int count = 0;
for (Node curr = start; curr != null; curr = curr.next) {
count++;
}
return count;
}
public int remainingNodes() {
start = recurRemove(start);
return size();
}
This looks elegant in my humble opinion, but the downside is that it needs stack space, and that is limited.
Even if you would use an explicit stack as local variable instead, it is a pity that the algorithm would have an auxiliary space complexity of O(đť‘›).
Solution with O(1) auxiliary space
We could make this work with O(1) auxiliary space by realising that the linked list itself can be used as a stack. This could be done with this algorithm:
Start a new list (as empty)
Keep taking nodes from the original list (making it shorter) and prepend those nodes one by one to that new list (as if it were a stack) until duplicate values are encountered.
When that happens there is one of the twin nodes already on the "stack", while the second one is still the original (shortened) list. From that list remove pairs for as long as itself has twins at its start. Once that is done check if now the first remaining node still has the same value as the node already on the stack. If so pop the node from the stack and remove it from the original list as well.
Repeat this process until the original list is empty.
The "stack" is now the list we want to have but in reversed order.
To get the list back in its intended order, repeat the above steps once more. This time no more nodes will be removed, but the list will be reversed still, which is the intention.
Here is an implementation:
public int reverseAndRemoveTwins() {
Node right = start;
start = null; // We will prepend unique values to this (stack)
int count = 0; // This time we will also keep a counter (size of stack)
while (right != null) {
Node next = right.next;
if (start == null || start.value != right.value) {
// Move the right node to become the head of the left list (reversal!)
right.next = start;
start = right;
right = next;
count++;
} else {
// Remove all identical values at the right
boolean odd = false;
while (right != null && start.value == right.value) {
right = right.next;
odd = !odd;
}
// ...and pop the one at the left too when an odd count was deleted
if (odd) {
start = start.next;
count--;
}
}
}
return count;
}
public int remainingNodes(){
reverseAndRemoveTwins(); // This also reverses the list
return reverseAndRemoveTwins(); // This reverses it back
}
This code seems less elegant, but if you care about memory efficiency it is the way to go.

Related

Add Two Numbers: LeetCode - Java solution

I'm trying to figure out the Java solution for the problem Add Two Numbers from Leetcode.
Below is LeetCode's Java solution but I have questions:
Why is dummyHead.next returned as the result when in the while loop never assigned the number to this listnode?
How works curr assign to dummyHead if curr is equal to dummyHead not the other way?
class Solution {
// Add Two Numbers (Java improved)
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode dummyHead = new ListNode(0);
ListNode curr = dummyHead;
int carry = 0;
while (l1 != null || l2 != null || carry != 0) {
int x = (l1 != null) ? l1.val : 0;
int y = (l2 != null) ? l2.val : 0;
int sum = carry + x + y;
carry = sum / 10;
curr.next = new ListNode(sum % 10);
curr = curr.next;
if (l1 != null)
l1 = l1.next;
if (l2 != null)
l2 = l2.next;
}
return dummyHead.next;
}
}
The important bit is new ListNode(0) that is what dummyHead and curr start with. Not different ListNode(s). So if you swapped curr and dummyHead like
ListNode curr = new ListNode(0);
ListNode dummyHead = curr;
it would behave the same way.
LinkedList always have two fields: one is value and second one is the pointer to next node. If it's last node than next points to null.
Here in the code you mentioned:
Line 1-3 :
generate new result linkedlist head using dummyHead and assign a variable curr to that head for moving and assigning purposes.
Carry is only used to check if sum is greater than 10 will be forwarded to next iterations.
In while loop:
we are looping untill all the values become null.
If one or two value is null than we are assiging 0 using ternary oprator, and generating sum.
After sum is generated modules will be consider as carry and divison will be consider as value of node.
After loop is done we are returning head of linkedlist using the variable we have created.
It should be dummyHead.next as dummyHead is the node 0 we have intialized, but actual result starts from the next value.
The quick explanation is there is a chain of nodes, provided by next in each Node. dummyHead is used to point to the first node of the chain. Initially, dummyHead and curr are the same node. They will be the same node until either one is reassigned.
The results are to be returned as a linked list, the structure / code of which is described in the comments above the solution header.
The correct code needs to return the head (first node) of the singly linked list. The head node will link to the 2nd node, the 2nd node will link to the 3rd, and so on for as many nodes needed. The last node will link to null.
ListNode dummyHead = new ListNode(0);
ListNode curr = dummyHead;
dummyHead and curr are reference types. At this point, they are two names for the same Object: val in one is the same as val in the other. next in one is the same as next in the other. What happens when the following line is executed?
curr.next = new ListNode(sum % 10);
next in dummyHead and next in curr point to the newly created Node. Remember, in the initial iteration, they are two names for the same object.
What happens when the following line is executed?
curr = curr.next;
Now, curr points to the newly created Node.
dummyHead still points to the first node -- the one to be returned.
They no longer refer to the same Object.
And next in dummyHead points to the 2nd node in the list, or null if the list has only the one node.
So, as the iteration progresses, a new Node is created. The last node in the chain, called curr, is linked to the new node. Then, curr is updated to point to the new node.
And dummyHead continues to point to the first node.

Removing Duplicates from linked list. Why are the position of "prev = head" and "p2 = p2.next" not outside the else statement?

For the first function, shouldn't "prev = head" be outside of else because we want to set the previous every time before we change the head value?
For the second function, shouldn't "p2 = p2.next" be outside of else because we want to go next every time?
Thank you guys.
//This would take O(n) but would require extra space O(n)
public static Node removeDuplicates(Node head){
Node prev = null;
Set<Integer> hs = new HashSet<Integer>();
while(head!= null){
if(hs.contains(head.data)){
prev.next = head.next;
}
else{
hs.add(head.data);
//why is prev = head here instead of out of the else statement?
prev = head;
}
head = head.next;
}
return head;
}
//This would take O(n^2) but no extra space is required.
public static Node removeDuplicatesTradeOff(Node head){
//pointer 1 and pointer 2.
Node p1 = head;
while(p1.next != null){
Node p2 = p1;
while(p2.next != null){
if(p1.data == p2.next.data){
p2.next = p2.next.next;
}
else{
//why is p2 = p2.next here instead of out of the else statement?
p2 = p2.next;
}
}
p1 = p1.next;
}
return head;
}
Shouldn't "p2 = p2.next" be outside of else because we want to go next every time
I think it would be more accurate to say that we want to have a different next available every time, instead of saying that we want to "go next" every time.
We don't always want to "go next". When prev.next has changed due to another operation (in this case removal of a duplicate) we want to stay where we are, because prev.next has already changed and is now pointing to a node further ahead (because a duplicate node has just been removed).
In other words, we don't want to have a different prev every time, we just want to have a different prev.next every time. So as long as prev.next advances every time, we don't care if prev stays the same sometimes.
That's why in both methods prev (or p2) only advances in the else branch, while prev.next (or p2.next) is updated (advances) only in the if branch.
Think of these two as different operations, the else branch being "go next" and the if branch being "drop next". When you drop a node ahead of you, you did not move (true!), but since you did drop one node ahead of you, now there is a new node ahead of you, so did not have to move. So you can just continue with if/else checks and sooner or later you will reach the end or you will drop the last node ahead of you.
One input example to illustrate this point is
head(1) -> 1 -> 1 -> 1 -> null
With such an input, the algorithm would only do
drop next
drop next
drop next
and it would be done.
No "go next" happened at all.
Result
head(1) -> null
Using solution 1 as a reference because the answer applies to both solutions. When you find a duplicate at the current node (head), you set the previous node’s (prev) next node to the current node’s next node (which removes the duplicate). On the next iteration, you will go to the next node in the list which is already being pointed at by the previous node. So, there is no need to overwrite prev. Another way to think about it is that the head you would be setting prev to is the node that you just removed. You wouldn’t want to do that.
Before removal:
node1 (prev) -> node2(head) -> node3
After removal:
node1 (prev) -> node3 (head)
As you can see, prev stays the same. No need to update it.
removeDuplicates:
If we remove a duplicate, the current removed node should not be assigned to prev. In fact prev is the previous node in the remaining kept list of uniques.
As is entirely evident, when there are more than 2 repeated values.
... -> prev: [42] -> [42] -> [42] -> [51] -> ...
should become
... -> prev: [42] -> [51] -> ...
removeDuplicatesTradeOff:
The same case. p2 (the previous node) may only advance when no duplicate was found.
Then there is a tiny bug:
removeDuplicates should return the new head normally - in case the head node is removed:
head = remove...(head); // Must assign, should the head node itself be removed.
Now it returns null. However for duplicates, the head node is never deleted. So either make it a void function or return the old head.
public static Node removeDuplicates(Node head){
Node prev = null;
Set<Integer> hs = new HashSet<>();
Node current = head;
// Loop-invariant: hs.isEmpty() || prev != null
while (current != null) {
if (hs.contains(current.data)) {
prev.next = current.next;
} else {
hs.add(current.data);
prev = current;
}
current = current.next;
}
return head;
}
Also notice that it is bad style using the name head for a running pointer.

Java Linked List pointer confusion

This Code below is from a java LinkedList implementation.The method adds a string element at an index point of the list and is taken from one of my cs books.
The linked list class has 2 global private variables
Node first;
Node last;
public void add(int index, String e) {
if (index < 0 || index > size()) {
String message = String.valueOf(index);
throw new IndexOutOfBoundsException(message);
}
// Index is at least 0
if (index == 0) {
// New element goes at beginning
first = new Node(e, first);
System.out.println("ran");
if (last == null)
last = first;
return;
}
// Set a reference pred to point to the node that
// will be the predecessor of the new node
Node pred = first;
for (int k = 1; k <= index - 1; k++) {
pred = pred.next;
}
// Splice in a node containing the new element
pred.next = new Node(e, pred.next);
System.out.println(toString());
// Is there a new last element ?
if (pred.next.next == null)
System.out.println("ran");
last = pred.next;
}
My question
I don't understand how Node first, last get updated in the condition below
Suppose you have a list that looks like ["1","2","3","7","4","5,"6"]
Then you add the the element "4" to index 3
So, the list looks likes ["1","2","3","4","7","4","5,"6"], but looking at the code of the add method I don't know how the first or last node pointers gets updated. Because in my mind these are the only pieces of code that run because the index isn't 0 and the last doesn't change
EDIT
The Object Node first is used in the toString method(not shown) to traverse through the collection
// Set a reference pred to point to the node that
// will be the predecessor of the new node
Node pred = first;
for (int k = 1; k <= index - 1; k++) {
pred = pred.next;
}
// Splice in a node containing the new element
pred.next = new Node(e, pred.next);
System.out.println(toString());
Before the add, the first element is "1" and the last element is "6".
After the add, the first element is "1" and the last element is "6".
You're right that the first and last dont change-- they don't need to, because you haven't changed the first or the last element.
I think you are getting bogged down by the way this linked list implementation works. The first and last pointers exist to just keep track of the start and end of the list. Hence, when you insert an element in the middle of the list, these pointers do not get updated, nor is there any need for such an update. But they do get updated should an insert land before the current head or after the current tail of the list. Here is the code which handles head inserts:
if (index == 0) {
// New element goes at beginning
first = new Node(e, first);
System.out.println("ran");
if (last == null)
last = first;
return;
}
The critical line here is actually this:
first = new Node(e, first);
The first pointer gets assigned to a new node, which in turn points to the old first node. Similarly, should an insert of a new node land at the end of the list, the following code will handle that:
if (pred.next.next == null) {
System.out.println("ran");
last = pred.next;
}
Here the last pointer is assigned to the new node which was inserted after the old last.
But other than these two edge cases, there is no need to update first and last with inserts.
If the index is 0, the node is added in the beginnng and the for loop doesn’t work.
If it is 1, the loop again doesn’t execute and node is simply added to the list.
However if it something else, then until the index-1 position is reached the loop shifts each element one step behind. Then the code just outside the loop (which slices in a node containing new element) inserts the element at index.
If after the loop has executed, the index turns out to be the last one then the last node is updated.
Hope this helps!

Understanding Doubly-Linked List Node Removal in Java

Clarification: The main question is which node is something like current.previous.next = current.next actually pointing to?
I'm using some sample code I found on a YouTube video, but really trying to break it down and understand it. Everything works as-is, I've added comments to each section to help me understand. Where I am really running into problems explaining what is happening in plain English is for node removal when the code start using previous and next in the same line. I'm not following exactly what it's pointing to now. I've worked through the code in the debugger, but I need some plain English explanation if someone wouldn't mind.
Let's say for example I've got a DLL that has 3,4,5,6,7. So I'm going to remove 5, which is the 3rd index. Here is the method for removal.
//Method to remove node at a specified position
public void removeAt(int index) {
//If the head doesn't exist, break out of the logic
if(head == null) return;
//If the requested index is smaller than 1 or greater
//than the size of the list, break out.
if(index < 1 || index > size) return;
//Declares the currently used link as the head
Link current = head;
//Declare int i counter for use in while loop
//While i is less than the index set current to the next node
//and add to the counter i
int i = 1;
while(i < index) {
current = current.next;
i++;
}
//If the next node doesn't exist, set current...previous next?
if(current.next == null) {
current.previous.next = null;
}
//Else if the node before the current is null, set current to
//the next node and then set the previous to null (I thought it
//already was null??) Set the head to the current node
else if(current.previous == null) {
current = current.next;
current.previous = null;
head = current;
}
//If none of the above conditions, set current previous next?? to
//the next node and current next previous to the previous node??
else {
current.previous.next = current.next;
current.next.previous = current.previous;
}
//Subtract from the size of the list
size--;
}
My main understanding issues come in when it starts using current.previous.next and current.next.previous. To me, current.previous.next is just saying to remain at current. If I have three numbers, 3 4 5 and current is 4, then previous is 3, so next would just go back to 4. I know this isn't right, but after reading other posts here, the Javadoc, and watching videos, I'm still not understanding what is going on.
Here is the other class:
public class Link {
private int data;
public Link previous;
public Link next;
public Link(int data) {
previous = null;
this.data = data;
next = null;
}
public Link(Link previous, int data, Link next) {
this.previous = previous;
this.data = data;
this.next = next;
}
public int Data() {
return data;
}
}
I'd appreciate some explanation. Thanks!
If node.next is null, it means that the node is the last element of the list. So if you try to remove the last element of the list, the element before the last element becomes the last element. Which is what this code does
if(current.next == null) {
current.previous.next = null;
}
If node.previous is null, it means that it is the first element of the list. First, we have to keep a reference to the following element, define that the following element must not have a previous element, and then define it as the head of the list. Which is what this code does
if(current.previous == null) {
current = current.next;
current.previous = null;
head = current;
}
Then to stay on your example
if current is 4, then current.previous is 3 and current.next is 5
so
current.previous.next = current.next
define that 3.next is now 5
and
current.next.previous = current.previous
define the 5.previous is now 3
So then 4 is not reference anymore in the list
Doubly link list (DLL) is a list that can be traversed in both directions as you already know. Each node has previous and next pointers to their siblings allowing navigation to both directions. You already have head variable pointing to the head of the linked list.
In the remove method you passed in the index of the node you wish to delete.
while(i < index) {
current = current.next;
i++;
}
Above while loop sets the current node to be the node we wish to delete. There are three cases to be handled at this point.
Current node can be the first node in the list. So current.previous points to nothing and current.next may point to next node if there is one.
Current node can be the last node of the list so that current.next = null.
Current node is neither head or tail of the list so that current.next and current.previous have corresponding values.
if(current.next == null) {
current.previous.next = null;
}
Above code section will is the second point mentioned above. current points to last node. In order to remove last node you have to go back to current.previous and set its link to null so that current will no longer be accessible from the previous node.
else if(current.previous == null) {
current = current.next;
current.previous = null;
head = current;
}
checks to see if the current node is the head of the node. If current is pointing to head of the linked list, it does not have any previous sibling so that current.previous points to null. So current.next must become the new head of the list. With current.previous = null we set the previous link to null and set this current head as the head variable. Now previous node has gone. No references to it.
Third case is the above bullet point 3. It's handled by
else {
current.previous.next = current.next;
current.next.previous = current.previous;
}
where we want to remove current node. That means we need to link current.previous and current.next as siblings.
As a final note, your Link class uses public fields. They should be private and access via getters and setters.

deleting a specific item from a java LL

So I'm trying to delete an item from a linked list in java.I'm not using java's predefined LL but I'm working with my own.
I know the concept to delete an item is to traverse in the link and compare the data in the list one by one.
so here's what I came up with, but it doesn't work!
public void delStudent(int regN) {
Node current = head;
Node q = head;
if (current.getStudent().getRegN() == regN) {
head = head.link;
}
for (int i = 0; i < this.length() - 1; i++) {
if (current.getStudent().getRegN() != regN) {
current = current.link;
q = current;
}
}
q.link= current.link.link;
}
Well, if your list is empty, the execution of the if statement in the beginning will immediately give a NullPointerException (because current will be null). Generally, for the LinkedList delete method, you must consider three cases: size == 0, size == 1, and size > 1 (where size is the number of nodes in the Linked List).
public void delStudent(int regN) {
Node current = head;
Node previous = head;
while (current != null ){ // keep traversing till end of list
if (current.getStudent().getRegN() == regN) { // found it!
previous.link = current.link; // relink
if (current == head){ // edge case : removed first element
head = current.link; // move head forward.
}
break;
} else {
previous = current;
current = current.link;
}
}
}
The above code assumes regN is unique and that there is only one student with that regN. Hope this helps.
The mistake is (I think) in these three lines:
current = current.link;
q = current;
(where you set q to be the same as current) and
q.link= current.link.link;
(and maybe also in using length() depending on it's implementation)
To see why let's look at how to delete in more detail:
Let's consider there are three nodes in your list x->y->z and you want to delete y.
In order to do so you need to set x.link = z.
To return to you example it means that the variable q should store the element before current, then deleting current can be done by
q.link = current.link;
In order order to have q be the predecessor of current you have to reverse the two lines above, i.e., use
q = current;
current = current.link;
Why am I saying depending on the implementation of length ? If you implementation of length just returns some number maintained by increasing whenever you add a value to the list, you should also decrement it when deleting one. If length traverses the list in order to find the number of elements then its ok, although not very efficient.
One last comment: your code will not work (even with the fixes I explain above) when there is no element with the given regN. Why? Because you always delete one element. Moreover, you might want to rethink the logic inside the loop. Currently if the element to delete is the second one and there is 1000000 elements you will run the loop nearly 1000000 times.
When you are checking each node you need to delete the node or break once you find a match. You need to maintain a node for the previous node to be able to delete the current node once you find a match.
I just realized that you were attempting to to that with Node q
for (int i = 0; i < this.length() - 1; i++) {
if (current.getStudent().getRegN() != regN) {
q = current;
current = current.link;
} else{ //we have a match
//remove the link to the current item
q.link = current.link;
break;
}
if you are using a doubly linked list you can use node.prev().link = current.link;

Categories

Resources