Five LeetCode "moderate difficulty" singly linked list questions

1. Pointing to Offer II 021. Delete the last nth node of the linked list

insert image description here
Title description:

Find the last nth node of the linked list and delete it

Three solutions:

The first solution (single pointer):

1. Traverse the linked list to find the length L
of the linked list 2. Subtract n from the length L of the linked list , which is the last nth node 3. Find the predecessor node of the last nth node, and let the next of the predecessor node point to the last nth node The node after the node. 4. The predecessor node of the last n nodes is L-n+1 5. Because if the head node to be deleted is the head node, the head node to be deleted has no pioneer node, so set a sentinel bit 6. Sentinel bit next points to head , so that the deletion of the head node is also converted into a general situation



insert image description here

code show as below:

class Solution {
    
    
    public ListNode removeNthFromEnd(ListNode head, int n) {
    
    
    			
        ListNode dummy = new ListNode(0, head);//设置哨兵位
        int length = getLength(head);//求出链表长度
        ListNode cur = dummy;//用cur去遍历
        //遍历L-n+1次就是要删除结点的前驱结点
        for (int i = 1; i < length - n + 1; ++i) {
    
    
            cur = cur.next;
        }
        //前驱结点的next指向要删除结点的后一个结点
        cur.next = cur.next.next;
        return dummy.next;
    }
		//求出链表长度
    public int getLength(ListNode head) {
    
    
        int length = 0;
        while (head != null) {
    
    
            ++length;
            head = head.next;
        }
        return length;
    }
}

Author's summary:

If the node to be deleted is the last node, the time complexity of the code will reach 2×n , although it is not very slow, but it still does not achieve the desired effect.

Complexity analysis:

  • Time complexity O(n) , where Nis the number of nodes in the given linked list.
  • Space complexity O(1)

The second solution (stack):

Implementation ideas:

We can store the nodes in the linked list on the stack one by one while traversing the linked list. According to the principle of "first in, last out" of the stack, the nth node popped up in our stack is the node we want to delete. And the node at the top of the stack is the predecessor node of the node to be deleted. In this way, the deletion operation becomes very convenient.

Implementation:

1. Create a stack and push the nodes in the linked list into the stack one by one.
2. After the linked list traversal is completed, pop the nodes from the stack in turn. When the nth node is reached, it is the node we want to delete. 3
. Since the node at the top of the stack is just the predecessor node of the nth node, it is enough to point the next of the node at the top of the stack to the next of the nth node. 4. Since the head node needs to be considered to be deleted, We set a sentinel bit so that even if the node to be deleted is the head node, the processing method is the same as a normal node.

As shown in the picture:

insert image description here

code show as below:

class Solution {
    
    
    public ListNode removeNthFromEnd(ListNode head, int n) {
    
    
        ListNode dummy = new ListNode(0, head);//创建哨兵位
        Deque<ListNode> stack = new LinkedList<ListNode>();//创建一个栈
        ListNode cur = dummy;//cur代替哨兵位遍历
        while (cur != null) {
    
    
        //依次放入
            stack.push(cur);
            cur = cur.next;
        }
        //弹出倒数n个结点
        for (int i = 0; i < n; ++i) {
    
    
            stack.pop();
        }
        //当前栈顶就是要删除结点的前驱结点
        ListNode prev = stack.peek();
        //
        prev.next = prev.next.next;
        return dummy.next;
    }
}

Author's summary:

The biggest feature of the stack is first in, last out, so reverse order output is an application scenario that stacks are often used in. First put all the elements into the stack one by one, and then pop all the elements out of the stack and output, thus realizing the output in reverse order.

Complexity analysis:

  • Time complexity O(n) , where Nis the number of nodes in the given linked list.
  • Space complexity O(n) , where Nis the length of the linked list, mainly the overhead of the stack.

The third solution (double pointer):

Problem-solving ideas:

Through the double pointer, since the penultimate nth node is deleted, we can use the distance difference, the fast pointer will go n+1 steps first, and then the two pointers will go together. When the fast pointer is empty, the slow The pointer is the n-1th node from the bottom (n-1 is the predecessor node of n).

Implementation:

  1. Define two pointers, cur and prev
  2. Cur takes n+1 steps first, and then traverses backwards together with the two pointers until cur is empty. At this time, prev is the predecessor node of the node to be deleted, just fine prev.next=prev.next.next.
  3. Define a sentinel bit so that both cur and prev point tonewHead

As shown in the picture:
insert image description here
the code is as follows:

class Solution {
    
    
    public ListNode removeNthFromEnd(ListNode head, int n) {
    
    
    //判断是否为空
        if(head==null) {
    
    
            return head;
        }
        //创建一个哨兵位
        ListNode newHead=new ListNode(0);
        //哨兵位的next指向head
        newHead.next=head;
        //将cur和prev同时指向newHead
        ListNode cur=newHead;
        ListNode prev=newHead;
		//快指针先走n+1步(也可以快指针总head走,走n步就可以,核心思路就是慢指针要和快指针之间的距离差1)
        for(int i=0;i<n+1;i++) {
    
    
            cur=cur.next;
        }
        //两个指针一起走
        while(cur!=null) {
    
    
            cur=cur.next;
            prev=prev.next;
        }
        //最后删除倒数第n个结点
        prev.next=prev.next.next;
        //返回newHead的next
        return newHead.next;
        
    }
}

Author's summary:

Different from the previous work of finding the k-th node from the bottom, this question is to delete the k-th node from the bottom, so we need to find the predecessor node of the k-th bottom. The core idea is that the fast pointer and the slow pointer are at the beginning The distance difference must be n+1, whether the fast pointer at the same position takes n+1 steps first, or the slow pointer is the previous node of the fast pointer, and the fast pointer takes n steps, the final goal to be reached is the same as the fast pointer The distance is n+1, so it will be easy to master the core idea and think about this question again.

Complexity analysis:

  • Time complexity O(n) , where Nis the number of nodes in the given linked list.
  • Space complexity O(1) .

2. Delete duplicate elements in the sorted list II (emphasis)

insert image description here

Title description:

Delete all repeated numbers in the ordered linked list, leaving only the numbers that appear once.

Things to consider:

  1. Consider head-cut and tail-cut,
  2. Note the null pointer exception,

A common state:

1->2->3->3->4->4->5->null (delete in the middle)

Two special states:

1->1->1->2->3->null (头删)
1->2->3->3->3->null (尾删)

normal state

insert image description here

Problem-solving ideas:

  1. Define three pointers,prev cur curNext
  2. Determine whether the vals of cur and curNext are equal, if they are not equal, give cur to prev, curNext to cur, curNext=curNext.next.

As shown in the picture:

insert image description here

Repeat the above operations untilcur.val == curNext.val

insert image description here

Judge again curNext.val == cur.valwhether it is true, if it is true, continue to repeat the above operation,

The final running result:

insert image description here

Special state (when the head node is repeated)

insert image description here

If the above code is still running at this time, a null pointer exception error will pop up, because prev is null, so prev.next will generate a null pointer exception. The solution is to determine whether it is header deletion, if If the head is deleted head=curNext, move the head node backward.

insert image description here

At this point our header removal is complete.

operation result:

insert image description here

Special state (when removing tail nodes)

insert image description here

Here you need to pay attention to the null pointer exception, the details are as follows

insert image description here
After solving, run the result:

insert image description here
The specific code is as follows:

class Solution {
    
    
    public ListNode deleteDuplicates(ListNode head) {
    
    
        if(head==null||head.next==null) {
    
    
            return head;
        }
        ListNode prev=null;
        ListNode cur=head;
        ListNode curNext=cur.next;
        while(curNext!=null) {
    
    
            //当他们的值不同时,集体后移,注意顺序
            if(cur.val!=curNext.val) {
    
    
                prev=cur;
                cur=curNext;
                curNext=curNext.next;
            } else {
    
    
                //当他们的值相同时,并且curNext不为空,进入循环
                //注意要将curNext!=null的判断条件写在左边
                //逻辑运算&&,先判断左边,如果左边为真才判断右边,
                //当你的curNext!=null写在右边时,
                //他会先进行curNext.val的操作,此时你的curNext已经为空
                //再去访问就会发出空指针异常
                while(curNext!=null&&cur.val==curNext.val) {
    
    
                    curNext=curNext.next;
                }
                //判断prev是否为空,如果为空就是头删
                if(prev!=null) {
    
    
                    //不为空将prev的next指向curNext
                    prev.next=curNext;
                } else {
    
    
                    //为空则头节点变成curNext
                    head=curNext;
                }
                //后续过程不变
                cur=curNext;
                //判断curNext是否为空,如果为空则不进行该操作
                if(curNext!=null)
                curNext=curNext.next;
            }
        }
        return head;
    }
}

Author's summary :

This question mainly considers the degree of care of the answerer. If you don’t pay attention, it may cause out-of-bounds access, so you need to always pay attention to places where mistakes may pop up, and drawing pictures yourself will be very helpful for writing this kind of programming questions. The big benefit, it can help us find small problems that we usually don't notice.

Complexity analysis:

  • Time complexity O(n) , where Nis the number of nodes in the given linked list.
  • Space complexity O(1) .

3. Delete the node in the linked list

insert image description here
Title description:

You are given a node in the linked list, and you cannot access the head node. To delete this node in the linked list ,
the example is as follows: 1->2->3->4->5->null node=3;
the output is: 1- >2->4->5->null

Problem-solving ideas:

As mentioned in the title, we cannot access the head node. Usually, when we delete a node, we always find the predecessor node of the node to be deleted, and let the next node of the predecessor node point to the next node of the node to be deleted.
insert image description here

But the title directly gives the node to be deleted, and its previous node that we cannot access, we can consider giving the val and next of the next node to the node to be deleted, because the next node's next Given our node, we can’t find the next node of the original node, so the deletion is naturally completed. If the C language needs to release memory, the next node can be saved in advance, and finally free().

As shown below:
insert image description here

code show as below:

class Solution {
    
    
    public void deleteNode(ListNode node) {
    
    
        node.val=node.next.val;//替换为下一个结点的val
        node.next=node.next.next;//替换为下一个结点的next
	}
}

Summarize:

Since you can't delete yourself first, then make yourself into a son, and then pretend that you are a son to support your grandson

Complexity analysis:

  • Time complexity O(1)
  • Space complexity O(1)

4. Rearrange the linked list

insert image description here

Title description:

It probably means:
insert image description here

Problem-solving ideas:

Idea one:

Singly linked list does not support random access, nor does it support traversal from back to front, so we can create a sequential list, store the nodes in the single linked list into the sequential list one by one, and finally find the node according to the subscript and rebuild it linked list.

Implementation:

  1. build a sequence table
  2. Store the linked list in the sequence list
  3. Create a sentinel position to connect the subsequent reordered nodes one by one

code show as below:

class Solution {
    
    
    public void reorderList(ListNode head) {
    
    
        if(head==null||head.next==null) {
    
    
            return;
        }
        //利用顺序表存储,然后按指定位置
        //我这里没有直接使用库的顺序表,
        ListNode [] array=new ListNode[50000];
        ListNode cur=head;//
        int i=0;
        //从0下标存入
        while(cur!=null) {
    
    
            array[i]=cur;
            cur=cur.next;
            i++;
        }
        //最后i是元素个数
        ListNode newHead=new ListNode(0);
        ListNode newPrev=newHead;
        int a=0;//a是下标
        int b=i-1;//b也是下标,i是元素个数,元素个数-1就是最后一个元素的下标
        int count=1;//根据奇偶判断读取哪个下标的元素
        //最后i+1就是链表的结点个数+1,
        //count是从1开始的,所以当count=i+1时,就代表读取完了所有结点
        while(count!=i+1) {
    
    
        //如果是奇数,就顺序读取
            if(count%2!=0) {
    
    
               newPrev.next=array[a];
               newPrev=newPrev.next;
                a++;//
            } else{
    
    
            //如果是偶数就逆序读取
                 newPrev.next=array[b];
                 newPrev=newPrev.next;
                 b--;
        }
        count++;
    }
    //将左后一个结点呢next置为kong,防止循环
       newPrev.next=null;
       //将头节点改为newHead的next
        head=newHead.next;
    }
}

Complexity analysis:

  • Time complexity O(n) , where n is the length of the linked list
  • Space complexity O(n) , n is the length of the sequence table

Idea 2 (find the midpoint of the linked list + reverse the linked list + merge the linked list)

Idea 2:

By finding the midpoint of the linked list and reversing the linked list from the midpoint, the implementation at this time is very similar to judging the palindromic linked list! After reversing the linked list, one node starts from the beginning, one node starts from the head of the reversed linked list, traverses backwards in turn, and finally uses the sentinel bit to merge the linked list

As shown in the picture:

insert image description here
Notice:

What we need to consider is the exit condition of the merged linked list. If it is a linked list with an odd number of nodes, we adopt it, which cur!=prevwill cause the last intermediate node not to be merged into the new linked list, so it needs to be considered separately

Specific steps:

  1. find the middle node
  2. The reverse order includes all nodes in the middle and behind
  3. merge singly linked list

The code is as follows (note the comments)

class Solution {
    
    

     public void reorderList(ListNode head) {
    
    
     if(head==null||head.next==null) {
    
    
             return;
         }
        //找到中间结点
        ListNode prev=head;//慢指针
        ListNode cur=head;//快指针
        while(cur!=null&&cur.next!=null) {
    
    
            cur=cur.next.next;
            prev=prev.next;
        }
        //逆序单链表
        cur=reverse(prev);
        prev=head;//prev从头遍历
        ListNode newHead=new ListNode(0);
       ListNode newPrev=newHead;
        int i=1;//来判断调用谁的结点
        while(true) {
    
    
            if(i%2!=0) {
    
    
               newPrev.next=prev;//衔接该结点
             //当两个结点相等时退出循环
               if(prev==cur) {
    
    
               //并将newPrev置为下一个结点
            		newPrev=newPrev.next;
                  	 break;
               }
               prev=prev.next;//prev指向下一个结点
            } else {
    
    
           	  newPrev.next=cur;//衔接该结点
           	 //当两个结点相等时退出循环
           	 if(prev==cur) {
    
    
           	 //并将newPrev置为下一个结点
           		newPrev=newPrev.next;
             	   break;
          	  }
            	cur=cur.next;//cur指向下一个结点
            }
            i++;
            newPrev=newPrev.next;//置为下一个结点
        }
        newPrev.next=null;//将尾结点的next置为null,防止循环

     }
     //逆置链表函数
    public ListNode reverse(ListNode head) {
    
    

        ListNode prev=null;
        ListNode cur=head;
        while(cur!=null) {
    
    
        ListNode curNext=cur.next;
        cur.next=prev;
        prev=cur;
        cur=curNext;
        }
        return prev;
    }

Author's summary:

The author of this question did not have an idea when he first did it, but by drawing a picture, he gradually got an idea. Regarding the conditions for me to merge the linked list and exit the loop, you can draw a picture to see it, and the corresponding picture can be better. To master the rules, including why the author if ( prev == cur )writes prev=prev.next或cur=cur.nextin front of the , you can draw pictures and think about it. In short, draw more pictures, think more, and start typing more codes.

Complexity analysis:

  • Time complexity O(n) , where n is the length of the linked list
  • Space complexity O(1)

5. Sword Pointer Offer II 077. Linked list sorting (important!)

insert image description here

Title description:

Given an unordered linked list, you need to sort the linked list

Problem-solving ideas:

Take the first node of the linked list as an ordered node, compare the subsequent nodes with the head of the ordered list, if it is smaller than the head node, then insert the head, if it is smaller than the tail of the ordered list, then Tail plug, otherwise it is the middle plug.

As shown in the picture:

insert image description here

Let's first list all the pointers that will be used

  ListNode cur=head.next;//后续链表的头指针
  
  ListNode newhead=head;//当前有序链表的头指针
  
  newhead.next=null;//与后续链表断开联系
  
  ListNode prev=newhead;//有序链表向后遍历指针的前驱指针
  
  ListNode prevNext=newhead.next;//用于有序链表向后遍历的指ListNode last=newhead;//尾结点
  
  ListNode curNext=cur.next;//用于找回后续链表的指针

Implementation:

  1. First divide the linked list into an ordered linked list and an unordered linked list
  2. The head node of the ordered linked list is compared with the node pointed to by cur in the unordered linked list. If the head node is smaller than the node corresponding to cur, the head is inserted .
  3. Compare the tail node of the ordered linked list with the node pointed to by cur in the unordered linked list. If the tail node is smaller than the node corresponding to cur, it means that there is no node larger than this node in the linked list. So just plug it in !
  4. The rest of the cases are inserted in the middle. We find the first node that is larger than the node corresponding to cur, and insert cur in front of the node to complete the middle insertion .
  5. After the insertion is completed, let prev return to the position of the head node, that is prev=newHead, let prevNext become the second node of the ordered list again, that isprevNext=prev.next;

Insertion process:
(There is a small flaw, the process of comparing cur with the tail node is ignored, and the comparison is done directly in order, but the general idea is this)
insert image description here

code show as below:

class Solution {
    
    
    public static ListNode sortList(ListNode head) {
    
    
        if (head == null || head.next == null) {
    
    
             return head;
         }
         ListNode cur=head.next;//后续链表的头节点
         ListNode newhead=head;//当前有序链表的头节点
         newhead.next=null;//与后续链表断开联系
         ListNode prevNext=newhead.next;//有序链表向后遍历的结点
         ListNode prev=newhead;//有序链表向后遍历结点的前驱结点
         ListNode last=newhead;//尾结点
         while(cur!=null) {
    
    
             //头插
             ListNode curNext =cur.next;
             if(newhead.val>=cur.val) {
    
    
                 cur.next=newhead;
                 newhead=cur;
             } else if(last.val<cur.val) {
    
    
             		//尾插
                 last.next=cur;
                 last=cur;
                 last.next=null;
             } else {
    
    //中间插入
             //找到大于等于cur.val的结点
                while(cur.val>prevNext.val) {
    
    
                //prev一直保存prevNext的前一个结点
                    prev=prevNext;
                    //prevNext置为它的下一个结点
                    prevNext=prevNext.next;
                }
                //将cur插入prev和prevNext之间
                prev.next=cur;
                cur.next=prevNext;
             }
             //重置prev
             prev=newhead;
             //重置prevNext
             prevNext=prev.next;
             //重置cur
            cur=curNext;
         }
         //返回新头节点
    return newhead;
   } 
}

Author's summary:

The main idea of ​​this question is to isolate the first node and compare it with the subsequent nodes of the linked list step by step, and perform head insertion, tail insertion, and middle insertion respectively. The difficulty of this question is not simple. For beginners For scholars.
And this question is an interview question that is often tested. The optimal solution can shorten the time complexity to O(nlogn) . I will optimize the solution of this question after learning merge and sort.

Complexity analysis:

  • Time complexity O(n^2) , n is the length of the linked list
  • Space complexity O(1)

Delete the last nth node oj of the linked list
Delete duplicate nodes in the linked list II
Delete nodes in the linked list
Rearrange the linked list and
sort the linked list

Guess you like

Origin blog.csdn.net/xiaoyubuhuiqiche/article/details/128225606