要不要一起组队刷题呀leetcode19题

导语

最近跟几个同学一起建了一个刷题的群,相互学习学习,就写写题解,推广推广吧。(虽然我知道并没有啥粉丝)

这个算一篇发起刷题的文章吧,文章流程是写了leetcode的一个题解,为后面建群学习抛砖引玉,然后建了一个leetcode刷题的群,咱们可以相互监督,相互解答,相互提高自己的算法水平,开阔思路。
毕竟程序就是算法加数据结构,而刷题能很大程度的提高咱们算法和数据结构的熟练度和让自己变得更聪明。你想想你有一个苹果,我有一个橙子,我们交互一下,还是每人只有一个水果。
但是你有一个方法,我有一个方法,我们交换一下,每人就有了两个方法,互利双赢,稳赚不赔啊。群内还有微软的acm大神坐镇,还在等什么赶紧加入一起学习吧~

不想看刷题的可以先溜了~ 想一起刷题的可以看下本文下方的二维码的群,加入一下。

首先,咱们来看题,这是leetcode的20题。

给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。

示例:
给定一个链表: 1->2->3->4->5, 和 n = 2.

当删除了倒数第二个节点后,链表变为 1->2->3->5.
说明:
给定的 n 保证是有效的。

进阶:
你能尝试使用一趟扫描实现吗?

题目给出的单链表定义:

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

题解:
这是一道跟链表操作相关的题目,如果是数组的话,那要删除倒数的某个元素是很容易的。
链表因为事先不知道链表的长度,所以删除倒数的节点就不好操作。

愚蠢方法:
这个题我咋一看,是想先遍历一遍知道长度了,
在去倒数的第n个节点的前一个元素的指针指向n的后一个元素就完事了。不过遍历一次知道长度,在删除又遍历了一次就是两次了。
题目进阶说,你能用一趟扫描实现吗?想一想那把遍历的元素保存起来,在根据n和元素多少的关系不就可以值遍历一次了, 于是就有了下面这个愚蠢的写法

public static ListNode removeLinkedListNode(ListNode head, int n) {
    
    
        ListNode currentNode = head;
        if (head.next == null && n == 1) {
    
    
            return null;
        }
        List<ListNode> listNodes = new ArrayList<>();
        while (currentNode.next != null) {
    
    
            listNodes.add(currentNode);
            currentNode = currentNode.next;
        }
        listNodes.add(currentNode);
        int size = listNodes.size();
        if (listNodes.size() == n) {
    
    
            // 如果是第一个数被移除  直接返回第一个节点的next
            return head.next;
        } else if (1 == n) {
    
    
            // 如果是最后一个元素被移除 将倒数第二个元素的指针置为null
            listNodes.get(size - 2).next = null;
            return head;
        } else {
    
    
            // 中间节点被移除 就将n - 1 指向n + 1
            ListNode subNode = listNodes.get(size - n - 1);
            ListNode plusNode = listNodes.get(size - n + 1);
            subNode.next = plusNode;
            return head;
        }
    }

用了一个list来保存每个节点,然后判断下删除的是头结点还是尾结点,还是中间节点被删除。
时间复杂度是o(m), m是链表的长度
空间复杂度是o(m), 因为每个元素都存了一次。
题目是可以做出来就是比较蠢,后来在思考也没有想到特别好的方式,最后看了官方的题解恍然大悟,茅塞顿开,醍醐灌顶!

聪明的方式:
学到的知识点1:为了数据处理的统一性,可以虚拟一些节点出来,方便数据处理,就像咱们高中做几何题目画辅助线一样,帮助咱们理解
我们上一个愚蠢的方式,对头结点,中间节点,尾结点分别采用了不同的处理方式,那我们是不是可以虚拟出一个头结点,放在原来的节点的最前面?这样处理后面的数据是不是就可以统一处理了?
只需要最返回的时候返回虚拟节点指向的节点即可。
学到的知识点2:快慢指针(可以百度一下),是链表中处理步长的常用方式(这里如果有算法大佬看到了,轻喷,我就是这么菜,题目刷得太少,算法数据结构一直是我的短处,根本就没用过这东西)
我们先想想要删除的是不是倒数的第n个数?那么这其中固定不变的就是从最后一个节点到n的距离对不对?我们使用两个指针,一直保持n的距离,然后一起从头结点走到尾结点。因为两个指针一直距离是n,
那么当快的那个指针走到了尾结点,是不是慢指针就走到了n前面的位置?想象一下,你拿着一根尺子,尺子长固定不变,当尺子的前端触碰到物体前边了,那么尺子的尾端是不是就是物体上距离物体前边,尺子长度的地方?
如果还没明白就可以看看下面的图(这个图是我从官方题解里面复制出来的,如果侵权了麻烦大佬找我说下,我就换掉)

所以此时我们删除掉,慢指针所在的这个节点是不是就可以了?看下代码

public static ListNode removeLinkedListNode(ListNode head, int n) {
    
    
       ListNode virtualNode = new ListNode(0);
       virtualNode.next = head;
       ListNode fastPoint = virtualNode;
       ListNode slowPoint = virtualNode;
       int count = 0;
       while (fastPoint != null) {
    
    
           // 先将快指针移动n个位置 因为是有虚拟节点所以要多移动一次是<=n
           if (count <= n) {
    
    
               fastPoint = fastPoint.next;
               ++count;
           } else {
    
    
               fastPoint = fastPoint.next;
               slowPoint = slowPoint.next;
           }

       }
       // 将慢指针的指向后一个节点
       slowPoint.next = slowPoint.next.next;
       return virtualNode.next;
   }

时间复杂度依然是o(m),空间复杂度是常数级别的复杂度是o(1),就两个指针,一个虚拟节点。
执行出来直接打败100%的java提交者,阔以呀,小伙纸。

平时空闲的时候就可以刷刷题,相互交流交流,提供自己的水平,岂不是美滋滋?下方是群的二维码,欢迎加入哦~
leetcode讨论群(如果下面链接失效了,欢迎在我的公众号发消息,我再把你拉进去):

猜你喜欢

转载自blog.csdn.net/sc9018181134/article/details/105186449