算法通关村第一关—链表白银挑战笔记 链表高频面试算法题

  1. 链表是否存在公共子节点
  2. 链表是否存在回文序列
  3. 合并有序链表
  4. 寻找中间结点
  5. 寻找倒数第K个元素
  6. 旋转链表
  7. 删除链表特定节点
  8. 删除连边倒数第K个节点
  9. 删除链表中的重复元素

1.链表是否存在公共子节点

在这里插入图片描述
解题技巧:常用的数据结构和常用算法思想想一遍。
常用的数据结构有数组、链表、队、栈、Hash、集合、树、堆。常用的算法思想有查找、排序、双指针、递归、迭代、分治、贪心、回溯和动态规划等等。

方法一 HashMap

链表A依次存入Hashset集合,并遍历链表B,看Hashset集合中是否有节点与之匹配,若有则是链表AB的第一个公共字节点,若无则AB链表无公共字节点。

  public NodeList findCommomNode(NodeList headA,NodeList headB){
        //定义一个hashset:无序且不重复;用来存放A链表
        HashSet<NodeList> 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;
    }

方法二 栈

链表A和链表B同时推入两个栈中,再用时出栈,如果出栈的元素相等,则继续出栈,直到出栈元素不同,则上一个元素就是第一个相同的节点。

 //栈的做法
    public NodeList findCommomNodeByTrack(NodeList headA,NodeList headB){
        //创建两个栈对象,用于存放链表A和链表B
        Stack<NodeList> stackA = new Stack<>();
        Stack<NodeList> stackB = new Stack<>();
        while (headA!=null){
            stackA.push(headA);
            headA=headA.next;
        }
        while (headB!=null){
            stackA.push(headB);
            headB=headB.next;
        }
        NodeList preNode = null;
       while (stackA.size()>0 && stackB.size() > 0){
           //查看栈顶元素是否相同
           if (stackA.peek()==stackB.peek()){
               preNode=stackA.pop();
               stackB.pop();
           }else {
               break;
           }
       }
         return preNode;
    }

方法三 差和双指针

让链表长的L1减去链表短的L2得到一个差值|L1-L2|,让L1先走|L1-L2|的步,此时L1和L2的长度已经是一致了,接下来,L1和L2同时向前走,直到遇到第一个相同的结点

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++;
        }
//将current的值变回头节点
        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.链表是否存在回文序列

方法一 栈

将链表全部推入栈中,一边出栈,一边链表进行遍历,比较二者的元素,如果有一个元素不相同则不存在回文序列

 public boolean isReturnNodeList(NodeList head){
        NodeList temp = head;
        Stack<Integer> stackNodeList = new Stack<>();
        //把链表节点的值放至栈中
        while (temp!=null){
            stackNodeList.push(temp.val);
            temp=temp.next;
        }
        while (head!=null){
            if (head.val != stackNodeList.pop()){
                return false;
            }
            head=head.next;
        }
        return true;
    }

方法二 双指针之快慢双指针

我们使用双指针思想里的快慢指针 ,fast一次走两步,slow一次走一步。当fast到达表尾的时候,slow正好到达一半的位置,那么接下来可以从头开始逆序一半的元素,或者从slow开始逆序一半的元素,都可以。

3.1合并有序链表

新建一个链表,然后分别遍历两个链表,每次都选最小的结点接到新链表上,最后排完。

3.2合并K个链表

如果面试遇到,我倾向先将前两个合并,之后再将后面的逐步合并进来,这样的的好处是只要将两个合并的写清楚,合并K个就容易很多,现场写最稳妥:

public NodeList mergeManyNodeList(NodeList[] manyNodeLists){
        //定义合并后的新链表
        NodeList newNodeList = null;
        //遍历链表集合,并递归合并两个有序链表
        for (NodeList nodeList:manyNodeLists){
            newNodeList = mergeTwoNodeLists(newNodeList, nodeList);
        }
        return newNodeList;
    }

4.双指针专题

4.1寻找中间节点

方法一 快慢指针 走两步,走一步

在这里插入图片描述
用两个指针 slow 与 fast 一起遍历链表。slow 一次走一步,fast 一次走两步。那么当 fast 到达链表的末尾时,slow 必然位于中间

 public NodeList findMiddleNodeList(NodeList head){
        //定义两个指针,快慢指针
        NodeList slow= head;
        NodeList fast =head;
     
        while (fast!=null && fast.next !=null){
            slow=slow.next;
            fast=fast.next.next;
        }
        return slow;
    }

4.2寻找倒数第K个元素

经典快慢双指针,区别是同样的速度,一个先走一个后走

在这里插入图片描述
快指针先走K+1,慢指针在第1个节点,两个指针同时向后走,快指针指向null的时候,慢指针刚好是倒数第K个节点。注意的点,可能K会大于链表长度
在这里插入图片描述

4.3旋转链表

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

方法一:链表反转

先将链表反转成{5,4,3,2,1},找到分割点分成两个链表{5,4} {3,2,1},再将这两个链表进行反转就行了{4,5}{1,2,3}

方法二:双指针找到(倒数)分割的位置,其实就是正着数Len-K+1 ,成为{1,2,3}{4,5},再将链表拼接{4,5}{1,2,3}

5.删除链表元素专题

5.1删除指定结点

在这里插入图片描述

使用虚拟头节点可以不用单独处理头结点,使用cur.next.val去判断
在这里插入图片描述

在这里插入图片描述

删除倒数第N个结点

在这里插入图片描述
方法一:整体遍历一遍获得链表长度Len,再重新遍历L-N+1就是要删除的地方
方法二:快慢双指针,这种方法只需要遍历一遍

5.2删除重复元素

在这里插入图片描述
在这里插入图片描述
重点是该链表是升序的,所以只要判断cur与cur.next是否相等,相等则将cur.next去除即可
在这里插入图片描述

5.3.2 重复元素全部删除

在这里插入图片描述
这时,则将cur.next与cur.next.next这两个进行比较就行,需要注意可能cur.next.next为空,而cur.next不为空的情况。
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值