《算法通关村第一关——链表白银挑战笔记》

系列文章目录

《算法通关村第一关——链表青铜挑战笔记》


一、 两个链表的第一个公共节点(剑指 Offer 52)

首先,我们要理解两个单链表第一个公共子节点有什么特点,图下图所示。

《算法通关村第一关——链表白银挑战笔记》_第1张图片

特点一:两个链表的第一个公共子节点是 第一个相同的元素,提到查找相同元素,第一想到的解决方法自然就是利用 Hash表来解决

   ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        //采用Hash
        HashSet<ListNode> hashset = new HashSet<>();
        ListNode listNode = headA;
        while (listNode != null) {
            hashset.add(listNode);
            listNode = listNode.next;
        }
        listNode = headB;
        while (listNode != null) {
            if (!hashset.add(listNode)) {
                return listNode;
            }
            listNode = listNode.next;
        }
        return null;
    }

特点二:从右往左看,第一个公共子节点又是最后一个相同的元素,我们只需要将两个链表逆序对比,找到第一个不同的元素,并返回上一个元素即可,将链表逆序可以利用栈的先进后出的特性

    ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        //采用栈
        //声明两个栈,存储链表将其逆序
        Stack <ListNode>stackA = new Stack();
        Stack <ListNode>stackB = new Stack();
        ListNode listNode=headA;
        //分别存储两个链表
        while (listNode!=null){
            stackA.push(listNode);
            listNode=listNode.next;
        }
        listNode=headB;
        while(listNode!=null){
            stackB.push(listNode);
            listNode=listNode.next;
        }
        listNode=null;
        //寻找第一个不同的元素
        while (!stackA.isEmpty()&&!stackB.isEmpty()){
            if(stackA.peek()!=stackB.peek()){
              break;
            }
            listNode=stackA.peek();
            stackA.pop();
            stackB.pop();
        }
        return listNode;
    }

二、判断表是否为回文序列(LeetCode234)


在这里插入图片描述

将链表逆序,和原列表依次对比

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

观察特点,回文串就是对称相等,那么解决问题的关键就分三步走,第一步找到中点,第二步将一半逆序,第三步将两部分遍历对比,最关键一步就是 找中点

存到数组中,利用数组确定长度,找到中点

    public boolean isPalindrome1(ListNode head) {
        ArrayList<ListNode> listNodeArrayList = new ArrayList<>();
        ListNode listNode =head;
        while (listNode!=null){
            listNodeArrayList.add(listNode);
            listNode=listNode.next;
        }
        for(int i=0, j=listNodeArrayList.size()-1;i<listNodeArrayList.size()/2;i++,j--){
            if (listNodeArrayList.get(i).val!=listNodeArrayList.get(j).val){
                return false;
            }
        }
        return true;
    }

利用双指针,找到中点

  public boolean isPalindrome(ListNode head) {
		//定义快慢指针,找到中间点
        ListNode fast=head,slow=head;
        while (fast!=null&&fast.next!=null){
            fast=fast.next.next;
            slow=slow.next;
        }
        //根据fast区分奇数偶数个节点,找到链表反转的起点
        if(fast!=null){
            slow=slow.next;
        }
		//反转链表
        ListNode pre=null,next=null;
        while (slow!=null){
            next=slow.next;
            slow.next=pre;
            pre=slow;
            slow=next;;
        }
        //对比
        while (pre!=null){
            if(pre.val!=head.val){
                return false;
            }
            pre=pre.next;
            head=head.next;
        }
        return true;
    }
    

三、合并有序列表

1.和并有序列表(leetcode25)

代码如下(示例):

    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode newnode=new ListNode(-1);
        ListNode temp=newnode;
        while (l1!=null&&l2!=null){
            if(l1.val<l2.val){
                newnode.next=l1;
                l1=l1.next;

            }else {
                newnode.next=l2;
                l2=l2.next;

            }
            newnode=newnode.next;
        }
        if (l1!=null){
            newnode.next=l1;
        }
        if(l2!=null){
            newnode.next=l2;
        }
        return temp.next;
    }


2.合并k个有序列表(leetcode78)

代码如下(示例):

 public ListNode mergeKLists(ListNode[] lists) {
        ListNode ln=null;
        for(int i=0;i<lists.length;i++){
            ln=mergeKList(ln,lists[i]);
            
        }
        return ln;
    }
    public ListNode mergeKList(ListNode l1,ListNode l2){
        ListNode newnode=new ListNode(-1);
        ListNode temp=newnode;
        while(l1!=null&&l2!=null){
            if(l1.val<l2.val){
                temp.next=l1;
                l1=l1.next;
            }
            else{
                temp.next=l2;
                l2=l2.next;
            }
            temp=temp.next;
        }
        temp.next = l1==null? l2:l1;
        return newnode.next;
    }

3.一道无聊的题(leetcode1669)

    public ListNode mergeInBetween(ListNode list1, int a, int b, ListNode list2) {
       ListNode temp=new ListNode(-1);
       temp.next=list1;
       int a_temp=a;
        while ((a--)>0){
            temp=temp.next;
        }
        //出现过错误,把a自减了,还用a,以后要主意呀
        int length=b-a_temp;
        
        ListNode temp1=temp;
        while ((length--)>=-1){
            temp1=temp1.next;
        }
        ListNode temp2=list2;
        while (temp2.next!=null){
            temp2=temp2.next;
        }
        temp.next=list2;
        temp2.next=temp1;
        return list1;
    }

四、双指针专题

1. 找中间节点(leetcode876)

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

2 . 链表中倒数第k个节点(剑指 Offer 22)

乍一看这道题最简单的方法是循环两次,第一确定长度,第二次循环找到我们要的倒数第k个节点。
差k个节点就遍历完整个链表,让个一个指针先快跑k步,另一个指针在开始跑,遍历完一边后,慢的指针就是我们想要的

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

3.旋转链表(leetcode61)


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

《算法通关村第一关——链表白银挑战笔记》_第2张图片

输入:head = [1,2,3,4,5], k = 2
输出:[4,5,1,2,3]

首先我们要清楚k时可能要大于链表的长度的,因此我们先取余,如上图所示,k=7和k=2旋转结果是相同的
旋转k个位置就是将后k个元素接到了来年表的头部

public ListNode rotateRight(ListNode head, int k) {
        int len=0;
        //移除特殊情况
        if(head==null){
            return null;
        }
        //计算链表长度
        ListNode listnode =head;
        while(listnode!=null){
            len++;
            listnode=listnode.next;
        }  
        //得到后k个元素的k值
         k=(k)%len;   
         //找后k个元素的前一个
        ListNode fast =head,preslow=head,slow=null;
        while(fast.next!=null){
            if((k--)>0){
                fast=fast.next;
            }else{
                preslow=preslow.next;
                fast=fast.next;
            }
        }
        //成环
        fast.next=head;
        //断裂
        slow=preslow.next;
        preslow.next=null;
        
        return slow;
    }

将整个链表反转,找到分割点k,再将两部分别反转

 public ListNode rotateRight(ListNode head, int k) {
        if(head==null){
            return null;
        }
      
        int len=0;
        ListNode temp=head;
        while(temp!=null){
            temp=temp.next;
            len++;
        }
     
        k=k%len;
  		//循环到原位置,直接返回
        if(k==0){
            return head;
        }
        head=fanzhuan(head);
       temp=head;
       //把握好k的边界值
        for (int i = 0; i < k-1; i++) {
            temp=temp.next;
        }
        ListNode l1=fanzhuan(temp.next);
        temp.next=null;
        ListNode l2=fanzhuan(head);
        head.next=l1;
        return l2;
    }
    public ListNode fanzhuan(ListNode head){
        ListNode  pre=null,cur=head,next=null;
        while (cur != null) {
            next=cur.next;
            cur.next=pre;
            pre=cur;
            cur=next;
        }
        return pre;
    }

五、删除链表元素专题

1.删除特定节点(leetcode203)

使用递归

public ListNode removeElements(ListNode head, int val) {
            
         if(head==null){
             return head;
         }
         if(head.val==val){
             return removeElements(head.next,val);
         }else{
            
              head.next=removeElements(head.next,val);
              return head;
         }

           
    }

使用循环

 public ListNode removeElements(ListNode head, int val) {
            
         ListNode newnode=new ListNode(-1,head);
        
         ListNode temp=newnode;
         while(temp.next!=null){
             if(temp.next.val==val){
                 temp.next=temp.next.next;
             }else{
                  temp=temp.next;
             } 
         }
         return newnode.next;           
    }

2. 删除倒数第n个节点(leetcode19)

双指针找到倒数,第k个节点的前一个,其要考虑边界条件删除倒数第n个元素

    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode fast=head,slow=head;
        while(fast.next!=null){
            if(n-->0){
                fast=fast.next;
            }else{
                fast=fast.next;
                slow=slow.next;
            }
        }
        if(n>0){
            return head.next;
        }else{
            slow.next=slow.next.next;
            return head;
        }

    }

代码改进,加入虚拟头节点,处理删除第一个节点的情况

    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode newnode =new ListNode(-1,head);
        ListNode fast=newnode,slow=newnode;
        while(fast.next!=null){
            if(n-->0){
                fast=fast.next;
            }else{
                fast=fast.next;
                slow=slow.next;
            }
        }
            slow.next=slow.next.next;
            return newnode.next;
    }

3.删除重复节点(leetcode 83)

 public ListNode deleteDuplicates(ListNode head) {
        ListNode newnode=new ListNode(-1,head);
        ListNode temp=newnode;
        Set set=new HashSet();
        while(temp.next!=null){
            if(set.add(temp.next.val)){
                temp=temp.next;
            }else{
                temp.next=temp.next.next;
            }
        }
        return newnode.next;
    }

4.删除重复过节点(leetcode82)

   public ListNode deleteDuplicates(ListNode head) {
   		//加入虚拟头节点,方便删除第一个元素是重复节点的情况
        ListNode newnode =new ListNode(-1,head);
        ListNode temp=newnode;
        
        while(temp.next!=null){
        	//分两种情况,后继的后继为空,则一定不重复,不为空则判断是否为重复元素
            if(temp.next.next!=null){
                if(temp.next.val==temp.next.next.val){
                    int val=temp.next.val;
                    while(temp.next!=null&&temp.next.val==val){
                        temp.next=temp.next.next;
                    }
                }else{
                     temp=temp.next;
                }
            }else{
              temp=temp.next;
            }
        }
        return newnode.next;
    }

总结

`

主要介绍链表基本的操作,可以借用Hash表来解决相同重复等问题,倒转链表可以考虑栈的数据结构,巧妙的使用双指针找到中间元素,使用双指针找到倒数第k个元素

你可能感兴趣的:(算法,链表,笔记,java)