这几日刷到了很多链表的算法题,发现链表的算法题真的很抽象,基本都不太会做。。。无奈只能看评论和大佬的题解,总算是有了点启发和思路,趁着记忆还未褪却赶紧来记录下来。我发现这些链表的题大佬们都很喜欢用快慢指针来做,所以我特地做了一个汇总,不得不说,快慢指针真的很好用!!
- 判断链表是否有环?
本题的思路也是用快慢指针来做的,空间复杂度可以是O(1),常量的空间复杂度真的很高效,如何用快慢指针来做呢?无非就是判断快慢两个指针最后是否会相遇,如果有环的话,快慢两个指针最后肯定会相遇,那么我们只需要在循环里面加一个if判断就好了,返回true,其他的都是返回false。用快慢指针来解决这个问题是我认为最好理解的,就好比龟兔赛跑一样,如果赛道是一个环的话兔子和乌龟最终都会相遇,灵魂就是一个快一个慢!!
代码如下:
/**
* 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) {
ListNode gui;
ListNode tu;
gui=tu=head;
if(head == null || head.next==null){
return false;
}
while(head != null){
if(tu == null || tu.next==null){
return false;
}
tu = tu.next.next;//快指针永远比慢指针快一步
gui = gui.next;
if(tu == gui){
return true;
}
}
return false;
}
}
运行效率:
2. 回文链表
本题解题思路依旧是用了快慢指针,回文数即前后顺序都一样的一串数,只不过这里是链表,如果是字符串或者是数组的话会很容易解决,但是链表的话需要进行一些额外的操作,比如反转链表等,下一题我会顺便贴一个反转链表的题。现在我们先来看这个题目,由题目可知,我们只需要用两个指针,一个快,一个慢,当快指针到达链表尾部的时候,慢指针刚好到达链表的中间,然后我们将前部分的链表进行反转链表然后对比前后两端链表是否相等就可以了,思路一旦清晰了,实现起来就快得多了。额外说一下本题的前半部分反转链表是在遍历的时候就顺便反转了,而不是到最后整体翻转,
代码如下:
/**
* 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) {
ListNode pre = null;//预先指针
ListNode slow = head;//慢指针
ListNode fast = head;//快指针
while(fast != null && fast.next != null){
ListNode temp = slow.next;
if(pre != null) {
slow.next = pre;
}
pre = slow;
fast = fast.next.next;
slow = temp;
}
if(fast != null) slow = slow.next;
while(slow != null){
if(slow.val != pre.val) return false;
slow = slow.next;
pre = pre.next;
}
return true;
}
}
结果效率:
3. 反转链表
这道题目很简单,并不是用快慢指针来解决的,而是简单的运用指针的变化来实现的反转,迭代链表,让当前节点的下一个节点都指向它本身的上一个节点,即实现了局部反转,节点的位置没有改变,改变的仅仅是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 reverseList(ListNode head) {
ListNode pre = null;//表示当前节点的前一个节点
ListNode cur = head;//表示当前节点
//我们要实现反转只需要将当前节点的下一个节点指向当前节点的前一个节点即可
//如此反复到最后一个节点
while(cur != null){
ListNode t = cur.next;
cur.next = pre;//当前节点的下一个节点指向当前节点的上一个节点 实现局部反转
pre = cur;
cur = t;
}
return pre;
}
}
结果效率:
4. 合并两个有序链表
这个题目用的是递归,递归很迷,每次看代码的时候思路都很清晰感觉很简单,但是每次一写都觉得很难。。。
思路:因为给定的两个链表是有序的,这样就好做多了。用递归的话就要有结束条件l1和l2等于null的时候就会返回响应的l。
然后我们通过三目运算符来判断两个链表的值谁大谁小,小的那个节点的下一个节点指向一个递归,递归带的参数是本节点的next和一个三目运算符,用作判断谁大谁小
执行效率: