小白都能看懂的力扣算法详解——链表(一)

!!本篇所选题目及解题思路均来自代码随想录 (programmercarl.com)

一 203.移除链表元素

题目要求:给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回新的头节点。

203. 移除链表元素 - 力扣(LeetCode)

我们的目标是要寻找val等于目标值的节点,那么我们就要遍历这个链表,找到该节点,之后让该节点的上一个节点指向它的下一个节点,即可实现“删除”。但是我们知道,单向链表只能指向它的下一个节点,那么我们该如何找到它的上一个节点呢?这里我能可以定义一个指针cur,让cur指向该元素的前一个节点,那么cur.next = cur.next.next 即实现了该节点的上一个节点指向它的下一个节点。因此我们可以得出cur.next = head;也就是说cur是头结点的前一个节点。解决了“删除”的问题,接下来思考如何实现寻找这个节点。也就是说,满足val等于目标值这个条件,翻译过来就是cur.next.val == targrt;(别忘了cur是当前节点的前一个节点,cur.next才是该节点)。也就是满足条件时执行cur.next = cur.next.next 操作,不满足条件时cur就移向下一个节点,继续遍历。最后一个问题,如何判断遍历何时结束?不难判断出,当cur指向链表的最后一个节点时,循环就该结束了(此时这个节点的val已经在cur指向它前一个节点时就对比过了),而cur.next为null也无法得到cur.next.val了。也就是说,我们的循环判断条件就是cur.next != null

整体思路结束,接下来来考虑一些特殊点,比如各种临界值。首先是当target==head.val的情况,满足cur.next.val == targrt条件;之后是target等于最后一个节点的值的情况,我们已经谈论过了;第三是链表长度为0的情况,此时head为null,即满足cur.next为null,退出循环;最后是有连续节点满足val等于target的情况,这时我们就会发现,如果有连续值满足条件,那么第二个节点就会被跳过对比,如何解决这个问题呢?我们可以在进行完cur.next = cur.next.next操作后,先不急着将cur向后移动(cur = cur.next),而是让它在原位置不动,这样下一轮比较cur.next.val时,cur.next就是第二个节点啦。

最后瞅一眼返回值,是返回头节点吗?如果直接返回head,那么如果head.val刚好等于target,已经被我们删除了,该怎么办?这时候就要请出我们的老朋友——虚拟头节点了(不熟也没关系,多刷几道链表题目之后就会发现,基本上有链表的地方就有它)。在最开始,设置一个虚拟头节点指向head,即p.next = head;无论头结点被删掉多少个,p.next都会指向它的下一项元素,也就是p始终都是“首个节点”,最后直接返回p.next即可。

class Solution {
    public ListNode removeElements(ListNode head, int val) {
        ListNode p = new ListNode();
        p.next = head;
        ListNode cur = p;
        while(cur.next != null) {
            if(cur.next.val == val) {
                cur.next = cur.next.next;
            } else {
                cur = cur.next;
            }
        }
        return p.next;
    }
   
}

 代码随想录上提供了另一种不适用虚拟头节点的思路,这里直接附上代码,不再具体讲解。其实思路与上面几乎一致,只是多了一个对头节点是否需要删除的判断,就像上面说过的,如果head.val刚好等于target,已经被我们删除了,该如何找到链表的头部?

class Solution {
    public ListNode removeElements(ListNode head, int val) {
        while(head != null && head.val ==val) {
            head = head.next;
        }
        ListNode cru = head;
        while(cru != null && cru.next != null){
            if(cru.next.val == val) {
                cru.next = cru.next.next;
            } else{
                cru = cru.next;
            }
        }
        return head;
    }
   
}

 关于此方法的具体讲解,以及该题目的视频讲解,其他语言版本代码,大家可以到代码随想录中自行查看。代码随想录 (programmercarl.com)icon-default.png?t=N7T8https://www.programmercarl.com/0203.%E7%A7%BB%E9%99%A4%E9%93%BE%E8%A1%A8%E5%85%83%E7%B4%A0.html#%E7%AE%97%E6%B3%95%E5%85%AC%E5%BC%80%E8%AF%BE

二 206.反转链表

题目描述:给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

206. 反转链表 - 力扣(LeetCode)

 这道题乍一看很难(反正我是毫无思路),看到有一种暴力解法是将链表中的元素存储在数组中,反转数组,再放回到链表里,感觉可行但是我没具体尝试(也太暴力了一点......).听完卡尔哥的讲解之后我的感受只有两个字:秒啊。

回归正题,翻转链表我们很自然就能想到让下一个元素指向上一个元素,问题在于,在单向链表中,如何获得上一个元素的位置呢?这里又要用到虚拟头节点了。我们定义一个指针pre指向虚拟头节点,也就是head的前一个元素,再定义一个指针cur指向head,让cur.next指向pre,即可完成下一个元素指向上一个元素,之后我们只要向后移动pre和cur,循环上面的操作即可。是不是很完美?错!这种做法存在一个很明显的错误,当cur.next指向pre之后,head后面的节点就失去了指向它的头节点,如图,那么cur如何如我们所愿向后移动呢?

这时候,我们就需要清楚我们的第三个指针temp出场了。我们在执行cur.next = pre之前,先令p指向cur.next,如此一来,就可保留住cur下一个节点的位置,我们就能随意改变cur.next的指向了。当我们完成cur.next = pre的操作后,只要让cur = temp,就可以让cur指向它原先的下一个节点了。

大体思路完成,接下来思考实现细节。

1.什么时候终止循环?

当循环进行到这一步时,整个反转操作就完成了,再往下走(cur = null)也就没有了意义,所以可以得到,临界条件为cur!=null

2.返回值是什么?

搞不明白就画图,在最后一步操作执行完时,指针情况是这样的,此时cur!=null条件不满足,结束循环。原先的最后一个节点就是现在的头节点,也就是pre指针所指向的节点,因此我们返回pre。

3.链表为空或只有一个节点时,是否需要特殊讨论?

链表为空时,head也为空,此时自然满足cur=null的条件,不进入循环,返回pre,为空。符合。

链表只有头节点时,cur != null,完成第一轮交换,之后pre指向了head,cur执行null,退出循环,返回pre(head),满足。

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode cur = new ListNode();
        ListNode pre = new ListNode();
        cur = head;
        pre= null; 
        while(cur != null ) {
            ListNode temp = new ListNode();
            temp = cur.next;
            cur.next = pre;
            pre = cur;
            cur = temp;
        }
        return pre;
    }
}

关于该题目的视频讲解,其他语言版本代码,大家可以到代码随想录中自行查看。

代码随想录 (programmercarl.com)icon-default.png?t=N7T8https://www.programmercarl.com/0206.%E7%BF%BB%E8%BD%AC%E9%93%BE%E8%A1%A8.html

三 707.设计链表

题目描述:

你可以选择使用单链表或者双链表,设计并实现自己的链表。

单链表中的节点应该具备两个属性:val 和 next 。val 是当前节点的值,next 是指向下一个节点的指针/引用。

如果是双向链表,则还需要属性 prev 以指示链表中的上一个节点。假设链表中的所有节点下标从 0 开始。

实现 MyLinkedList 类:

  • MyLinkedList() 初始化 MyLinkedList 对象。
  • int get(int index) 获取链表中下标为 index 的节点的值。如果下标无效,则返回 -1 。
  • void addAtHead(int val) 将一个值为 val 的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。
  • void addAtTail(int val) 将一个值为 val 的节点追加到链表中作为链表的最后一个元素。
  • void addAtIndex(int index, int val) 将一个值为 val 的节点插入到链表中下标为 index 的节点之前。如果 index 等于链表的长度,那么该节点会被追加到链表的末尾。如果 index 比长度更大,该节点将 不会插入 到链表中。
  • void deleteAtIndex(int index) 如果下标有效,则删除链表中下标为 index 的节点。

707. 设计链表 - 力扣(LeetCode)

 这道题非常非常非常麻烦,但是其实难度不是很高,建议大家可以做完其他链表题目后再翻回来做这道题,可能更容易一些。这里直接给出代码及相应注释,不再具体讲解,关于该题目的视频讲解以及其他语言版本代码,大家可以到代码随想录中自行查看。代码随想录 (programmercarl.com)icon-default.png?t=N7T8https://www.programmercarl.com/0707.%E8%AE%BE%E8%AE%A1%E9%93%BE%E8%A1%A8.html#%E7%AE%97%E6%B3%95%E5%85%AC%E5%BC%80%E8%AF%BE

 // 设计链表结构
 class ListNode {
    int val;
    ListNode next;
    ListNode(){}
    ListNode(int val) {
        this.val=val;
    }
}
// 初始化
class MyLinkedList {
    //size存储链表元素的个数
    int size;
    //虚拟头结点
    ListNode head;

    //初始化链表
    public MyLinkedList() {
        size = 0;
        head = new ListNode(0);
    }

    //获取第index个节点的数值,注意index是从0开始的,第0个节点就是头结点
    public int get(int index) {
        //如果index非法,返回-1
        if (index < 0 || index >= size) {
            return -1;
        }
        ListNode currentNode = head;
        //包含一个虚拟头节点,所以查找第 index+1 个节点
        for (int i = 0; i <= index; i++) {
            currentNode = currentNode.next;
        }
        return currentNode.val;
    }

    //在链表最前面插入一个节点,等价于在第0个元素前添加
    public void addAtHead(int val) {
        addAtIndex(0, val);
    }

    //在链表的最后插入一个节点,等价于在(末尾+1)个元素前添加
    public void addAtTail(int val) {
        addAtIndex(size, val);
    }

    // 在第 index 个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
    // 如果 index 等于链表的长度,则说明是新插入的节点为链表的尾结点
    // 如果 index 大于链表的长度,则返回空
    public void addAtIndex(int index, int val) {
        if (index > size) {
            return;
        }
        if (index < 0) {
            index = 0;
        }
        size++;
        //找到要插入节点的前驱
        ListNode pred = head;
        for (int i = 0; i < index; i++) {
            pred = pred.next;
        }
        ListNode toAdd = new ListNode(val);
        toAdd.next = pred.next;
        pred.next = toAdd;
    }

    //删除第index个节点
    public void deleteAtIndex(int index) {
        if (index < 0 || index >= size) {
            return;
        }
        size--;
        if (index == 0) {
            head = head.next;
	    return;
        }
        ListNode pred = head;
        for (int i = 0; i < index ; i++) {
            pred = pred.next;
        }
        pred.next = pred.next.next;
    }
}

 

  • 27
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值