链表常见思路

链表的一些常见思路

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

🐏,好好看噢

从尾到头打印链表

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
双向链表是一种常见数据结构,它可以在链表中的每个节点中保存指向前一个节点和后一个节点的引用。这使得在双向链表中,我们可以在 O(1) 的时间复杂度内访问前一个节点和后一个节点。 实现双向链表思路如下: 1. 定义节点类:首先,我们需要定义一个节点类作为双向链表的基本单位。节点类应该包含两个重要的属性:数据和指针。数据用于存储节点中的值,指针用于指向前一个节点和后一个节点。 ```python class Node: def __init__(self, data): self.data = data self.prev = None self.next = None ``` 2. 构建双向链表类:接下来,我们需要构建一个双向链表类来管理整个链表。该类应该包含头节点和尾节点的引用。 ```python class DoublyLinkedList: def __init__(self): self.head = None self.tail = None ``` 3. 实现插入操作:要在双向链表中插入一个新节点,我们需要考虑两种情况:插入在链表的开头和插入在链表的其他位置。 - 插入在开头:如果要插入一个新节点作为链表的新头部,我们只需要将新节点的 next 指针指向当前头部节点,同时将当前头部节点的 prev 指针指向新节点。最后,更新链表的头部引用即可。 - 插入在其他位置:如果要插入一个新节点在链表的其他位置,我们需要先找到插入位置的前一个节点。然后,将新节点的 prev 指针指向前一个节点,将新节点的 next 指针指向前一个节点的下一个节点,再将前一个节点的 next 指针指向新节点,最后将新节点的下一个节点的 prev 指针指向新节点。 ```python def insert(self, data): new_node = Node(data) if self.head is None: self.head = new_node self.tail = new_node else: new_node.next = self.head self.head.prev = new_node self.head = new_node ``` 4. 实现删除操作:要在双向链表中删除一个节点,我们需要考虑几种情况:删除头节点、删除尾节点以及删除其他位置的节点。 - 删除头节点:如果要删除头节点,我们只需要将头节点的下一个节点作为新的头节点,并将新头节点的 prev 指针设为 None。 - 删除尾节点:如果要删除尾节点,我们只需要将尾节点的前一个节点作为新的尾节点,并将新尾节点的 next 指针设为 None。 - 删除其他位置的节点:如果要删除其他位置的节点,我们需要先找到要删除的节点。然后,将要删除节点的前一个节点的 next 指针指向要删除节点的下一个节点,将要删除节点的下一个节点的 prev 指针指向要删除节点的前一个节点。 ```python def delete(self, data): if self.head is None: return current = self.head while current: if current.data == data: if current.prev: current.prev.next = current.next else: self.head = current.next if current.next: current.next.prev = current.prev else: self.tail = current.prev break current = current.next ``` 这样,双向链表的基本实现就完成了。当然,还可以根据需要添加其他操作,比如查找节点、获取链表长度等。请注意,在操作双向链表时,我们需要特别注意处理边界情况,比如链表为空或只有一个节点的情况。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值