【代码随想录】链表刷题

很多重复的题参考:【代码随想录】双指针法刷题

理论基础

单链表:

双链表:

循环链表:

单链表节点的定义:

public class ListNode {
    
    
    public int val; // 节点的值
    public ListNode next; // 指向的下一个节点

    public ListNode() {
    
    }

    public ListNode(int val) {
    
    
        this.val = val;
    }

    public ListNode(int val, ListNode next) {
    
    
        this.val = val;
        this.next = next;
    }
}

移除链表元素

题目:203. 移除链表元素

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

移除链表元素

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

递归:

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;
}

迭代:找到要删除节点的前一个节点,进行删除操作

class Solution {
    
    
    public ListNode removeElements(ListNode head, int val) {
    
    
        if (head == null) return head;
        ListNode node = head;
        while (node != null && node.next != null) {
    
    
            if (node.next.val == val) node.next = node.next.next;
            else node = node.next;
        }
        return head.val == val ? head.next : head;
    }
}

设计链表

题目:707. 设计链表

动态单链表

动态单链表:(虚拟头节点)

class ListNode {
    
    
    int val;
    ListNode next;

    ListNode() {
    
    }

    ListNode(int val) {
    
    
        this.val = val;
    }
}

class MyLinkedList {
    
    
    int size; // 链表元素个数
    ListNode head; // 虚拟头节点

    // 初始化链表
    public MyLinkedList() {
    
    
        size = 0;
        head = new ListNode(0);
    }

    // 获取第 index 个元素的值
    public int get(int index) {
    
    
        if (index < 0 || index >= size) return -1;
        ListNode cur = head; // 从头节点开始查找
        // 包含虚拟头节点, 所以查找第 index+1 个节点
        while (index-- >= 0) cur = cur.next;
        return cur.val;
    }

     // 在链表最前面插入一个节点
     public void addAtHead(int val) {
    
    
        addAtIndex(0, val);
    }

     // 在链表的最后插入一个节点
     public void addAtTail(int val) {
    
    
        addAtIndex(size, val);
    }

    // 在第 index 个节点之前插入一个新节点
    // 如果 index 为 0,那么新插入的节点为链表的新头节点
    // 如果 index 等于链表的长度,则说明是新插入的节点为链表的尾结点
    // 如果 index 大于链表的长度,则返回空
    public void addAtIndex(int index, int val) {
    
    
        if (index > size) return;
        if (index < 0) index = 0;
        size++;
        // 找到要插入节点的前驱
        ListNode pre = head;
        while (index-- > 0) pre = pre.next;
        // 插入操作
        ListNode newNode = new ListNode(val);
        newNode.next = pre.next;
        pre.next = newNode;
    }

    // 删除第 index 个节点
    public void deleteAtIndex(int index) {
    
    
        if (index < 0 || index >= size) return;
        size--;
        // 找到要删除节点的前驱
        ListNode pre = head;
        while (index-- > 0) pre = pre.next;
		// 删除操作
        pre.next = pre.next.next;
    }
}

动态双向链表

动态双向链表:(虚拟头节点和虚拟尾节点)

class ListNode {
    
    
    int val;
    ListNode next, prev;

    ListNode(int x) {
    
    
        val = x;
    }
}

class MyLinkedList {
    
    
    int size;
    ListNode head, tail;

    public MyLinkedList() {
    
    
        size = 0;
        head = new ListNode(0); // 虚拟头节点
        tail = new ListNode(0); // 虚拟尾节点
        head.next = tail;
        tail.prev = head;
    }

    public int get(int index) {
    
    
        if (index < 0 || index >= size) return -1;
        ListNode cur = head;

        // 索引 < 一半从前往后找, 索引 > 一半从后前找
        if (index < size / 2) {
    
    
            for (int i = 0; i <= index; i++) cur = cur.next;
        } else {
    
    
            cur = tail;
            for(int i = 0; i <= size - 1 - index; i++) cur = cur.prev;
        }
        return cur.val;
    }

    public void addAtHead(int val) {
    
    
        ListNode newNode = new ListNode(val);
        newNode.next = head.next;
        head.next.prev = newNode;
        head.next =  newNode;
        newNode.prev = head;
        size++;
        // addAtIndex(0, val);
    }

    public void addAtTail(int val) {
    
    
        ListNode newNode = new ListNode(val);
        newNode.next = tail;
        newNode.prev = tail.prev;
        tail.prev.next = newNode;
        tail.prev = newNode;
        size++;
        // addAtIndex(size, val);
    }

    public void addAtIndex(int index, int val) {
    
    
        if (index > size) return;
        if (index < 0) index = 0;
        // 找到要添加的前驱
        ListNode pre = head;
        while (index-- > 0) pre = pre.next;
        // 添加操作
        ListNode newNode = new ListNode(val);
        newNode.next = pre.next; // 新节点的 next 指向
        newNode.prev = pre; // 新节点的 prev 指向
        pre.next.prev = newNode; // 后继的 prev 指向新
        pre.next = newNode; // 前驱的 next 指向新
        size++;
    }

    public void deleteAtIndex(int index) {
    
    
        if(index >= size || index < 0) return;
        // 找到要删除的前驱
        ListNode pre = head;
        while (index-- > 0) pre = pre.next;
        // 删除操作
        pre.next.next.prev = pre;
        pre.next = pre.next.next;
        size--;
    }
}

静态单链表

静态单链表:(虚拟头节点)

class MyLinkedList {
    
    
    final int N = 1010;
    int[] e = new int[N];
    int[] ne = new int[N];
    int h = 0; // 头节点
    int idx = 1; // 有虚拟头节点
    int size = 0; // 长度

    public MyLinkedList() {
    
    }
    
    public int get(int k) {
    
    
        if (k < 0 || k >= size) return -1;
        int t = ne[h];
        while (k -- > 0) t = ne[t];
        return e[t];
    }
    
    public void addAtHead(int val) {
    
    
        e[idx] = val;
        ne[idx] = ne[h];
        ne[h] = idx++;
        size++;
    }
    
    public void addAtTail(int val) {
    
    
        int t = h;
        while (ne[t] != 0) t = ne[t];
        e[idx] = val;
        ne[idx] = ne[t];
        ne[t] = idx++;
        size++;
    }
    
    public void addAtIndex(int k, int val) {
    
    
        if (k <= 0) addAtHead(val);
        else if (k == size) addAtTail(val);
        else if (k > 0 && k < size) {
    
    
            k --;
            int t = ne[h];
            while (k -- > 0) t = ne[t];
            e[idx] = val;
            ne[idx] = ne[t];
            ne[t] = idx++;
            size++;
        }
    }
    
    public void deleteAtIndex(int k) {
    
    
        if (k < 0 || k >= size) return;
        int t = h;
        while (k -- > 0) t = ne[t];
        ne[t] = ne[ne[t]];
        size--;
    }
}

反转链表

题目:206. 反转链表

双指针:

public ListNode reverseList(ListNode head) {
    
    
    ListNode cur = head, pre = null;
    while (cur != null) {
    
    
        ListNode tmp = cur.next;
        cur.next = pre;
        pre = cur;
        cur = tmp;
    }
    return pre;
}

递归:

public ListNode reverseList(ListNode head) {
    
    
    if (head == null || head.next == null) return head;
    ListNode node = reverseList(head.next);
    head.next.next = head;
    head.next = null;
    return node;
}

头插法:空间 O(n)

public ListNode reverseList(ListNode head) {
    
    
    ListNode res = null;
    for (ListNode x = head; x != null; x = x.next)
        res = new ListNode(x.val, res);
    return res;
}

两两交换链表中的节点

题目:24. 两两交换链表中的节点

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

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

迭代 + 虚拟头节点:

public ListNode swapPairs(ListNode head) {
    
    
	ListNode vn = new ListNode(0, head); // 虚拟头节点
	ListNode cur = vn, tmp = null;
	while (cur != null && cur.next != null && cur.next.next != null) {
    
    
		tmp = cur.next;
		cur.next = cur.next.next;
		tmp.next = cur.next.next;
		cur.next.next = tmp;
		cur = cur.next.next;
	}
	return vn.next;
}

递归:

public ListNode swapPairs(ListNode head) {
    
    
	if (head == null || head.next == null) return head;
	ListNode next = head.next;
	head.next = swapPairs(next.next);
	next.next = head;
	return next;
}

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

题目:19. 删除链表的倒数第 N 个结点

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

快慢指针:

  • 快指针先走 n 步,然后和慢指针同时开始走
  • 当快指针指向最后一个节点,此时慢指针指向的便是要删除的节点(的前一个节点)
public ListNode removeNthFromEnd(ListNode head, int n) {
    
    
	if (head == null || head.next == null) return null;
	ListNode fast = head, slow = head;
	while (n-- > 0) fast = fast.next; // 快指针先走 n 步
	if (fast == null) return head.next; // 删除第一个节点
	while (fast.next != null) {
    
    
		fast = fast.next;
		slow = slow.next;
	}
	slow.next = slow.next.next;
	return head;
}

快慢指针 + 虚拟头节点:

一般使用虚拟头节点可以不用处理特殊情况

public ListNode removeNthFromEnd(ListNode head, int n) {
    
    
	ListNode vn = new ListNode(0, head); // 虚拟头节点
	ListNode slow = vn, fast = vn;
	while (n-- > 0) fast = fast.next; // 快指针先走 n 步
	while (fast.next != null) {
    
    
		fast = fast.next;
		slow = slow.next;
	}
	slow.next = slow.next.next;
	return vn.next;
}

递归:

class Solution {
    
    
    int cur = 0;
    public ListNode removeNthFromEnd(ListNode head, int n) {
    
    
        if (head == null) return null;
        head.next = removeNthFromEnd(head.next, n);
        cur++;
        if (cur == n) return head.next;
        return head;
    }
}

链表相交

题目:面试题 02.07. 链表相交

给你两个单链表的头节点 headAheadB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null

双指针:

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
    
    
	ListNode curA = headA, curB = headB;
	int lenA = 0, lenB = 0;
	// 求链表 A 的长度
	while (curA != null) {
    
     
		lenA++; 
		curA = curA.next; 
	}
	// 求链表 B 的长度
	while (curB != null) {
    
     
		lenB++; 
		curB = curB.next; 
	}
	curA = headA;
	curB = headB;
	// 链表 A 更长则让 A 多走, B 更长则让 B 多走,保证 A B 从同一位置开始比较
	if (lenA > lenB) for (int i = 0; i < lenA - lenB; i++) curA = curA.next;
	else for (int i = 0; i < lenB - lenA; i++) curB = curB.next;
	// 逐个比较 A 和 B 
	while (curA != null) {
    
    
		if (curA == curB) return curA;
		curA = curA.next;
		curB = curB.next;
	}
	return curA;
}

巧妙做法:

  • a 走完走 b,b 走完走 a,如果有相交点,必然会遇到
  • 如果没有相交点,最终会在相遇在 null
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
    
    
	ListNode h1 = headA, h2 = headB;
	while (h1 != h2) {
    
    
		h1 = (h1 == null) ? headB : h1.next;
		h2 = (h2 == null) ? headA : h2.next;
	}
	return h1;
}

哈希:

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
    
    
	Set<ListNode> set = new HashSet<>();
	ListNode p = headA;
	while (p != null) {
    
    
		set.add(p);
		p = p.next;
	}
	p = headB;
	while (p != null) {
    
    
		if (set.contains(p)) return p;
		p = p.next;
	}
	return null;
}

环形链表:快慢指针

题目:141. 环形链表 - 力扣(LeetCode)

给你一个链表的头节点 head ,判断链表中是否有环。

快慢指针:

public boolean hasCycle(ListNode head) {
    
    
	ListNode slow = head, fast = head;
	while (fast != null && fast.next != null) {
    
    
		slow = slow.next;
		fast = fast.next.next;
		if (slow == fast) return true;
	}
	return false;
}

哈希:

public boolean hasCycle(ListNode head) {
    
    
	Set<ListNode> set = new HashSet<>();
	while (head != null && !set.contains(head)) {
    
    
		set.add(head);
		head = head.next;
	}
	return head != null;
}

环形链表 II**

题目:142. 环形链表 II - 力扣(LeetCode)

给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

快慢指针 判断环 + 找环入口:

public ListNode detectCycle(ListNode head) {
    
    
	ListNode slow = head, fast = head;
	// 快慢指针判断是否有环
	while (fast != null && fast.next != null) {
    
    
		slow = slow.next;
		fast = fast.next.next;
		if (slow == fast) {
    
     // 有环, 找环的起始点
			fast = head; // 快指针从头开始
			while (fast != slow) {
    
    
				fast = fast.next;
				slow = slow.next;
			}
			return fast;
		}
	}
	return null;
}

哈希:

public ListNode detectCycle(ListNode head) {
    
    
	Set<ListNode> set = new HashSet<>();
	while (head != null && !set.contains(head)) {
    
    
		set.add(head);
		head = head.next;
	}
	return head;
}

猜你喜欢

转载自blog.csdn.net/weixin_43734095/article/details/126788369