链表简单题总结(待续)

02.02题:单链表中返回倒数第k个节点

方法1:常规法
思路:常规想法:先得到链表长度,再遍历得到倒数第k个数:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public int kthToLast(ListNode head, int k) {
    
    //***2个循环中的链表名不一样,因为若相同,则前一个遍历完之后head就指在最后一个节点了,在进行第二个循环遍历时就指向最后的NULL空指针了,所以需要把头节点另保存起来
    int len = 0;
    ListNode first = head;
    while(first != null){//得到链表长度
        len++;
        first = first.next;
    }
    int lindex = len - k + 1;//正序数该节点的位置
    int i=1;
    while(i < lindex){
        head = head.next;
        i++;
    }
    return head.val;
    }
 }
  • 效率:0ms,36.7MB

方法2:双指针法
思路:为了使得A指针走到最后一步(NULL)而B指针正好走到倒数第k个节点,则令A指针先走到第k+1个节点

int index= 1;
    ListNode first = head;
    ListNode second = head;
    while(first != null){
        if(index < k+1){
            first = first.next;
        }
        else{
            first = first.next;
            second = second.next;
        }
        index++;
    }
  • 效率:0ms,36MB

方法3:使用栈解决
这题要求的是返回后面的k个节点,我们只要把原链表的结点全部压栈,然后再把栈中最上面的k个节点出栈,出栈的结点重新串成一个新的链表即可,原理也比较简单,直接看下代码。

public int kthToLast(ListNode head, int k) {
        Stack<ListNode> stack = new Stack<>();
        //链表节点压栈
        while (head != null) {
            stack.push(head);
            head = head.next;
        }
        //在出栈串成新的链表
        ListNode firstNode = stack.pop();
        while (--k > 0) {
            ListNode temp = stack.pop();
            temp.next = firstNode;
            firstNode = temp;
        }
        return firstNode.val;
    }

*看一下运行结果,这个效率就差很多了

方法4:递归求解
之前讲过链表的逆序打印410,剑指 Offer-从尾到头打印链表,其中有这样一段代码

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

这段代码其实很简单,我们要理解他就要弄懂递归的原理,递归分为两个过程,递和归,看一下下面的图就知道了,先往下传递,当遇到终止条件的时候开始往回走。
前面也刚讲过递归的原理426,什么是递归,通过这篇文章,让你彻底搞懂递归,这题如果使用递归的话,我们先来看一下递归的模板

public ListNode getKthFromEnd(ListNode head, int k) {
    //终止条件
    if (head == null)
        return head;
    //递归调用
    ListNode node = getKthFromEnd(head.next, k);
    //逻辑处理
    ……
}

终止条件很明显就是当节点head为空的时候,就没法递归了,这里主要看的是逻辑处理部分,当递归往下传递到最底端的时候,就会触底反弹往回走,在往回走的过程中记录下走过的节点,当达到k的时候,说明到达的那个节点就是倒数第k个节点,直接返回即可,如果没有达到k,就返回空,搞懂了上面的过程,代码就很容易写了

//全局变量,记录递归往回走的时候访问的结点数量
int size;

public int kthToLast(ListNode head, int k) {
    //边界条件判断
    if (head == null)
        return 0;
    int val = kthToLast(head.next, k);
    ++size;
    //从后面数结点数小于k,返回空
    if (size < k) {
        return 0;
    } else if (size == k) {
        //从后面数访问结点等于k,直接返回传递的结点k即可
        return head.val;
    } else {
        //从后面数访问的结点大于k,说明我们已经找到了,
        //直接返回node即可
        return val;
    }
}

在这里插入图片描述

02.03:实现一种算法,删除单向链表中间的某个节点(即不是第一个或最后一个节点),假定你只能访问该节点。

题目警告:只能访问这个需要被删除的节点
所以先把该节点node的下一个节点的值赋给该节点,再把下一个节点删掉
在这里插入图片描述

public void deleteNode(ListNode node) {
       //题目警告:只能访问这个需要被删除的节点
       //思路:先把该节点的下一个节点的值赋给该节点,在把下一个值删掉
       node.val = node.next.val;
       node.next = node.next.next;
    }

21题:合并两个升序链表

/**
 * 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 l1, ListNode l2) {
        ListNode L = new ListNode(-1);
        ListNode temp = L;

        while(l1 != null && l2 != null){//当两个链表都非空时
            if(l1.val <= l2.val){
                temp.next = l1;
                l1 = l1.next;
            }
            else{
                temp.next = l2;
                l2 = l2.next;
            }
            temp = temp.next;
        }

        temp.next = l1 == null ? l2 : l1;
        return L.next;   
    }
}

很妙的思想
a. 头节点的定义:首先定义一个链表L,其头节点值为-1,同时再定义一个节点temp也指向L,故后续通过temp的移动将两个链表合并起来,最后返回整个链表时用L.next(因为L在头节点,但L值为-1).
b.链接的过程中不要忘记temp自身的移动
c. 最后当某一个链表非空时的三元判断法的写法需要注意下。
在这里插入图片描述

876题:找非空链表的中间节点

如果是奇数,找中间节点
如果是偶数,找第二个中间节点

class Solution {
    public ListNode middleNode(ListNode head) {
        //找中间节点(偶数则返回第二个中间节点)
        ListNode fast = head;
        ListNode slow = head;
        while(fast.next != null && fast.next.next != null){//这个循环找到的是(奇数中间节点和偶数的第一个中间节点)
            fast = fast.next.next;
            slow = slow.next;
        }
        if(fast.next != null){//如果是偶数的话,需要再往后找一个
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }
}

在这里插入图片描述

234题:判断回文链表

思路分析

如果是对数组操作,很显然采用双指针的思想,从前端和后端分别开始遍历
但是单链表存在两个问题:长度不容易知,只能从头开始遍历
因此考虑:将链表的后半段逆序,然后利用快慢指针对后半段和前半段分别进行遍历比较
解决问题:
1.找到中间节点(利用快慢指针,慢指针走1步,快指针走2步,当快指针走到最后一步时,慢指针刚好都中间节点)(奇数中间节点,偶数第一个中间节点)
2. 对后半段进行逆序(这是一个重点!!!)
3. 前后端比较
4. 对链表复原
时间O(n),空间O(1)

class Solution {
    public boolean isPalindrome(ListNode head){
        if(head == null || head.next == null){
            return true;
        }

        //找到前半段的尾节点并反转后半段链表
        ListNode firstHalfEnd = endOfFirstHalf(head);
        ListNode secondHalfStart = reverselist(firstHalfEnd.next);
        
        //判断是否回文,前后段比较,head指针(前半段)与backrev指针(后半段)移动比较
        boolean flag = true;
        ListNode p1 = head;
        ListNode p2 = secondHalfStart;
        while(flag && p2 != null){
            if(p1.val != p2.val){
                flag = false;
                //break;
            }
            p1 = p1.next;
            p2 = p2.next;
        }

        //还原链表并返回结果
        firstHalfEnd.next = reverselist(secondHalfStart);
        return flag;
    }

        //链表逆序函数
        private ListNode reverselist(ListNode head){
            ListNode cur = head;
            ListNode rev = null;//因为链表的最后一个节点都是空节点
            while(cur != null){
                ListNode tempnext = cur.next;//用来保存当前节点的下一个节点
                cur.next = rev;
                rev = cur;
                cur = tempnext;
            }
            return rev;
        }

        //找到前半部分的尾节点
        private ListNode endOfFirstHalf(ListNode head){
            ListNode fast = head;
            ListNode slow = head;
            while(fast.next != null && fast.next.next != null){
                fast = fast.next.next;
                slow = slow.next;
            }
            return slow;
        }
}

在这里插入图片描述

18. 删除某个节点

题目给定头指针和被删节点的值
分析做题过程中需要注意的点:
1.如果被删的是头节点
2.while循环在碰到被删节点时结束,而不是按常规的从头至尾遍历
3.当最后一个节点时,cur不为空,那cur.next不报错,相当于cur.next=null,因为链表的最后有一个空节点?

class Solution{
    public ListNode deleteNode(ListNode head,int val){
        ListNode pre = head;
        ListNode cur = pre.next;
        if(head == null) return null;
        if(head.val == val) return head.next;//被删节点是头节点
        while(cur != null && cur.val != val){//这个循环的好处:当碰到被删节点时就不再继续遍历了节省时间且好操作,而之前的方法是一直遍历完。
            pre = cur;
            cur = cur.next;
        }
        if(cur != null){//这个地方需要注意一下,即使cur是最后一个节点,其cur.next即为null节点也不报错,应该是因为链表的最后一个节点就是null节点,刚好把这个null赋给pre.next=null;
            pre.next = cur.next;
        }
        return head;
    }
}

61.旋转链表

题目描述
给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。
示例 1:
输入: 1->2->3->4->5->NULL, k = 2
输出: 4->5->1->2->3->NULL
示例 2:
输入: 0->1->2->NULL, k = 4
输出: 2->0->1->NULL

思路分析:
1.先找到旧的头和尾以及链表长度
2.再把链表整合成闭环链表
3.根据数字规律找到新的头和尾
当k<n时,新尾:n-k;新头:n-k-1
当k>=n时,新尾:n-k%n;新头:n-k%n+1
可以发现,头总在尾的后一个节点(以上按索引从1开始)
4.在尾巴处断链

class Solution {
    public ListNode rotateRight(ListNode head, int k) {
        if(head == null) return null;
        if(head.next == null) return head;
        //找到旧的头和尾
        ListNode last = head;
        int n = 1;//链表长度
        while(last.next != null){
            n++;
            last = last.next;
        }
        last.next = head;//闭环
        ListNode tailnew = head;
        for(int i = 1;i < n-k%n;i++){//找到新尾节点
            tailnew = tailnew.next;
        }
        ListNode headnew = tailnew.next;//找到新头节点
        tailnew.next = null;
        return headnew;
    }
}

52.两个链表的首个公共节点

示例 1:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Reference of the node with value = 8
在这里插入图片描述

思路分析:浪漫的双指针相遇
我变成了你,走你走过的路;你变成了我,走我走过的路;然后我们就相遇了。<你的名字>
A长度为L1+C;B长度为L2+C;当A和B两者分别走L1+L2+C,两者顶相遇
当PA指针走到A的最后一个节点时,再重新定位到headB;
当PB指针走到B的最后一个节点时,再重新定位到headA;
这样,当PA=PB时,定为第一个公共点。

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode PA = headA;
        ListNode PB = headB;

        while(PA != PB){
            PA = PA==null? headB : PA.next;
            PB = PB==null? headA : PB.next;
        }
        return PA;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值