LeetCode算法题合集—链表篇

链表基础算法题

链表的定义(java)

//单链表的定义
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; }
}

1.移除链表元素

https://leetcode-cn.com/problems/remove-linked-list-elements/
给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。

示例 1:
输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]

示例 2:
输入:head = [7,7,7,7], val = 7
输出:[]

方法一:直接法—迭代
删除头结点时另做考虑

class Solution {
    public ListNode removeElements(ListNode head, int val) {
        while(head!=null && head.val==val){
            head=head.next;     //若满足条件,则移动头节点的位置
        }
        if(head==null) return head;
        ListNode first = head;  //保存头节点的位置
        while(head.next!=null){
            if(head.next.val==val){
                head.next=head.next.next;
            }else{
                head=head.next;
            }
        }
        return first;
    }
}
//时间复杂度: O(n)
//空间复杂度: O(1)

方法二:虚拟头节点(常用)
设置一个虚拟头结点,不需要另外考虑头节点,这样原链表的所有节点就都可以按照统一的方式进行移除了

class Solution {
    public ListNode removeElements(ListNode head, int val) {
        //虚拟结点,指向下一个结点是head头节点
        ListNode dummy = new ListNode(-1,head); 
        ListNode prev=dummy;    //借用其他结点进行删除,保证dummy.next指向头节点
        while(prev.next!=null){
            if(prev.next.val==val){
                prev.next=prev.next.next;
            }else{
                prev=prev.next;
            }
        }
        return dummy.next;
    }
}
//时间复杂度: O(n)
//空间复杂度: O(1)

方法三:递归
递归的终止条件是head为空,此时直接返回head
head 不为空时,递归地进行删除操作,然后判断head 的节点值是否等于val 并决定是否要删除head

class Solution {
    public ListNode removeElements(ListNode head, int val) {
        if (head == null) {
            return head;	//退出递归
        }
        head.next = removeElements(head.next, val);	//下一个结点交给递归处理
        return head.val == val ? head.next : head;	//保证当前节点满足条件
    }
}
//时间复杂度: O(n)
//空间复杂度: O(n) 其中n是链表的长度。空间复杂度主要取决于递归调用栈,最多不会超过n层

2.设计链表

https://leetcode-cn.com/problems/design-linked-list/
设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:valnext。val 是当前节点的值,next 是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。假设链表中的所有节点都是 0-index 的。

在链表类中实现这些功能:

  • get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
  • addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
  • addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
  • addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
  • deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。

方法一:单链表法
先设计 addAtIndex,因为伪头的关系 addAtHead 和 addAtTail 可以使用 addAtIndex 来完成。

public class ListNode {
  int val;
  ListNode next;
  ListNode(int x) { val = x; }
}

class MyLinkedList {

    int size;   //链表大小
    ListNode head;  //虚拟头节点

    public MyLinkedList() {
        size = 0;
        head = new ListNode(0);
    }
   
    /*+++++++++++++++++方法实现++++++++++++++++++*/
    
    //对应下标插入新节点
    public void addAtIndex(int index, int val) {
        if (index > size) return;
        if (index < 0) index = 0;
        ListNode node = new ListNode(val);
        ListNode prev = head;
        while(index-- > 0) prev = prev.next;
        node.next = prev.next;
        prev.next = node;
        size++;
    }
	//头部插入新节点
    public void addAtHead(int val) {
        addAtIndex(0,val);
    }
    //尾部插入新节点
    public void addAtTail(int val) {
        addAtIndex(size,val);
    }
    //删除对应下标的节点
    public void deleteAtIndex(int index) {
        if(index>=size||index<0) return;
        ListNode prev=head;
        while(index-- > 0) prev = prev.next;
        prev.next = prev.next.next;     //删除节点prev.next
        size--;
    }
	//获取对应下标的节点值
    public int get(int index) {
        if(index>=size||index<0) return-1;
        ListNode prev=head;
        while(index-- >= 0) prev = prev.next;
        return prev.val;
    }
}

3.反转链表

https://leetcode-cn.com/problems/reverse-linked-list/
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

题解:
假设存在链表 1→2→3→∅,我们想要把它改成 ∅←1←2←3。
方法一:迭代
在遍历列表时,将当前节点的 next指针改为指向前一个元素。由于节点没有引用其上一个节点,因此必须事先存储其前一个元素。在更改引用之前,还需要另一个指针tmp来存储下一个节点。不要忘记在最后返回新的头引用!
LeetCode算法题合集—链表篇_第1张图片

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode pre=null;
        ListNode curr=head;
        while(curr!=null){
            ListNode tmp=curr.next; // 保存下一个节点
            curr.next=pre;  //指向反向
            pre=curr;
            curr=tmp;
        }
        return pre;     //返回新的头节点
    }
}
//时间复杂度: O(n)
//空间复杂度: O(1) 

方法二:递归
LeetCode算法题合集—链表篇_第2张图片

class Solution {
    public ListNode reverseList(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode p = reverseList(head.next);
        head.next.next = head;  //指向反转 head.next-->head
        head.next = null;       // 当前head-->null
        return p;
    }
}
//时间复杂度: O(n)
//空间复杂度: O(n) 

4.两两交换链表中的节点

https://leetcode-cn.com/problems/swap-nodes-in-pairs/
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

输入:head = [1,2,3,4]
输出:[2,1,4,3]
题解:
方法一:迭代法
具体而言,交换之前的节点关系是temp -> node1 -> node2,交换之后的节点关系要变成 temp -> node2 -> node1,因此需要进行如下操作
LeetCode算法题合集—链表篇_第3张图片

class Solution {
    public ListNode swapPairs(ListNode head) {
        ListNode dummy = new ListNode(0);   //虚拟头节点
        dummy.next = head;
        ListNode prev = dummy;
        while(prev.next!=null && prev.next.next!=null){
            ListNode temp = head.next.next; // 缓存 next
            prev.next = head.next;          // 将 prev 的 next 改为 head 的 next
            head.next.next = head;          // 将 head.next(prev.next) 的next,指向 head
            head.next = temp;               // 将head 的 next 接上缓存的temp
            prev = head;                    // 步进1位
            head = head.next;               // 步进1位
        }

        return dummy.next;
    }
}
//时间复杂度: O(n)
//空间复杂度: O(1) 

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

https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。(1 <= n <= sz)
你能尝试使用一趟扫描实现吗?
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]

思路:双指针
双指针的经典应用,如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了。
这里我们待fast指向null的时候,slow指向的是要删除的前驱结点,直接slow.next=slow.next.next删除结点。

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy = new ListNode(0,head);  //虚拟头节点
        ListNode fast = head;   //快指针
        ListNode slow = dummy;   //慢指针
        while(n-->0){
                fast=fast.next;     //快指针先移动n步
        }
        while(fast!=null){
            fast = fast.next;
            slow = slow.next;
        }
        slow.next = slow.next.next;     //删除结点
        return dummy.next;
    }
}

你可能感兴趣的