【数据结构】链表必刷题(1)

本文详细介绍了链表的各种操作,包括反转链表、删除元素、找到中间节点、找到倒数第k个节点、合并两个有序链表、判断回文链表、从尾到头打印链表、找到两个链表的交点以及删除重复节点。每种操作都提供了至少两种不同的解决方案,包括迭代和递归等方法,涵盖了空间复杂度O(1)和时间复杂度O(n)的高效实现。
摘要由CSDN通过智能技术生成

反转链表:JZ24

题目链接 OJ链接

题目描述:

给定一个单链表的头结点pHead(该头节点是有值的,比如在下图,它的val是1),长度为n,反转该链表后,返回新链表的表头。
数据范围: 0 ≤ n ≤ 1000
要求:空间复杂度 O(1) ,时间复杂度 O(n) 。

  1. 思路1:
    用一个数组存储结点数据,然后遍历数组,创建一个头节点,用尾插的思路连接成表,但是这种方法的空间复杂度为O(N)
    public ListNode reverseList(ListNode head) {
        ArrayList<Integer> list = new ArrayList<>();
        ListNode cur = head;
        while (head != null) {
            list.add(head.val);
            head = head.next;
        }
        ListNode newHead =null;
        for (int x : list) {
            cur = new ListNode(x);
            cur.next = newHead;
            newHead = cur;
        }
        return newHead;
    }

或者直接连接

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode ans = null;
        for (ListNode x = head; x != null; x = x.next) {
            ans = new ListNode(x.val,ans);
        }
        return ans;
    }
}
  1. 思路2:
    迭代:遍历链表时,用pre和next来维护前后指向,先用一个变量tmp存储next中的结点,然后改变前后指向,直到next为空。
class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode pre = null;
        ListNode next = head;
        while(next != null) {
            ListNode tmp = next.next;
            next.next = pre;
            pre = next;
            next = tmp;
        }
        return pre;
    }
}

移除链表元素

OJ链接

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

  1. 思路1
    先保证头结点不是我们要删除的数据,然后删除元素,删除元素只需要把删除元素的前一个next指向删除元素的next。
/**
 * 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 removeElements(ListNode head, int val) {
        //保证头节点有效
        while (head != null && head.val == val) {
            head = head.next;
        }
        if (head == null) {
            return null;
        }
        //删除val节点
        ListNode pre = head;
        ListNode target = pre.next;
        while (pre != null && target != null) {
            if (target.val == val) {
                pre.next = target.next;
            } else {
                pre = pre.next;
            }
            target = target.next; 
        }
        return head;
    }
}
  1. 思路2

按照思路1的思路来优化,我们可以创建一个虚拟头节点指向头节点,现在我们就不需要判断头节点的有效,按照规则删除即可。

class Solution {
    public ListNode removeElements(ListNode head, int val) {
        if (head == null) {
            return null;
        }
        //创建一个无效头节点
        ListNode newHead = new ListNode(-1);
        newHead.next = head;
        ListNode pre = newHead;
        while (pre.next != null) {
            if (pre.next.val == val) {
                pre.next = pre.next.next;
            } else {
                pre = pre.next;
            }
            
        }
        return newHead.next;
    }
}

链表的中间节点

OJ链接

给定一个头结点为 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。

  1. 方法1
    思路:先遍历一遍得到长度,然后在找到长度/2的节点
class Solution {
    public ListNode middleNode(ListNode head) {        
        ListNode cur = head;
        int size = 0;
        while (cur != null) {
            cur = cur.next;
            size++;
        }
        int n = size / 2;
        while (head != null && n != 0) {
            head = head.next;
            n--;
        }
        return head;
    }
}
  1. 思路:
    利用快慢指针,fast前进的步长始终时slow的两倍,当fast到尾部时,slow在中间。
class Solution {
    public ListNode middleNode(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        return slow;
    }
}

链表的倒数第k个节点:JZ22

OJ链接
输入一个链表,输出该链表中倒数第k个结点。

  1. 方法1

思路:暴力遍历求得长度 size,然后遍历得到 size - k 处节点

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode FindKthToTail(ListNode head,int k) {
        int size = 0;
        ListNode tmp = head;
        while (tmp != null) {
            tmp = tmp.next;
            size++;
        }  
        if (head == null || size < k || k <= 0) {
            return null;
        }
        int cut = size - k;
        tmp = head;
        while(cut != 0) {
            tmp = tmp.next;
            cut--;
        }
        return tmp;   
    }
}
  1. 方法2

思路:fast指针先走k步,slow指针才开始走,此时二者相对距离为k步,当fast走到末尾时(此时移动size - k步), 对slow指针也走了size - k步,此时指向倒数第k个节点

public class Solution {
    public ListNode FindKthToTail(ListNode head,int k) {
        ListNode fast = head;
        ListNode slow = head;
        while (fast != null) {
            if (k != 0) {
                k--;
            } else {
                //slow和fast的相对距离为k
                slow = slow.next;
            }
            //先使fast走k步
            fast = fast.next;
        }
        //判断k是否合法,如果k大于链表长度,此时k != 0,但fast已经为空;如果k小于0,fast和slow一起走,最后都为空,所以返回null
        return k == 0 ? slow : null;       
    }
}

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

OJ地址

这道题时上一道题的进阶,思路差不多,如果我们要删除倒数的N个节点,就需要找到其前一个结点,然后改变指向,所以我们只需要让fast走N+1步,当fast到链尾时,slow指向N - 1节点

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode fast = head;
        ListNode slow = head;
        //fast先走n步
        while (fast != null && n != 0) {
            fast = fast.next;
            n--;
        }
        //如果fast==null,说明删除的是第一个节点
        if (fast == null) {
            return head.next;
        }
        //先使fast走一步,然后一起走
        fast = fast.next;
        while (fast != null) {
            fast = fast.next;
            slow = slow.next;
        }
        //删除第N个节点
        slow.next = slow.next.next;
        return head;
    }
}

合并两个有序链表:JZ25

OJ链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

  1. 方法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 mergeTwoLists(ListNode list1, ListNode list2) {
        ListNode dummyHead = new ListNode(0);
        ListNode cur = dummyHead;
        while (list1 != null && list2 != null) {
            if (list1.val < list2.val) {
                cur.next = list1;
                list1 = list1.next;
            } else {
                cur.next = list2;
                list2 = list2.next;
            }
            cur = cur.next;
        }
        //其中一个链表为空,链接另一个链表
        cur.next = list1 == null ? list2 : list1; 
        return dummyHead.next;
    }
}
  1. 方法2
    递归:每次都把最小值压入栈,最后出栈的时候,将所有数连在一起就可以了。说白了,就是用一个栈维护了顺序。最后的连接,当然是小的连小的,所以l1 小,就连到 l1,l2 小就连到 l2,最后先返回的,就是最小的头结点。

class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        if(list1 == null) {
            return list2;
        } 
        if (list2 == null) {
            return list1;
        }
        if (list1.val < list2.val) {
            list1.next = mergeTwoLists(list1.next, list2);
            return list1;
        } else {
            list2.next = mergeTwoLists(list1, list2.next);
            return list2;
        }
    }
}

回文链表

OJ链接

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

  1. 方法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) {
        //1.快慢指针找到中间节点
        ListNode fast = head;
        ListNode slow = head;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        //2.反转链表
        ListNode cur = slow.next;
        while (cur != null) {
            ListNode curNext = cur.next;
            cur.next = slow;
            slow = cur;
            cur = curNext;
        }
        //3.判断,由于slow的最后一个节点的结点域中存放的是 
        while (head != slow) {
            if (slow.val != head.val) {
                return false;
            }
            //如果是偶数
            if (head.next == slow) {
                return true;
            }
            head = head.next;
            slow = slow.next;
        }
        return true;
    }
}

从尾到头打印链表:JZ6

OJ链接

输入一个链表的头节点,按链表从尾到头的顺序返回每个节点的值(用数组返回)。

  1. 利用堆
import java.util.ArrayList;
import java.util.Stack;
public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        ArrayList ans = new ArrayList<>();
        Stack stack = new Stack();
        while (listNode != null) {
            stack.push(listNode.val);
            listNode = listNode.next;
        }
        while (!stack.empty()) {
            ans.add(stack.pop());
        }
        return ans;               
    }
}
  1. 递归
    ArrayList<Integer> ans = new ArrayList<>();
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        if (listNode != null) {
            printListFromTailToHead(listNode.next);
            ans.add(listNode.val);
        }
        return ans;
    }
}
  1. 头插
public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        ArrayList<Integer> ans = new ArrayList<>();
        while (listNode != null) {
            ans.add(0,listNode.val);
            listNode = listNode.next;
        }
        return ans;
    }
}

两个链表的第一个公共结点:JZ52

OJ链接

输入两个无环的单向链表,找出它们的第一个公共结点,如果没有公共节点则返回空。

思路:
假如两个链表是有交点的,我们定义p1,p2指针,分别从链表1和链表2开始遍历;
在这里插入图片描述
然后是p1,p2以相同速度移动,当任意一个到末尾时,上图是p2,此时它从链表1的头结点开始移动,也就是说任意一个为空时,从另一个链表头开始移动。由于速度相同它们的路程相同等于(链表1长度 + 链表2长度 - 公共部分长度)
所以:如果它们有公共结点时二者一定会相遇,如果没有那么它们走过的路程为(链表1长度 + 链表2长度) 所以最后都为null。

public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        ListNode p1 = pHead1;
        ListNode p2 = pHead2;
        while (p1 != p2) {
            p1 = (p1 == null) ? pHead2 : p1.next;
            p2 = (p2 == null) ? pHead1 : p2.next;
        }
        return p1;
    }
}

删除链表中的重复结点

OJ链接

在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。

思路:
因为头结点可能会是重复结点,所以我们定义一个虚拟头节点,遍历链表,删除重复元素

public class Solution {
    public ListNode deleteDuplication(ListNode pHead) {
        ListNode dummyHead = new ListNode(-1);
        dummyHead.next = pHead;
        ListNode cur = dummyHead;
        while (cur.next != null && cur.next.next != null) {
            if (cur.next.val == cur.next.next.val) {
                int tmp = cur.next.val;
                //删除相同元素
                while (cur.next != null && cur.next.val == tmp) {
                    cur.next = cur.next.next;
                }
            } else {
                cur = cur.next;
            }
        }
        return dummyHead.next;
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Zzt.opkk

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值