这几道题是LeetCode上标签“链表”、列表“热题Top100”、难度为“简单”的几道题目,题解有官方题解也有个人题解,有的地方意思可能表达得不是很清楚也可能存在错误,有问题请提出,感谢❤
一.环形链表
题目描述:
给定一个链表,判断链表中是否有环。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是
-1,则在该链表中没有环。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。
进阶:
你能用 O(1)(即,常量)内存解决此问题吗?
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/linked-list-cycle
题解:
1.之前在别的地方做过这道题😄,看到题目就直接用双指针了,O(1)内存解决。
像跑操场,first指针比second指针快走两个节点,若链表有环,两个指针一定会在某个节点相遇。
/**
* 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 first = head;
ListNode second = head.next.next;
while (first != second ) {
if (second == null || second.next == null) {
return false;
}
first = first.next;
second = second.next.next;
}
return true;
}
}
2.O(n)的额外空间,用一个集合存放遍历过的节点,当遍历的节点已存在集合中,则证明链表有环。
/**
* 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> containMap = new HashSet();
while (head != null) {
if (containMap.contains(head)) { return true; }
containMap.add(head);
head = head.next;
}
return false;
}
}
二.相交链表
题目描述:
编写一个程序,找到两个单链表相交的起始节点。
如下面的两个链表:
在节点 c1 开始相交。
示例 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
输入解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为
[5,0,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
示例 2:
输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Reference of the node with value = 2
输入解释:相交节点的值为 2
(注意,如果两个链表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。在 A
中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。**
示例 3:
输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB
= 2
输出:null 输入解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
解释:这两个链表不相交,因此返回 null。
注意:
如果两个链表没有交点,返回 null.
在返回结果后,两个链表仍须保持原有的结构。
可假定整个链表结构中没有循环。 程序尽量满足 O(n)时间复杂度,且仅用 O(1) 内存。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/intersection-of-two-linked-lists
题解:
这道题可以直接暴力解,循环链表A的每个节点看链表B的每个节点是否有同个节点的情况,双重循环(注意记得先保存第二重循环的链表的头部,以免循环过程中头部引用丢失)。下面列出另外两种解法。
1.受到上一题的启发,可以使用一个集合保存一条链表的所有节点,遍历第二条链表判断集合中是否有相同节点。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
Set<ListNode> containSet = new HashSet();
if (headA == null || headB == null) { return null; }
while (headA != null) {
containSet.add(headA);
headA = headA.next;
}
while (headB != null) {
if (containSet.contains(headB)) { return headB; }
headB = headB.next;
}
return null;
}
}
三.反转链表
题目描述:
反转一个单链表。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
进阶:
你可以迭代或递归地反转链表。你能否用两种方法解决这道题?
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/reverse-linked-list
题解:
1.迭代方式反转链表,
假设curNode是当前遍历节点,preNode是上一个节点,nextNode是下一个节点。
若要反转,curNode.next = preNode,然后再引用nextNode节点。
因此在每次遍历过程中,需保存上一个节点preNode和下一个节点nextNode
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
ListNode preNode = null;
ListNode currentNode = head;
ListNode nextNode = null;
while (currentNode != null) {
nextNode = currentNode.next;
currentNode.next = preNode;
// 对于下个节点来说,当前节点为上个节点
preNode = currentNode;
currentNode = nextNode;
}
return preNode;
}
}
2.递归方式反转列表。递归操作可以让我们直接走到链表的最后一个节点进行操作,再一步步退回到头节点。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
// 递归结束条件,到达最后一个节点则返回该节点的引用
if (head == null || head.next == null) { return head; }
ListNode newHead = reverseList(head.next);
// 反转
head.next.next = head;
head.next = null;
return newHead;
}
}
四.回文链表
题目描述:
请判断一个链表是否为回文链表。
示例 1:
输入: 1->2
输出: false
示例 2:
输入: 1->2->2->1
输出: true
进阶:
你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?
题解:
1.刚开始没啥好的想法,想不到按进阶的要求完成题目。就还是新建了个栈,遍历一遍链表将节点放入栈。再遍历一遍链表并从栈中取节点值判断是否相同。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public boolean isPalindrome(ListNode head) {
ListNode indexHead = head;
Stack<ListNode> stack = new Stack();
while (head != null) {
stack.push(head);
head = head.next;
}
while ( !stack.isEmpty() ) {
if (stack.pop().val != indexHead.val ) { return false; }
indexHead = indexHead.next;
}
return true;
}
}
2.官方题解:用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题,反转后半部分链表,判断前后部分链表值是否一致(判断是否回文),最后要恢复链表。
class Solution {
public boolean isPalindrome(ListNode head) {
if (head == null) return true;
ListNode firstHalfEnd = endOfFirstHalf(head);
ListNode secondHalfStart = reverseList(firstHalfEnd.next);
ListNode p1 = head;
ListNode p2 = secondHalfStart;
boolean result = true;
// 因为需要先恢复链表后才能返回结果,所以用result暂存结果
while (result && p2 != null) {
if (p1.val != p2.val) result = false;
p1 = p1.next;
p2 = p2.next;
}
// 恢复链表
firstHalfEnd.next = reverseList(secondHalfStart);
return result;
}
public ListNode reverseList(ListNode head) {
ListNode preNode = null;
ListNode currentNode = head;
ListNode nextNode = null;
while (currentNode != null) {
nextNode = currentNode.next;
currentNode.next = preNode;
// 对于下个节点来说,当前节点为上个节点
preNode = currentNode;
currentNode = nextNode;
}
return preNode;
}
// 快指针一次走两步,慢指针一次走一步,当快指针到达尾部时,慢指针处于
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;
}
}