剑指Offer_#18_删除链表的节点

剑指Offer_#18_删除链表的节点

Contents

题目

给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。
返回删除后的链表的头节点。
注意:此题对比原题有改动

示例 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 被删除的节点

思路分析

Leetcode的此题和书本上原题不符,原题中输入参数的待删除节点不是int类型,而是指针类型,所以可以通过用待删除节点的后一节点覆盖待删除节点,来达到O(1)的时间复杂度。Leetcode上的题目,只能通过从前往后遍历来实现,复杂度是O(n)

需要考虑的几个边界条件

  1. 输入为null,也应该返回null
  2. 删除的是第一个节点,返回head.next
  3. 删除的是最后一个节点

在提交代码之前,应该自己编写以上的几个测试用例,先自己测试通过,再提交。

《剑指Offer》原题思路

  • 原题的意思是,删除节点val,这里传入的参数val是ListNode的指针,或者说是ListNode对象。
    • 区别在于,这样就可以直接访问到待删除节点的后一节点了,可以用下面的方法。
  • 设要删除节点为j,前后节点为i,k,算法思路是:
    1.修改节点jval为后一节点kval
    2.然后将j节点的next指向k节点的next(也就是跳过k节点)。
    这样做的效果与直接删除j节点一样。
  • 需要考虑的特殊条件
    如果待删除的节点就是最后一个节点,那么已经没有下一节点了,上面的方法行不通,所以只能遍历。

解答1:从前往后遍历节点

编写代码需要注意的一点
为了删除当前节点cur,需修改的时上一节点prenext指针。所以必须在访问到上一个节点pre时就进行判断,然后修改pre节点的指针。
这导致了第一个节点head在循环当中无法被访问和判断,解决这一问题有两个方法:

  1. head节点之前增加一个伪节点dummyHead,再返回结果时返回dummyHead.next,这样可在循环中处理head节点。
    • dummyHead之所以被叫做伪节点,是因为这个节点的作用只是提供了一个指向head的指针,而它的val属性没有任何意义,最后返回时也不需要这个节点
  2. 进入循环代码之前,单独对head节点进行判断和处理。
    代码中两种方法都写了,可以看出,两种写法代码量是一样的,所以用哪种方式都可以。
class Solution {
    public ListNode deleteNode(ListNode head, int val) {
        //增加一个伪节点,则循环语句可以覆盖head节点,不需为head节点单独进行判断
        //此时,返回值为dummyHead.next
        ListNode dummyHead = new ListNode(0);
        dummyHead.next = head;
        //如果没有伪节点,那么需要单独对head进行判断
        //此时,返回值为head
        // if(head == null) return null;     
        // if(head.val == val) return head.next;
        ListNode cur = dummyHead;
        while(cur != null){//遍历查找数值是val的节点
            if(cur.next != null && cur.next.val == val){
                cur.next = cur.next.next;//跳过要删除的节点
                break;
            }
            cur = cur.next;//向后遍历
        }
        //return head;
        return dummyHead.next;
    }
}

复杂度分析

时间复杂度O(n)
空间复杂度O(1)

解答2:剑指Offer原题解法

按照书上的原题意写了如下代码,只可在本地IDE测试。

public class Solution {
    public static ListNode deleteNode(ListNode head, ListNode val){
        if(head == null || val == null) return null;
        if(val.next != null){//待删除节点不是尾节点
            val.val = val.next.val;//用下一节点的值覆盖待删除节点
            val.next = val.next.next;//跳过下一节点
        }else{//待删除节点是尾节点,则从前向后遍历
            if(head == val) return null;//链表只有一个节点,则返回空链表
            ListNode cur = head;
            while(cur != null){//ERROR:这里不可以用cur.next,可能出现NullPointerException
                if(cur.next != null && cur.next == val){//如果cur.next是待删除节点
                    cur.next = cur.next.next;//cur连接到cur.next.nex,相当于跳过了待删除节点
                }
                cur = cur.next;//向后遍历
            }
        }
        return head;
    }
}

复杂度分析

时间复杂度
输入不是尾节点时,时间复杂度为O(1)
输入是尾节点时,时间复杂度为O(n),
平均时间复杂度:(1*O(n)+(n-1)*O(1))/n=O(1),依然是O(1),比解法1好
空间复杂度
O(1)

另附测试代码

测试代码包含ListNode类和Main类。
ListNode类中实现了两个工具方法:

  • LinkedList:输入数组,生成对应的链表
  • PrintLinkedList:输入链表,打印出对应的数组
import java.util.ArrayList;

public class ListNode {
    int val;
    ListNode next;
    ListNode(int x) { val = x; }

    public static ListNode LinkedList(int[] numArray){//输入数组,构造一个链表
        ListNode dummyHead = new ListNode(0);
        ListNode pre = dummyHead;
        for(int i = 0;i <= numArray.length - 1;i++){
            ListNode cur = new ListNode(numArray[i]);//构造当前节点
            pre.next = cur;
            pre = cur;
        }
        return dummyHead.next;
    }

    public static void PrintLinkedList(ListNode head){//输入链表头节点,打印对应的数组
        if(head == null) System.out.println("[]");
        else{
            ListNode cur = head;
            ArrayList<Integer> res = new ArrayList<>();
            while(cur != null){
                res.add(cur.val);
                cur = cur.next;
            }
            System.out.println(res);
        }
    }
}

Main类用于测试

public class Main {
    public static void main(String[] args){
        ListNode head = ListNode.LinkedList(new int[]{1,2,3,4,5});
        //这样写是不对的,因为这个val和head不在一个对象当中,所以在删除节点时,无法进行比较
        //ListNode val = ListNode.LinkedList(new int[]{5});
        //正确写法:val应该是从head对象获取到的
        ListNode val = head.next;
        //打印
        System.out.println("输入:head,val");
        ListNode.PrintLinkedList(head);
        ListNode.PrintLinkedList(val);

        ListNode deleted = Solution.deleteNode(head,val);
        //打印
        System.out.println("输出:deleted");
        ListNode.PrintLinkedList(deleted);
    }
}

使用方法
在IDE中建立三个类,名为Solution,Main,ListNode。将上面的代码粘贴到对应类的java文件中。
自行修改main方法中的输入值head,val,然后运行Main类当中的main方法即可进行测试。

原文地址:https://www.cnblogs.com/Howfars/p/13183047.html