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

本文介绍了如何使用哈希、集合和栈的数据结构解决两个链表的第一个公共子节点问题,以及如何通过双指针方法判断回文链表和合并链表。还讨论了删除链表元素的不同策略,包括特定元素、倒数第n个节点和重复节点的删除。
摘要由CSDN通过智能技术生成

目录

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

解法一:Hash

解法二:集合

解法三:栈

2.回文链表

解法一:全部压栈,全部比较。

解法二:优化解法一

解法三:双指针

3.合并链表

3.1合并两个有序链表

解法一:新建一个链表

解法二:递归

3.2合并K个有序链表

3.3合并两个链表

4.双指针专题

4.1.寻找中间节点

4.2寻找倒数第k个元素

4.3.旋转链表

4.3.1.解法一:将整个链表反转,再将前k个和后n-k个链表反转即可

4.3.2.解法二:双指针

4.3.3.解法三:找到倒数第k个,也就是正数第lenth-k+1个

5.删除链表元素

5.1.删除特定元素

5.2.删除倒数第n个节点

5.3.删除重复结点

5.3.1.保留一个重复的结点,使每个结点只出现一次

5.3.2.删除所有的重复结点,只留下不同的结点


遇到问题时首先将常用的数据结构和算法思想都想一遍

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

注意做链表题目时要时刻注意给你的链表是否为空链表!!!

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

LeetCode160.相交链表

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。

图示两个链表在节点 c1 开始相交

两个链表的头节点已知,相交之后成为一个单链表,但相交的节点未知,需找到两个链表的合并点。

解法一:Hash

先将第一个链表存入Map中,然后遍历第二个链表,检查节点是否在Map存在

public static ListNode findFirstCommonNodeByMap(ListNode pHead1, ListNode pHead2) {
        if (pHead1==null||pHead2==null){
            return null;
        }
        HashMap<ListNode,Integer> hashMap=new HashMap<>();
        while(pHead1!=null){
            hashMap.put(pHead1,null);
            pHead1=pHead1.next;
        }
        while (pHead2!=null){
            if(hashMap.containsKey(pHead2)){
                return pHead2;
            }
            pHead2=pHead2.next;
        }
        return null;
    }

解法二:集合

思想与Hash一样

  public static 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;
    }

解法三:栈

将两个链表全部压入栈中,再取栈顶元素,找到最后一组相同的节点(因为栈后进先出)即可

public static 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(!stackA.isEmpty() && !stackB.isEmpty()){
            if(stackA.peek()==stackB.peek()){
                preNode=stackA.pop();
                stackB.pop();
            }else {
                break;
            }
        }
        return preNode;
    }

2.回文链表

力扣(LeetCode)234.回文链表

给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。

示例 1:

输入:head = [1,2,2,1]
输出:true

解法一:全部压栈,全部比较。

将链表中的元素全部压栈,然后一边出栈,一边重新遍历链表,比较两者的元素值

public static boolean isPalindrome(ListNode head) {
        Stack<ListNode> stack=new Stack<>();
        ListNode node=head;
        while (node!=null){
            stack.push(node);
            node=node.next;
        }
        while(!stack.isEmpty() &&head!=null){
            if(stack.pop().val!=head.val){
                return false;
            }
            head=head.next;
        }
        return true;
}

解法二:优化解法一

全部压栈,比较一半,遍历链表,将元素全部压入栈中,获得链表的总长度,比较一半的元素即可

public static boolean isPalindrome(ListNode head) {
        Stack<ListNode> stack=new Stack<>();
        ListNode node=head;
        int count=1;
        int newCount=0;
        while (node!=null){
            stack.push(node);
            node=node.next;
            count++;
        }
        newCount=count/2;
        while (count!=newCount){
            if(stack.pop().val!=head.val){
                return false;
            }
            head=head.next;
            count--;
        }
        return true;
}

解法三:双指针

将链表前半段反转创建一个新的链表,从头开始比较新的链表和slow指向的结点

public static boolean isPalindrome(ListNode head) {
        //快慢指针,最优解
        ListNode fast=head;
        ListNode slow=head;
        ListNode newHead=null;//新头
        ListNode node=head;
        //当链表节点为奇数个时fast.next=null,偶数时fast=null
        while(fast!=null&&fast.next!=null){//fast到达最后一个节点,slow到达一半
            fast=fast.next.next;
            slow=slow.next;
            //反转链表,,头插法
            node.next=newHead;
            newHead=node;
            node=slow;
        }
        //当链表节点为奇数个时slow=slow.next
        if(fast!=null){//奇数个
            slow=slow.next;
        }
        while (slow.next!=null){
            if (newHead.val!=slow.val){
                return false;
            }
            newHead=newHead.next;
            slow=slow.next;
        }
        return true;
}

3.合并链表

3.1合并两个有序链表

力扣(LeetCode)21.合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 

示例 1:

输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]

将两个升序链表合并为一个新的升序链表并返回,可以新建一个链表,然后分别遍历两个链表,每次比较并将小的那个节点接到新的链表上去,最后排完,另外可能还有一个链表不为空,直接将不为空的那个链表接到新链表上去。

解法一:新建一个链表

public static ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        ListNode dummyNode=new ListNode(-1);//虚拟节点,为了方便处理头节点
        ListNode newHead=dummyNode;
        while (list1!=null&&list2!=null){
            if (list1.val<=list2.val){
                dummyNode.next=list1;
                list1=list1.next;
            }else {
                dummyNode.next=list2;
                list2=list2.next;
            }
            dummyNode=dummyNode.next;
        }
        /*while (list1!=null){
            dummyNode.next=list1;
        }
        while (list2!=null){
            dummyNode.next=list2;
        }*/    
          /*这里用两个while循环也可以,只有一个while会执行,但再leetcode上会运行超时,所以选                    
        用下面这种表达式的写法*/
        dummyNode.next=list1==null?list2:list1;
        return newHead.next;
    }

解法二:递归

public static ListNode mergeTwoListsByRe(ListNode l1, ListNode l2) {
        if (l1==null){
            return l2;
        }
        if (l2==null){
            return l1;
        }
        if (l1.val<= l2.val){
            l1.next=mergeTwoListsByRe(l1.next,l2);
            return l1;
        }else {
            l2.next=mergeTwoListsByRe(l1,l2.next);
            return l2;
        }
    }

3.2合并K个有序链表

将合并两个有序链表的问题搞清楚了,合并K个也就是一个循环的事了,需要提前将链表装进数组中,再通过循环调用合并两个链表的方法,因为合并两个链表的返回值为新链表的头节点,所以无需担心这里得到的返回值。

public static ListNode mergeKLists(ListNode[] lists) {
        ListNode res=null;
        for (ListNode list : lists) {
            res=mergeTwoLists(res,list);
        }
        return res;
    }

3.3合并两个链表

leetcode 1669.合并两个链表

给你两个链表 list1 和 list2 ,它们包含的元素分别为 n 个和 m 个。请你将 list1 中下标从 a 到 b 的全部节点都删除,并将list2 接在被删除节点的位置。

下图中蓝色边和节点展示了操作后的结果:

注意,这里说的是下标a,b,下标是从0开始的,所以找到a的前一节点,b的后一节点,再将list2接进来即可。

public static ListNode mergeInBetween(ListNode list1, int a, int b, ListNode list2) {
       ListNode node1=null;
       ListNode node2=null;
       ListNode node3=list2;
       ListNode dummyNode=new ListNode(-1);//处理头节点,如果a=0即从头节点开始删除
       dummyNode.next=list1;
       node1=dummyNode;
       node2=dummyNode;
       int count=0;
       while (count<b+2){//node2到达被删的节点b的后一节点
           if (count<a)//node到达被删的节点a的前一节点
               node1=node1.next;
           node2=node2.next;
           count++;
       }
       while (list2.next!=null){
           list2=list2.next;
       }
       node1.next=node3;
       list2.next=node2;
       return list1;
   }

4.双指针专题

4.1.寻找中间节点

leetcode 876. 链表的中间结点

寻找一个链表的中间节点,可以利用快慢指针,即双指针,fast一次走两步,slow一次一步,当块fast指针到达链表尾部或外部时,慢指针slow刚好到达链表的中间,链表就直接返回slow,因为不管链表是不是奇数,偶数,slow指针都刚好满足题目的要求。

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

4.2寻找倒数第k个元素

输入一个链表,输出链表中倒数第k个节点

eg:1->2->3->4->5,k=2;

返回链表:4->5;

        将fast向后遍历到第K+1个节点,slow指向第一个节点,此时两个指针之间刚好间隔k个节点,此时两个指针同步向后走,当fast走到最后一个节点时,slow指向倒数第k个节点。

注意k可能大于链表的长度。

public static ListNode getKthFromEnd(ListNode head, int k) {
       ListNode fast=head;
       ListNode slow=head;
       while ((fast!=null&&k>0)){
           fast=fast.next;
           k--;
       }
       while (fast!=null){//此时slow指向倒数第k个节点,而fast=null
           fast=fast.next;
           slow=slow.next;
       }
       return slow;
   }

4.3.旋转链表

leetcode 61. 旋转链表

给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k 个位置。

eg:1->2->3->4->5,k=2;

返回链表:4->5->1->2->3;

4.3.1.解法一:将整个链表反转,再将前k个和后n-k个链表反转即可

4.3.2.解法二:双指针

快指针向前走k,再同步向前走,使slow.next指针指向倒数第k个节点,fast再指向头节点,令slow.next=null即可。简单点说,此题可以理解为就是将链表拆分成两份,然后将后一份接到前一份前面。如上可以分成两个链表1->2->3和4->5。

 public ListNode rotateRight(ListNode head, int k) {
       ListNode fast=head;
       ListNode slow=head;
       ListNode temp=head;
       int count=0;
       if (head==null||k==0){
           return head;
       }
       //注意k可能大于链表的长度,此时要取余
       while (temp!=null){
           temp=temp.next;
           count++;
       }
       if (k>=count){
           k=k%count;
           if (k==0){//k与链表长度相等
               return head;
           }
       }
       while (k>0){
           fast=fast.next;
           k--;
       }
       while (fast.next!=null){//此时fast在最后一个节点,slow.next指向倒数第k个节点
           fast=fast.next;
           slow=slow.next;
       }
       temp=slow.next;
       fast.next=head;
       slow.next=null;
       return temp;

    }

4.3.3.解法三:找到倒数第k个,也就是正数第lenth-k+1个

要注意取模防止越界。

 public ListNode rotateRight(ListNode head, int k) {
      if (head==null||k==0){
            return head;
        }
        ListNode fast=head;
        ListNode slow=head;
        ListNode newHead=head;
        int count=1;
        while(fast.next!=null){//此时的fast为链表的最后一个节点
            fast=fast.next;
            count++;
        }
        if (k>=count){
            k=k%count;
            if (k==0){
                return head;
            }
        }
        int len=count-k;
        for (int i = 0; i < len-1; i++) {
            slow=slow.next;
        }
        newHead=slow.next;
        fast.next=head;
        slow.next=null;
        return newHead;
    }

5.删除链表元素

5.1.删除特定元素

leetcode 203. 移除链表元素

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。

由于此处要求删除所有特定的元素的节点,所以头节点也需要处理,此处应定义一个虚拟节点指向头节点。

public ListNode removeElements(ListNode head, int val) {
        ListNode node=null;
        ListNode dummyNode=new ListNode(-1);
        dummyNode.next=head;
        node=dummyNode;
        while (node.next!=null){
            if (node.next.val==val){
                node.next=node.next.next;//此处node和dummyNode的.next是一致的,所以不需要特别指定是不是头节点
            }else {
                node=node.next;
            }
        }
        return dummyNode.next;
    }

5.2.删除倒数第n个节点

leetcode 19. 删除链表的倒数第 N 个结点

双指针法

 public static ListNode removeNthFromEnd(ListNode head, int n) {
        if (head==null){
            return head;
        }
        ListNode dummyNode=new ListNode(-1);//要处理头结点
        dummyNode.next=head;
        ListNode fast=dummyNode;
        ListNode slow=dummyNode;
        while (n>0){//此时fast和slow中间会隔n-1个结点
            fast=fast.next;
            n--;
        }
        while (fast.next!=null){//当fast指向最后一个结点时,fast和slow隔n-1个结点,即slow指向倒数第n个结点的前一结点
            fast=fast.next;
            slow=slow.next;
        }
        slow.next=slow.next.next;
        return dummyNode.next;
}

5.3.删除重复结点

5.3.1.保留一个重复的结点,使每个结点只出现一次

leetcode 83. 删除排序链表中的重复元素

将此结点与下一结点比较,如果结点的val值相等,则删除下一结点,因为在删除下一结点的时候只是将当前结点的指向越过了下一结点,所以下一次比较就是当前结点和指向的下一结点。

public ListNode deleteDuplicates(ListNode head) {
        if (head==null){
            return head;
        }
        ListNode node=head;
        while (node.next!=null){//保证有下一结点
            if (node.val==node.next.val){
                node.next=node.next.next;
            }
            else node=node.next;
        }
        return head;
}

5.3.2.删除所有的重复结点,只留下不同的结点

leetcode 82. 删除排序链表中的重复元素 II

此时需要将重复的结点全部删除,可以参照上一的思路,但是要注意此时要从虚拟结点开始比较下一结点和下下结点。

 //删除重复元素,只留下不同的结点
    public ListNode deleteDuplicates(ListNode head) {
        if (head==null){
            return head;
        }
        ListNode dummyNode=new ListNode(-1);
        dummyNode.next=head;
        ListNode node=dummyNode;
        while (node.next!=null&&node.next.next!=null){
            if (node.next.val==node.next.next.val){
                int x=node.next.val;
                while (node.next!=null&&node.next.val==x){//因为可能重复结点有两个及以上,又因为是已排好序的链表
                    node.next=node.next.next;
                }
            }
            else node=node.next;
        }
        return dummyNode.next;
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值