《算法通关村第一关——链表经典问题笔记》

本文详细介绍了链表的一些经典问题及其解决方案,包括找到两个链表的第一个公共节点、判断链表是否为回文序列、合并有序链表、寻找中间节点以及删除特定节点的方法,主要涉及双指针、栈、递归等算法思想。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

《算法通关村第一关——链表经典问题笔记》

链表的常见的增删改查问题---注意指针位置。

1.两个链表第一个公共子节点

查找问题

分析问题:相交问题--》如何相交?

同时到一个地方?分为两个问题:1.链表相同长度2.不同长度

如果相同则直接比较,不同长度则要转换成一样的长度。------》拼接

解决思路:

1.常用的数据结构有数组、链表、队、栈、Hash、集合、树、堆。常用的算法思想有查找、排序、双指针、递归、迭代、分治、贪心、回溯和动态规划等等。

可以用集合,hashset------>唯一不重复,快速查找是否存在

public ListNode findFirstCommonNodeBySet(ListNode headA, ListNode headB) {
    Set<ListNode> set = new HashSet<>();
    while (headA != null) {
        set.add(headA);
        headA = headA.next;
    }
    
    while (headB != null) {
        if (set.contains(headB))
            return headB;
        headB = headB.next;
    }
    return null;
}

2.可以使用栈---》因为相交后面相同元素,从后往前比较

import java.util.Stack;
    public ListNode findFirstCommonNodeByStack(ListNode headA, ListNode headB) {
        Stack<ListNode> stackA=new Stack();
        Stack<ListNode> stackB=new Stack();
        while(headA!=null){
            stackA.push(headA);
            headA=headA.next;
        }
        while(headB!=null){
            stackB.push(headB);
            headB=headB.next;
        }
      
      ListNode preNode=null;
      while(stackB.size()>0 && stackA.size()>0){
          //比较栈顶
          if(stackA.peek()==stackB.peek()){
            preNode=stackA.pop();
             stackB.pop();
          }else{
              break;
          }
      }
        return preNode;
    }

3.变成相同的长度----》拼接法ab与ba

public ListNode findFirstCommonNode(ListNode pHead1, ListNode pHead2) {
         if(pHead1==null || pHead2==null){
             return null;
         }
        ListNode p1=pHead1;
        ListNode p2=pHead2;
        while(p1!=p2){
            p1=p1.next;
            p2=p2.next;
            if(p1!=p2){
            //一个链表访问完了就跳到另外一个链表继续访问
                if(p1==null){
                    p1=pHead2;
                }
                if(p2==null){
                    p2=pHead1;
                }
            }
        }
        return p1;
    }

4.双指针法:---》快慢指针----》长度变为一致

public ListNode findFirstCommonNode(ListNode pHead1, ListNode pHead2) {
     if(pHead1==null || pHead2==null){
             return null;
         }
        ListNode current1=pHead1;
        ListNode current2=pHead2;
        int l1=0,l2=0;
        //分别统计两个链表的长度
        while(current1!=null){
            current1=current1.next;
            l1++;
        }
        
         while(current2!=null){
            current2=current2.next;
            l2++;
        }
        current1=pHead1;
        current2=pHead2;
        int sub=l1>l2?l1-l2:l2-l1;
        //长的先走sub步
       if(l1>l2){
           int a=0;
           while(a<sub){
            current1=current1.next;
            a++;
        }   
       }
      
       if(l1<l2){
           int a=0;
           while(a<sub){
            current2=current2.next;
            a++;
        }   
       }
        //同时遍历两个链表
       while(current2!=current1){
          current2=current2.next;
          current1=current1.next;
       } 
        
        return current1;
    }

2.判断链表是否为回文序列

什么是回文---》就是前后读都相同----》对称

查找问题:

前后查找

解决思路:

1.将链表元素全部压栈,然后一边出栈,一边重新遍历链表,一边比较两者元素值,只要有一个不相等,那就不是。

栈是倒序的好办法--》可倒序解决问题

在遍历的时候使用递归来反转一半链表可以吗?

可以,因为对称的

public boolean isPalindrome(ListNode head) {
    ListNode temp = head;
    Stack<Integer> stack = new Stack();
    //把链表节点的值存放到栈中
    while (temp != null) {
        stack.push(temp.val);
        temp = temp.next;
    }
    //之后一边出栈,一遍比较
    while (head != null) {
        if (head.val != stack.pop()) {
            return false;
        }
        head = head.next;
    }
    return true;
}

3.合并有序链表

查找,增添问题

查找比较---------》注意指针问题

public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        ListNode prehead = new ListNode(-1);
        ListNode prev = prehead;
        while (list1 != null && list2 != null) {
            if (list1.val <= list2.val) {
                prev.next = list1;
                list1 = list1.next;
            } else {
                prev.next = list2;
                list2 = list2.next;
            }
            prev = prev.next;
        }
        // 最多只有一个还未被合并完,直接接上去就行了,这是链表合并比数组合并方便的地方
        prev.next = list1 == null ? list2 : list1;
        return prehead.next;
    }

4.寻找中间结点

查找问题

1.双遍历去中间

2.双指针法

这个问题用经典的快慢指针可以轻松搞定,用两个指针 slow 与 fast 一起遍历链表。slow 一次走一步,fast 一次走两步。那么当 fast 到达链表的末尾时,slow 必然位于中间。

偶数的时候该返回哪个?想一想为什么会这样?

原因是先判断后做,因此在后面的时候会多走一步。

为了避免这个原因-----------》fast.next != null

public ListNode middleNode(ListNode head) {
        ListNode slow = head, fast = head;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        return slow;
    }

寻找倒数第K个元素

一样的,可倒序k(栈)

双指针---》

public ListNode getKthFromEnd(ListNode head, int k) {
        ListNode fast = head;
        ListNode slow = head;
        while (fast != null && k > 0) {
            fast = fast.next;
            k--;
        }
        while (fast != null) {
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }

旋转链表

关注点:可能旋转的时候大于链表长度

因为k有可能大于链表长度,所以首先获取一下链表长度len,如果然后k=k % len,如果k == 0,则不用旋转,直接返回头结点。否则: 1.快指针先走k步。 2.慢指针和快指针一起走。 3.快指针走到链表尾部时,慢指针所在位置刚好是要断开的地方。把快指针指向的节点连到原链表头部,慢指针指向的节点断开和下一节点的联系。 4.返回结束时慢指针指向节点的下一节点。

代码可以由反转链表得出。

5.删除特定结点

遇到的问题---》并不是判断节点相同,而是里面的元素相同

虚拟节点可以解决头节点问题

public ListNode removeElements(ListNode head, int val) {
        ListNode dummyHead = new ListNode(0);
       //注意
        dummyHead.next = head;
        ListNode cur = dummyHead;
        while (cur.next != null) {
            if (cur.next.val == val) {
                cur.next = cur.next.next;
            } else {
                cur = cur.next;
            }
        }
        return dummyHead.next;
    }

双指针也可以解决------》记得不是慢指针是真节点,快指针是探寻节点

注意:跳出循环后最后面的慢指针的下一个指向null。

这里最好不用,因为要改变原来的数组,所以直接用单节点改变原来的就行了

删除倒数第n个结点

查找第倒数n+1个节点

public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy = new ListNode(0);
        dummy.next=head;
        int length = getLength(head);
        ListNode cur = dummy;
        for (int i = 1; i < length - n + 1; ++i) {
            cur = cur.next;
        }
        cur.next = cur.next.next;
        ListNode ans = dummy.next;
        return ans;
    }
​
    public int getLength(ListNode head) {
        int length = 0;
        while (head != null) {
            ++length;
            head = head.next;
        }
        return length;
    }

public ListNode removeNthFromEnd(ListNode head, int n) {
       ListNode dummy = new ListNode(0);
        dummy.next=head;
        ListNode first = head;
        ListNode second = dummy;
        for (int i = 0; i < n; ++i) {
            first = first.next;
        }
        while (first != null) {
            first = first.next;
            second = second.next;
        }
        second.next = second.next.next;
        ListNode ans = dummy.next;
        return ans;
    }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值