链表的一些常见思路
写这篇文章主要是介绍几道链表的经典题目,主要是为大家提供一种思路,也算是对自己知识的一种总结吧
🐏,好好看噢
从尾到头打印链表
由于链表的特有性质,它要从尾到头打印链表,相对于数组要麻烦一点,数组我们可以直接利用int i = nums.length -1
从末尾遍历到数组的第一位元素
1.使用栈
栈由于有后进先出的特性,我们可以先将链表存到一个栈中,再从栈中取出来,这样就逆序了
class Solution{
public ArrayList<Integer> printListFromTailToHead(ListNode listNode){
// 形参中的ListNode是自定义的一个类。一般题目会提供的
ArrayList<Integer> res = new ArrayList<>();
// 栈,不同语言可能会稍有不同,最不济就建立一个栈,也算是对于栈数据结构的一种巩固
Stack<Integer> stack = new Stack<>();
while(listNode != null){
stack.add(listNode.val);
listNode = listNode.next;
}
while(!stack.isEmpty()){
res.add(stack.pop()); // 弹栈操作
}
return res;
}
}
2.使用递归
class Solution{
ArrayList<Integer> res = new ArrayList<>();
public ArrayList<Integer> printListFromTailToHead(ListNode listNode){
return res;
}
public void traverse(ListNode listNode){
// base case
if(listNode == null){
return;
}
tarverse(listNode.next);
res.add(listNode.val); // 在递归快要离开的地方添加元素,有点回溯的意味在里头
}
}
删除链表中的重复结点
思路:我们怎么去找到链表中是哪些结点重复呢?很简单,我们可以利用两个指针去指向链表,弄一个指针去前面探探路,只有前面的探路指针指向的结点不等于后面的指针的时候,我们再将后面的结点指向前面探路的指针指向的结点
public ListNode deleteDuplication(ListNode pHead) {
if(pHead == null){
return null;
}
ListNode fast = pHead;
ListNode slow = pHead;
while(fast != null){
if(fast.val != slow.val){
// 至于这两行代码是怎么写出来的,我建议可以结合画图去写
slow.next = fast;
slow = slow.next;
}
fast = fast.next;
}
slow.next = null; // 断开后面的联系
return pHead;
}
这里原有链表的联系在实际开发中根据语言不同可能需要手动断开,但对于Java这种有自动回收机制的就其实不必手动去断开
反转链表
1.递归
递归其实是比较简洁的,但对于可能没有学过递归的小伙伴可能不太友好
public ListNode reverse(ListNode head){
// base case 一个结点或者空的时候,返回本身即可
if(head == null || head.next == null){
return head;
}
// 反转链表的新的头结点last
ListNode last = reverse(head.next);
// 进行反转操作
head.next.next = head;
head.next = null;
return last;
}
2.迭代
对于每一个结点都进行反转链表的操作
public ListNode reverse(ListNode head){
ListNode pre = null;
ListNode cur = head;
while(cur != null){
ListNode next = cur.next;
cur.next = pre;
// 下面这两行代码顺序不能变哦
pre = cur;
cur = next;
}
}
链表是否成环
判断链表是否成环,其实挺简单的,这种属于是没有看过算法思想的时候没什么思路,看过一次之后以后遇到判断是否成环问题都可以很轻松地写出来了
解法思路:先说个生活的小问题吧。小明和小红在操场上跑步,小明跑得比较快,那是不是小明会在某个时刻追上小红呢? 那假设那是一条无止境的道路,小明和小红是不是永远不可能碰见,小明永远在前面。 判断链表是否成环,其实就是这个生活小问题。我们就可以搞个快指针(小明),再搞个慢指针(小红),然后看他们会不会在某个时刻相遇就行了
public boolean hasCycle(ListNode head){
ListNode fast = head;
ListNode slow = head;
while(fast != null && fast.next == null){
fast = fast.next.next; // 每次跑两步
slow = slow.next; // 每次跑一步
if(fast == slow){
return true;
}
}
return false; // 到达这里fast说明到达链表的末尾了
}
第K个结点
链表由于没办法直接访问第K个元素,所以只能从头开始一个一个遍历
如果是正向的第K个结点,还好办点,直接在每次遍历的时候,对结点进行计数就可以了。这个就不讲了
那如果是倒数第K个结点呢?
要是只是为了单纯拿到倒数第K个结点的值,其实也挺好做的,就利用上文的逆序遍历或者将链表反转就可以了
那如果是为了拿到倒数第K个结点连同后面的结点(相当于以倒数第K个结点为头结点的链表),返回值是ListNode
类型的
这里提供两种方法:第一,也是最容易理解的,倒数第K个结点,不就是正数第 N - K + 1 个结点吗?但是我们不知道N呀,不知道就求呗
我们通过遍历整个链表,得到N,然后就变得求正数第 N - K + 1 个结点了。是不是还蛮简单的?但是这种方法存在一个问题,就是我们需要对链表进行两次遍历。
第二,一个很奇妙的技巧。先解决一个问题,我们要到达正数第K个结点,需要走多少步呢?是不是 K - 1 步就行了,这个步就相当于链表的那个箭头嘛,如果不理解的话建议画个图,应该很快就明白了的。那我们要找到 第 N - K + 1 个结点,那是不是就走 N - K 步就行了,那如果我们要走到链表的末尾,也就是要走到链表末尾的null
位置,相当于链表的第 N + 1 个结点(其实这个结点是不存在的,这样说只是让你更好理解),也就是要走 N 步。
我们把上面那一段话翻译成代码,我们利用两个指针,p1
指针,p2
指针一开始都指向链表的头结点,p1
结点向前走 K 步,然后此时p2
开始动,p1,p2
一起往后走,走到链表的末尾,此时p1,p2
一共走 N - K 步,此时p2
就到达了第 N - K + 1 个结点的位置了,也就是倒数第K个结点的位置了,神不神奇,问题突然就解决了
public ListNode method(ListNode head,int K){
ListNode p1 = head;
ListNode p2 = head;
for(int i = 0; i < K; i++){
p1 = p1.next;
}
while(p1 != null){
p1 = p1.next;
p2 = p2.next;
}
return p2;
}
至此,这篇文章就结束了,这篇文章对于想要深度掌握链表的小伙伴肯定是远远不够的,因为里面还有很多需要进阶的东西,比如说如何找到链表闭环的起点呀,这也是但可以作为刚接触链表的小伙伴一个学习思路,这也是对我自己学习的一个总结而已,如果大家有什么好的想法也可以给我留言,大家一起进步噢!
ps : 🐏,一起加油噢