zoukankan      html  css  js  c++  java
  • 剑指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方法即可进行测试。

  • 相关阅读:
    大型项目前端架构浅谈
    图标使用新姿势- react 按需引用 svg 的实现
    为什么json 不能使用 int64类型
    SSL证书对SEO网站优有什么影响?使用HTTPS的SEO优势
    web安全测试必须注意的五个方面
    在小程序中实现 Mixins 方案
    网站web前端常见的优化措施
    前端 HTML空格的六种方式
    如何正确选型,React Native 还是 Native?
    CSS3 渐变(Gradients)
  • 原文地址:https://www.cnblogs.com/Howfars/p/13183047.html
Copyright © 2011-2022 走看看