手撕代码之“链表”

删除重复链表

题目描述: 在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
解题思路:
(1)新建一个指向头节点pHead的新节点Head
(2)tmp指向链表的实际头节点pHead
(3)同时需要一个pre节点来保存当前节点的前一节点的,初始化时,pre=Head;以便于删除重复节点后能连接后续的节点
分两种情况讨论:当头节点就有相同的节点的时候,此时,Head.next=tmp.next;
当相同的节点不在头节点的时候,pre=pre.next;(指向当前链表的头节点),tmp=tmp.next;
图解算法
在这里插入图片描述

/*
 public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    public ListNode deleteDuplication(ListNode pHead){
        if(pHead==null || pHead.next==null){
            return pHead;
        }
        ListNode Head=new ListNode(0);
        Head.next=pHead;
        ListNode pre=Head;
        ListNode tmp=Head.next;
        while(tmp!=null){
            if(tmp.next!=null && tmp.val==tmp.next.val){
               while(tmp.next!=null && tmp.val==tmp.next.val){
                   tmp=tmp.next;
               }
               pre.next=tmp.next;
               tmp=tmp.next;
            }else{//头节点不相同的情况
                pre=pre.next;
                tmp=tmp.next;
            }
        }
        return Head.next;
    }
}

链表中找环的入口

题目描述:
给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
解题思路:
(1)使用快慢指针,fast=fast.next.next;slow=slow.next;快指针走过的路程为慢指针的两倍。如果有环,两者必然会相遇。
(2)第一次相遇后,将slow复原到pHead,fast仍然从相遇点出发,当两者以相同的速读再次前进时,下一次相遇点则为环的起点。

/*
 public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    public ListNode EntryNodeOfLoop(ListNode pHead){
        if(pHead==null || pHead.next==null){
            return null;
        }
        ListNode slow=pHead;
        ListNode fast=pHead;
        while(fast!=null && fast.next!=null){
            fast=fast.next.next;
            slow=slow.next;
            if(slow==fast){
                break;
            }
        }
        //第一次相遇,此时让slow回到起点,slow和fast以相同的速度前行,
        //他们的再次相遇点位环的起点
        slow=pHead;
        while(slow!=fast){
            fast=fast.next;
            slow=slow.next;
        }
        //相遇,slow为环状的起点
        return slow;
    }
}

约瑟夫环问题

问题描述:
首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0…m-1报数…这样下去…直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!_)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)
解题步骤:
(1)用java中的list集合来模拟约瑟夫环问题,模拟环状的关键在于:index%list.size();
(2)初始化index为要删除的节点前一个位置,index+m则表示为当前要删除的节点位置,删除完成节点后,需要将index–;即移动到前一个位置,便于下次循环继续删除。

import java.util.*;
public class Solution {
    public int LastRemaining_Solution(int n, int m) {
        //特判
        if(n==0 || m==0){
            return -1;
        }
        LinkedList<Integer> list=new LinkedList<Integer>();
        //创建循环链表
        for(int i=0;i<n;i++){
            list.add(i);
        }
        //表示index在头节点之前的位置
        int index=-1;
        while(list.size()>1){
            index=(index+m)%(list.size());
            list.remove(index);
            index--;//将index回到头节点之前的位置,即上一个节点
        }
        return list.get(0);
    }
}

两条链表中的公共节点

题目描述:
输入两个链表,找出它们的第一个公共结点。(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)
在这里插入图片描述

解题思路分析
(1)使用双指针方法,同时遍历两条链表。
(2)如果存在公共节点,那么,两个指针必然会相交。
(3)但是两条链表的长短不一,这种情况下,必然有一个指针会先停下来(如果没有找到公共节点),这个时候,就让这个指针指向另外一条链表的头节点,但另外一个指针也结束后,也让他指向另外一条链表。如此反复进行遍历,如果有节点,他们必将指向同一个节点。

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
         if(pHead1==null && pHead2==null){
             return null;
         }
         if(pHead1==null || pHead2==null){
             return null;
         }
         ListNode p=pHead1;
         ListNode q=pHead2;
         while(p!=q){
             p=p.next;
             q=q.next;
             if(p!=q && p==null){
                 p=pHead2;
             }
             if(p!=q && q==null){
                 q=pHead1;
             }
         }
        return p;
    }
}

复制复杂的链表问题

问题描述:
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点),请对此链表进行深拷贝,并返回拷贝后的头结点。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
解题思路:
由于存在Random指针,可以考虑用map将原链表中的每个节点的值保存起来。然后依次按照链表的的节点顺序从map中取出复制的节点,用于构建新的链表。分别用p、q指针指向原链表和目标链表。
直接上代码更加清晰动人

/*
public class RandomListNode {
    int label;
    RandomListNode next = null;
    RandomListNode random = null;

    RandomListNode(int label) {
        this.label = label;
    }
}
*/
import java.util.*;
public class Solution {
    public RandomListNode Clone(RandomListNode pHead){
        if(pHead==null){
            return null;
        }
        RandomListNode p = pHead;
        //新建节点
        RandomListNode target = new RandomListNode(p.label);
        RandomListNode q=target;
        HashMap<RandomListNode,RandomListNode> map=new HashMap<RandomListNode,RandomListNode>();
        //创建两条链表之间的映射关系
        while(pHead!=null){
            map.put(pHead,new RandomListNode(pHead.label));
            pHead=pHead.next;
        }
        //开始复制
        while(q!=null){
            q.next=map.get(p.next);//p点的下一个节点
            q.random=map.get(p.random);//p的random节点
            
            p=p.next;
            q=q.next;
        }
        return target;
    }
}

合并两条链表

题目描述:
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
思路分析:
(1)设置两个指针p和q分别指向两条链表,新建一条target链表用作最后的结果返回,同时给target新建一个辅助指针t:t=target
(2)比较p和q节点的值,分情况讨论,存在三种情况:

  • p.val< q.val,则t的下个节点接p
  • p.val==q.val,则t需要分别接p和q
  • p.val>q.val,则他需要接q;
    (3)最后,可能存在两条链表不一样长的情况,同时需要考虑两种情况,把长的剩余的链表直接到t的后面
    (4)最后注意是返回target.next
/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode Merge(ListNode list1,ListNode list2) {
        if(list1==null && list2==null){
            return null;
        }
        if(list1==null){
            return list2;
        }
        if(list2==null){
            return list1;
        }
        ListNode p=list1;
        ListNode q=list2;
        ListNode target=new ListNode(-1);
        ListNode t=target;
        while(p!=null && q!=null){
            if(p.val<q.val){
                t.next=p;
                p=p.next;
                t=t.next;
            }else if(p.val==q.val){
                t.next=p;
                p=p.next;
                t=t.next;
                t.next=q;
                q=q.next;
                t=t.next;
            }else{
                t.next=q;
                q=q.next;
                t=t.next;
            }
        }
        //至少有一条链表先完成
        if(p==null){
            t.next=q;
            t=t.next;
        }else if(q==null){
            t.next=p;
            t=t.next;
        }else{
            return target.next;
        }
        return target.next;
    }
}

链表的反转

题目描述:
给定一个链表,求出该链表的反转链表
解题思路1:
在这里插入图片描述

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode ReverseList(ListNode head) {
        if(head==null){
            return null;
        }
       ListNode pre=null;
       ListNode cur=head;
       ListNode next=null;
       while(cur!=null){
           next=cur.next;
           //连接逆转链表
           cur.next=pre;
           //三个指针向后移动
           pre=cur;
           cur=next;
       }
        return pre;
    }
}

解题思路2:
这种方式需要耗费额外的内存(需要复制每一个节点)

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode ReverseList(ListNode head) {
        if(head==null){
            return null;
        }
        ListNode p=head;
        ListNode target=new ListNode(-1);
        ListNode t=target;
        while(p!=null){
            ListNode q=new ListNode(p.val);//需要复制节点
            q.next=t.next;
            t.next=q;
            // 拼接完成
            p=p.next;
            
        }
        return target.next;
    }
}


输出链表倒数第K个节点

题目描述:
输入一个链表,输出该链表中倒数第k个结点。
解题思路:
(1)注意特判情况,当链表的长度小于输入参数K时,无效
(2)设置p和q两个指针,让p先走k步,然后让p和q同步走,当q为null时,此时p所指向的节点即为倒数第k个节点

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode FindKthToTail(ListNode head,int k) {
        if(count(head)<k){
            return null;
        }
        ListNode p=head;
        ListNode q=head;
        for(int i=0;i<k;i++){
            q=q.next;
        }
        while(q!=null){
            q=q.next;
            p=p.next;
        }
        return p;
    }
    private int count(ListNode head){
        if(head==null){
            return 0;
        }
        ListNode tmp=head;
        int count=0;
        while(tmp!=null){
            count++;
            tmp=tmp.next;
        }
        return count;
    }
}

从尾到头打印链表

解题思路:
(1)先反转链表,然后再遍历即可
(2)使用栈先存储链表,然后从栈中取出数据并打印;
这里以第(2)种方案为案例进行代码演示:

/**
*    public class ListNode {
*        int val;
*        ListNode next = null;
*
*        ListNode(int val) {
*            this.val = val;
*        }
*    }
*
*/
import java.util.*;
public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        ArrayList<Integer> res=new ArrayList<Integer>();
        if(listNode==null){
            return res;
        }
        Stack<Integer> stack=new Stack<Integer>();
        ListNode tmp=listNode;
        while(tmp!=null){
            stack.add(tmp.val);
            tmp=tmp.next;
        }
        while(!stack.isEmpty()){
            res.add(stack.pop());
        }
        return res;
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值