剑指offer 面试题18. 删除链表的节点 Java实现 Leecode解法 和剑指offer O(1)复杂度解法(改进版) 原理及代码实现

23 篇文章 0 订阅
5 篇文章 0 订阅

一、先来看Leecode上的题目要求
面试题18. 删除链表的节点
给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。
返回删除后的链表的头节点。

注意:此题对比原题有改动
示例 1:输入: head = [4,5,1,9], val = 5 输出: [4,1,9] 解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.
示例 2:输入: head = [4,5,1,9], val = 1 输出: [4,5,9] 解释: 给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.
说明:题目保证链表中节点的值互不相同 若使用 C 或 C++ 语言,你不需要 free 或 delete 被删除的节点。

其中ListNode的定义如下:

 Definition for singly-linked list.
	  public class ListNode {
	      int val;
	      ListNode next;
	      ListNode(int x) { val = x; }

解题思路:
在单向链表中删除一个节点,常规的做法无疑是从链表的头节点开始,顺序遍历查找要删除的节点,并在链表中删除该节点。
这种思路由于需要顺序查找,时间复杂度自然就是O(n)了。
之所以需要从头开始查找,是因为我们需要得到将被删除的节点的前一个节点。
在单向链表中,节点中没有指向前一个节点的指针,所以只好从链表的头节点开始顺序查找

基于上面的解题思路,下面是两种Java实现代码:

双百解法1: 时间复杂度O(n) 空间复杂度O(1).


    public ListNode deleteNode1(ListNode head, int val) {
        if(head==null) return head;
        if(head.val==val) return head.next;
        ListNode cur = head;
        while(cur.next!=null&&cur.next.val!=val){
            cur = cur.next;
        }
        
        if(cur.next!=null) cur.next=cur.next.next;
        //而如果是cur.next==null导致的跳出循环,则说明链表中查询完毕也没有找到对应节点,不对链表进行修改。
        return head;
    }

双百解法2:迭代
时间复杂度O(n) 空间复杂度O(1) 。
不是很推荐推荐,理解起来不是很方便,且一旦输入的val值在链表中不存在,函数会报错。leecode中默认val一定在链表中,所以函数可以成功提交。

public ListNode deleteNode2(ListNode head, int val) {
	        if(head.val==val)
	            return head.next;
	        
	        head.next = deleteNode2(head.next,val);
	        return head;
	    }

二、剑指offer书中要求O(1)时间复杂度的原理和java实现
在剑指offer书中的题目原意和上面的有所不同: 要求在O(1)时间内删除链表节点
即:给定单向链表的头指针和一个节点指针,定义一个函数在O(1)时间内删除该节点

思路:此时,在提供了需要被删除的节点指针,而非节点指针值的情况下,是否还一定需要得到被删除的节点的前一个节点呢?
答案是否定的。我们可以很方便地得到要删除的节点的下一个节点。
如果我们把下个节点的内容复制到需要删除的节点上覆盖原有的内容,再把下一个节点删除,那就相当于把当前需要删除的节点删除了。

1.上述思路还有一个问题:如果要删除的节点位于链表的尾部,那么它就没有下一个节点,怎么办?
那么我们就需要从链表的头节点开始,顺序遍历得到该节点的前序节点,并完成删除操作。

2.最后需要注意的是,如果链表中只有一个节点,而我们又要删除链表的头节点(也是尾节点),
那么,此时我们在删除节点之后,还需要把链表的头节点设置为 null.

接下来我们分析这种思路的时间复杂度。对于n-1个非尾节点而言,我们可以在O(1)时间内把下一个节点的内存复制覆盖要删除的节点,并删除下一个节点;
对于尾节点而言,由于仍然需要顺序查找,时间复杂度是O(n)。
因此,总的平均时间复杂度是 [(n-1)O(1)+O(n)]/n,结果还是O(1),符合面试官的要求。

有了这些思路,我们就可以动手写代码了,下面是这种思路的参考代码:

   public ListNode deleteNode3(ListNode head, ListNode val){
		   if(head==null) return head;//链表为空,直接返回
		   
		   if(val.next!=null) {//需要删除的节点后面还有节点时,此时链表中最少有两个节点
			   //注意,直接val = val.next 头结点对应的链表不会有任何变化,只是val对应的节点发生了变化
			   //必须先把下个节点的内容复制到需要删除的节点上覆盖原有的内容,再把下一个节点删除,
			   val.val= val.next.val;
			   val.next = val.next.next;//如果val本身就不在链表内,则该语句对链表没有影响,链表不变。
			   return head;
		   }
		 //需要删除的节点后面没有节点时,即要删除的节点位于尾节点
		 //这时有两个情况:1.链表中只有这一个节点,此时删除后链表为null。2.需要删除的节点前面还有节点。
		  
	       if(head==val) {  //1.链表中只有这一个节点,且为被删除节点时,此时删除后链表为null。
	    	   return null;
	       } 
	       
	       //2.需要删除的节点前面还有节点。或者只有一个节点但是该节点不是需要被删除的节点
		   ListNode cur =head;
		   while(cur.next!=null&&cur.next!=val) { //这里cur.next.val!=val.val可能会有问题
			   cur=cur.next;
		   }
		   if(cur.next!=null) cur.next=cur.next.next;
		   //而如果是cur.next==null导致的跳出循环,则说明链表中查询完毕也没有找到对应节点,不对链表进行修改。
		   return head;
	   }

具体的细节实现见代码中的注释。

上面的代码中可以兼顾查询和删除功能,且全部遍历仅在删除节点位于尾部时才会进行,
总的平均时间复杂度仍为 [(n-1)O(1)+O(n)]/n,结果还是O(1),符合面试官的要求
且上述代码还支持链表中节点值相同的情况

下面是测试代码:使用调试模式可以很方便的观察到链表的变化

 public static void main(String[] args) {
			ListNode head = new ListNode(1);
			ListNode s2 = new ListNode(2);
			ListNode s3 = new ListNode(3);
			ListNode s4 = new ListNode(4);
			ListNode s5 = new ListNode(5);
			head.next = s2;
			s2.next =s3;
			s3.next =s4;
			s4.next = s5;
			Solution18 so = new Solution18();
			ListNode res1 = so.deleteNode3(head, s5);
			ListNode head2 = new ListNode(1);
			ListNode res2 = so.deleteNode3(head2, head2);
		}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值