链表常见思路

链表的一些常见思路

写这篇文章主要是介绍几道链表的经典题目,主要是为大家提供一种思路,也算是对自己知识的一种总结吧

🐏,好好看噢

从尾到头打印链表

由于链表的特有性质,它要从尾到头打印链表,相对于数组要麻烦一点,数组我们可以直接利用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 : 🐏,一起加油噢

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值