LC-初级算法-链表

初级算法

链表

LC删除链表中的节点

请编写一个函数,用于删除单链表中某个特定节点。在设计函数时需要注意,你无法访问链表的头节点head,只能直接访问要被删除的节点
题目数据保证需要删除的节点不是末尾节点

示例 1:

在这里插入图片描述

输入:head = [4,5,1,9], node = 5
输出:[4,1,9]
解释:指定链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9

示例 2:
在这里插入图片描述

输入:head = [4,5,1,9], node = 1
输出:[4,5,9]
解释:指定链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9

提示:

链表中节点的数目范围是 [2, 1000]
-1000 <= Node.val <= 1000
链表中每个节点的值都是 唯一 的
需要删除的节点 node 是 链表中的节点 ,且 不是末尾节点

题解:
杀掉A

在论坛里看到的比较有趣的一种解释:

  • 正常杀手需要找到A的把柄才可以杀掉A.
  • 可现在找打A本人后竟然没有可以获取A把柄的途径
  • A得知我们要杀他,心生一计,可助你完成任务
  • A说我又B的把柄,你杀了B,我改头换面,以B的身份活着
  • GC也会自动清理掉B的尸体,没人会知道

代码如下:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public void deleteNode(ListNode node) {
    //把B的值给A
        node.val = node.next.val;
    //把A的下一个指向B的下一个
    //这样B就没有被指向了
        node.next = node.next.next;
    }
}

还有通俗版:
正常我们删除链表里的一个元素,我们只需要让他的前一个结点指针指向要删除结点的下一个结点即可。
在这里插入图片描述
因为他不是双向链表,所以我们无法获取到要删除结点的前一个结点。
首先题要求是从要删除结点开始,所以我们只能把要删除结点的下一个结点的值赋给要删除的结点,然后删除要删除结点的下一个结点即可,而且题中说了,删除的结点是非末尾结点,那么这题就好办了。
在这里插入图片描述
代码同上。
在这里插入图片描述

LC删除链表的倒数第N个节点

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

示例 1:

在这里插入图片描述
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]

示例 2:

输入:head = [1], n = 1
输出:[]

示例 3:

输入:head = [1,2], n = 1
输出:[1]

提示:

链表中结点的数目为 sz
1 <= sz <= 30
0 <= Node.val <= 100
1 <= n <= sz

题解:
非递归解决

这题让删除链表的倒数第N个结点,首先最容易想到的就是求出链表的长度length,然后就可以找到要删除链表的前一个结点,让他的前一个结点指向要删除结点的下一个结点即可,这里就以示例为例画个图看一下.在这里插入图片描述
代码如下:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        int last = length(head) - n;
        ListNode res = head;
        //如果等于0说明要删除头节点
        if(last == 0)
        return res.next;
        //因为要删除第last位置上的元素
        //所以找到他的前一个元素
        for(int i = 0;i < last -1;i++){
            res = res.next;
        }
        //将要删除元素的前一个元素的下一个指向要删除元素的前一个元素的下一个的下一个
        res.next = res.next.next;
        //最后返回头结点即可
        return head;

    }
    //计算链表长度
    public int length(ListNode s){
        int len = 0;
        while(s != null){
            len++;
            s = s.next;
        }
        return len;
    }
}

在这里插入图片描述

双指针求解

上面是先计算链表的长度,其实不计算链表的长度也是可以的,我们可以使用俩个指针,一个指针fast先走n步,然后另一个指针slow从头结点开始,找到要删除结点的前一个结点,这样就可以完成结点的删除了。
代码如下:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        //定义一个快指针
        ListNode fast = head;
        //定义一个慢指针
        ListNode sole = head;
        //先让快指针走n步
        for(int i = 0;i < n;i++){
            fast = fast.next;
        }
        //如果走完n步后,快的指针的后一个是null那么就是删除头结点
        if(fast == null)
        return head.next;

        //只要快的指针不等于空就循环
        //当快的指针指向null的时候,sole就指向了要删除结点的前一个结点
        while(fast.next != null){
            fast = fast.next;
            sole = sole.next;
        }

        //然后退出循环将慢指针的next指向next的next删除了就可以
        sole.next = sole.next.next;
        return head;
    }
}

在这里插入图片描述

递归解决

我们知道获取链表的长度除了上面的那种方式,还可以递归的方式,比如:

//求链表的长度
private int length(ListNode head) {
    if (head == null)
        return 0;
    return length(head.next) + 1;
}

上面计算链表长度的递归其实可以把它看做是从后往前计算,当计算的长度是n的时候就表示遍历到了倒数第n个节点了,这里只要求出倒数第n+1个节点,问题就迎刃而解了,来看下代码

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        int pos = length(head,n);
        if(pos == n)
            return head.next;
        return head;
    }
    public int length(ListNode node,int n){
        if(node == null)
        return 0;

        int pos = length(node.next,n) + 1;

        if(pos == n + 1)
            node.next = node.next.next;
        return pos;
    }
}

在这里插入图片描述

LC反转链表

给你单链表的头结点head,请你反转链表,并返回反转后的链表。
示例 1:

在这里插入图片描述

输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]

示例 2:
在这里插入图片描述

输入:head = [1,2]
输出:[2,1]

示例 3:

输入:head = []
输出:[]

提示:

链表中节点的数目范围是 [0, 5000]
-5000 <= Node.val <= 5000

进阶:链表可以选用迭代或递归方式完成反转。你能否用两种方法解决这道题?

题解:
使用栈解决

链表的反转是老生常谈的一个问题了,同时也是面试中常考的一道题。最简单的一种方式就是使用栈,因为栈是先进后出的。实现原理就是把链表节点一个个入栈,当全部入栈之后再一个个出栈,出栈的时候把出栈的结点串成一个新的链表。
原理如下:
在这里插入图片描述
代码:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        Stack<ListNode> stack = new Stack<>();
        //把链表节点全部摘掉放到栈中
        while(head != null){
            stack.push(head);
            head = head.next;
        }
        if(stack.isEmpty())
            return null;
        ListNode node = stack.pop();
        ListNode dummy = node;
        //遍历所有结点
        while(!stack.isEmpty()){
            ListNode tempNode = stack.pop();
            node.next = tempNode;
            node = node.next;
        }
        //将最后一个结点反转前的头结点,一定要让他的next等于空
        //否则就会构成环
        node.next = null;
        return dummy;
    }
}

在这里插入图片描述

双链表求解

双链表求解是把原链表的结点一个个摘掉,每次摘掉的链表都让他成为新的链表的头结点,然后更新新链表。下面以链表1-2-3-4为例:
在这里插入图片描述

在这里插入图片描述
他每次访问的原链表节点都会成为新链表的头结点,最后再来看下代码:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {

        ListNode wnew = null;
        while(head != null){
            //先保存访问的结点下一个结点,保存起来
            //留着下一步访问的
            ListNode tmep = head.next;
            /**
            每次访问的原链表结点都会成为新链表的头结点,
            其实就是把新链表挂到访问的原链表结点的后面就行了
             */
            head.next = wnew;
            //更新新链表
            wnew = head;
            //重新赋值,继续访问
            head = tmep;
        }
        //返回新链表
        return wnew;
    }
}

在这里插入图片描述

递归解决

终止调剂就是链表为空,或者是链表没有尾结点的时候,直接返回

if(head == null || head.next == null)
	return head;

递归调用是要从当前结点的下一个结点开始递归。逻辑处理这块是要把当前结点挂到递归之后的链表的末尾,代码如下:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        //终止条件
        if(head == null || head.next == null)
            return head;
        //保存当前结点的下一个结点
        ListNode next = head.next;
        //从当前结点的下一个结点开始递归调用
        ListNode reverse = reverseList(next);
        /**
        reverse是反转之后的链表,业务函数reverseList
        表示的是对链表的反转,所以反转完之后next肯定是链表
        reverse的尾结点,然后我们再把当前结点
        head挂到next结点的后面就完成了链表的反转
         */
        next.next = head;
        //这里head相当与变成了尾结点,尾结点都是为空的
        //否则会成环
        head.next = null;

        return reverse;
    }
}

在这里插入图片描述

LC合并俩个有序链表

将俩个升序链表合并为一个新的升序链表并返回,新链表是通过拼接给定的俩给链表的所有结点组成的。
示例 1:
在这里插入图片描述

输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]

示例 2:

输入:l1 = [], l2 = []
输出:[]

示例 3:

输入:l1 = [], l2 = [0]
输出:[0]

提示:

两个链表的节点数目范围是 [0, 50]
-100 <= Node.val <= 100
l1 和 l2 均按 非递减顺序 排列

题解:
新建链表

因为链表是升序的,我们只需要遍历每个链表的头,比较一下那个小就把那个链表的头拿出来放到新的链表中,一直这样循环,直到有一个链表为空,然后我们再把另一个不为空的链表挂到新的链表中。
我们就以3-4-7-9和2-5-6两链表来画个图看一下怎么合并的。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码如下:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        if(list1 == null)
        return list2;
        if(list2 == null)
        return list1;
        //用res去接收新的链表
        ListNode res = new ListNode(0);
        //用no执行新的链表的头
        ListNode no = res;
        //如果list1和list2不为空就循环
        while(list1 != null && list2 != null){
            //如果list1的值大于list2的值
            //就把list2的值添加到新的链表里
            if(list1.val > list2.val){
                res.next = list2;
                //如果list2循环
                list2 = list2.next;
            }else{
                //如果不大于就把list1给新的链表
                res.next = list1;
                list1 = list1.next;
            }
            //让赋完值以后指向下一个继续循环
            res = res.next;
        }
        //当循环结束list1为空就把list2后面的所有都加到新的链表里
        //下面相反
        if(list1 == null){
            res.next = list2;
        }
        if(list2 == null){
            res.next = list1;
        }
        //最后返回新链表的第一个位置
        return no.next;
    }
}

LC回文链表

给你一个单链表的头结点head,请你判断该链表是否为回文链表,如果是,返回true,否则,返回false。

示例 1:
在这里插入图片描述

输入:head = [1,2,2,1]
输出:true

示例 2:
在这里插入图片描述

输入:head = [1,2]
输出:false

提示:

链表中节点数目在范围[1, 105] 内
0 <= Node.val <= 9

进阶:你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?

题解:
反转后半部分链表

这题是让判断链表是否回文链表,所谓的回文链表就是以链表的中间为中心点俩边对称。我们常见的有判断一个字符串是否是回文字符串,这个比较简单,可以使用俩个指针,一个最左边一个最右边,俩个指针同时往中间靠,判断所指的字符是否相等。

但这题判断的是链表,业务这里是单向链表,只能从前往后访问,不能从后往前访问,所以使用判断字符串的那种方式是行不通的。但我们可以通过找到链表的中间结点然后把链表后半部分反转,最后再用后半部分反转的链表和前半部分一个个比较即可。如图所示:
在这里插入图片描述
在这里插入图片描述

代码如下:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public boolean isPalindrome(ListNode head) {
        ListNode last = head;
        ListNode sole = head;
        //通过快慢指针找到中间
        while(last != null && last.next != null){
            last = last.next.next;
            sole = sole.next;
        }
        //如果last不为空那么就是偶行,
        //就让慢指针再往前
        if(last != null){
            sole = sole.next;
        }
        //反转后半的链表
        sole = reverse(sole);
        //让原来的快指针成为左半链表的头
        last = head;
    
        while(last != null && sole != null){
            
            if(last.val != sole.val)
            return false;
            last = last.next;
            sole = sole.next;
        }
        return true;
    }

    //反转链表
    public ListNode reverse(ListNode head){
        //创建新链表的头结点
        ListNode dev = null;
        while(head != null){
            //记录要保存结点的下一个值
            ListNode temp = head.next;
            //让要保存的结点指向头结点
            head.next = dev;
            //将头结点赋值给现在的头结点继续循环
            dev = head;
            //让head的成为原链表的下一个值
            head = temp;
        }
        //最后返回头结点
        return dev;
    }
}

在这里插入图片描述

使用栈解决

我们知道栈是先进后出的一种数据结构,这里还可以使用栈先把链表的结点全部存放到栈中,然后再一个个出栈,这样就相当于链表从后往前访问了,通过这种方式也能解决,代码如下:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public boolean isPalindrome(ListNode head) {
        if(head == null)
        return true;
        ListNode temp = head;
        Stack<Integer> stack = new Stack();

        int len = 0;
        while(temp != null){
            stack.push(temp.val);
            temp = temp.next;
            len++;
        }
        len >>= 1;

        for(int i = 0;i < len;i++){
            if(head.val != stack.pop())
                return false;
                head = head.next;
        }
        return true;
    }
}
递归方式解决

我们知道,如果对链表逆序打印可以这样写:

private void printListNode(ListNode head) {
    if (head == null)
        return;
    printListNode(head.next);
    System.out.println(head.val);
}

也就是说最先打印的是链表的尾结点,他是从后往前打印的,如果以后谁再给你说单向链表不能从后往前遍历,你就拿这代码怼他,看到这里是不是就有灵感了,我们来对上面代码进行改造一下即可:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    ListNode temp;
    public boolean isPalindrome(ListNode head) {
        temp = head;
        return isPalindrome2(head);
    }

    public boolean isPalindrome2(ListNode head){
        if(head == null)
            return true;;
        boolean is =  isPalindrome2(head.next)&& (head.val == temp.val);
        temp = temp.next;
        return is;
    }
}

在这里插入图片描述

LC环形链表

给你一个链表的头结点head,判断链表中是否有环。
如果链表中某个结点,可以通过连续跟踪next指针再次到达,则链表中存在环。为了表示给定链表中的环,评测系统内部使用整数pos,来表示链表尾到链表中的位置(索引从0开始),主要:pos不作为参数进行传递。仅仅是为了标识链表的实际情况。
如果链表中存在环,则返回true,否则,返回false。
示例 1:

在这里插入图片描述

输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。

示例 2:

在这里插入图片描述

输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。

示例 3:

在这里插入图片描述

输入:head = [1], pos = -1
输出:false
解释:链表中没有环。

提示:

链表中节点的数目范围是 [0, 104]
-105 <= Node.val <= 105
pos 为 -1 或者链表中的一个 有效索引 。

进阶:你能用 O(1)(即,常量)内存解决此问题吗?

题解:
快慢指针解决

判断链表是否有环应该是老生常谈的一个话题了,最简单的一种方式就是快慢指针,慢指针针每次走一步,快指针每次走两步,如果相遇就说明有环,如果有一个为空说明没有环。代码比较简单

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        if(head == null){
            return false;
        }
        ListNode fast = head;
        ListNode slow = head;

        while(fast != null && fast.next != null){
            slow = slow.next;

            fast = fast.next.next;

            if(fast == slow)
            return true;
        }
        return false;
    }
}

在这里插入图片描述

存放到集合中

这题还可以把结点寸放到集合set中,每次寸法的时候判断当前结点是否存在,如果存在,说明有环,直接返回true,比较容易理解:

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        Set<ListNode> res = new HashSet<>();
        while(head != null){
            if(res.contains(head))
                return true;

            res.add(head);
            head = head.next;
        }
        return false;
    }
}
逐个删除

一个链表从头结点开始一个个删除,所谓删除就是让他的next指针指向他自己。如果没有环,从头结点一个个删除,最后肯定会删除玩,如下图所示:
在这里插入图片描述
如果是环形的,那么有俩种情况,一种是o型,一种是6型。原理都是一样的,我们就看一下o型的。
在这里插入图片描述

在这里插入图片描述
如上图所示,如果删到最后,肯定会出现head = head.next;
代码如下:

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        if(head == null || head.next == null){
            return false;
        }

        ListNode next = head.next;

        if(head == head.next){
            return true;
        }
        head.next = head;

        
        return hasCycle(next);
    }
}

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值