【剑指offer】链表相关题

【一】从尾到头打印链表

题目描述:输入一个链表,按链表从尾到头的顺序返回一个ArrayList。
解题:利用栈的先进后出思想
也可以直接用Collection.reverse()方法直接反转列表(具体见源码)

public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
    ArrayList<Integer> arrayList = new ArrayList<>();
    Stack<Integer> stack = new Stack<>();
    while (listNode != null){
        stack.push(listNode.val);
        listNode = listNode.next;
    }
    while (!stack.isEmpty()){
        arrayList.add(stack.pop());
    }
    return arrayList;
}

【二】链表中倒数第K个结点

题目描述:输入一个链表,输出该链表中倒数第k个结点。
解题:栈,先进后出,出栈时计算输出第K个结点

public ListNode FindKthToTail(ListNode head,int k) {
    if (head == null || k==0)
        return null;
    Stack<ListNode> stack = new Stack<>();
    while (head != null){
        stack.push(head);
        head = head.next;
    }
    int n = 0;
    while (!stack.isEmpty()){
        ListNode temp = stack.pop();
        n++;
        if (n == k)
            return temp;
    }
    return null;
}

【三】反转链表

题目描述:输入一个链表,反转链表后,输出新链表的表头。
解题:直接控制指针的指向(控制指向一般就需要创建多个结点来辅助)

public ListNode ReverseList(ListNode head) {
    if(head == null)
        return null;
    ListNode reversedHead = null;
    ListNode current = head;
    ListNode tmp = null;//用于保存后续链表
    ListNode pre = null;//前结点,用于反转指向
    while(current != null)
    {//pre->current->tmp  ===》 pre<-current tmp(先保存住) ==》后移,依次反转每两个结点间的指向
        tmp = current.next; //先保存后续链表,防止断裂后丢失
        current.next = pre; //反转,当前指针不再指向原始下个结点tmp,指向前一个结点pre
        if(tmp == null) //当下个结点为空时,说明当前结点已为尾结点
            reversedHead = current;//为什么返回的是最后一个结点???
        pre = current; //依次把前一个结点和当前结点后移一位,进行再一次的current与pre反转
        current = tmp;
    }
    return reversedHead;
}

【四】合并两个排序的链表

题目描述:输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
解题:
归并排序流程,链表形式
直接两两比较两个链表的结点值;新创建一个链表,较小值的list将插入到新链表中
特殊情况:其中一个list为空,直接返回另一个list;均不为空,但长度不同,当遍历完较短的链表后,将另一链表剩下结点全部直接插入新链表

public ListNode Merge(ListNode list1,ListNode list2) {
        if (list1 == null)
            return list2;
        if (list2 == null)
            return list1;
        //新建链表并初始化
        ListNode newList = new ListNode(-1);
        ListNode list = newList;
        while (list1 != null && list2 != null){
            if (list1.val <= list2.val){
                list.next = list1;
                list1 = list1.next;
            }else {
                list.next = list2;
                list2 = list2.next;
            }
            list = list.next;//勿忘此行 需要往后移至最尾部,以便连接下一插入结点
        }
        //当较短的一个链表遍历完结后
        if (list1 == null)
            list.next = list2;//直接将所有的剩余结点插入至新链表
        if (list2 == null)
            list.next = list1;
        return newList;//返回整个链表
//        return newList.next;//返回链表的头节点
    }

【五】两个链表的第一个公共结点

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

解题思路:
穷举遍历 两个链表结点两两依次遍历

public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
    if (pHead1 == null || pHead2 == null)
        return null;
    ListNode p2 ;
    while (pHead1 != null){
        p2 = pHead2;
        while (p2 != null){
            if (pHead1.val == p2.val)
                return pHead1;
            p2 = p2.next;
        }
        pHead1 = pHead1.next;
    }
    return null;
}

【六】链表中环的入口节点

题目描述:给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
解题:利用数学中的不同速度相向而行的问题----相遇即有环

public ListNode EntryNodeOfLoop(ListNode pHead){
    if (pHead == null)
        return pHead;
    ListNode slowPointer,fastPointer ;

    //判断是否有环
    boolean isloop = false;
    slowPointer = fastPointer = pHead;
    while (fastPointer != null && fastPointer.next != null){
        slowPointer = slowPointer.next;
        fastPointer = fastPointer.next.next;
        //相遇则有环
        if (slowPointer == fastPointer){//相遇点
            isloop =true;
            break;
        }
    }
    //一个指针从链表头开始,一个从相遇点开始,每次一步,再次相遇的点即是入口点
    if (isloop){
        slowPointer = pHead;//链表头开始
        while (fastPointer != null && fastPointer.next != null){
            //再次相遇点
            if (slowPointer == fastPointer)
                return slowPointer;
            slowPointer = slowPointer.next;
            fastPointer = fastPointer.next;
        }
    }
    return null;
}

【七】删除链表中重复的结点

题目描述:在一个排序的链表中,存在重复的结点,
请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。
例如,链表1->2->3->3->4->4->5 处理后为 1->2->5

解题思路:当前左右结点不同,则该链表其他地方也不会存在重复点

public ListNode deleteDuplication(ListNode pHead) {
    if (pHead == null)
        return pHead;
    ListNode head = new ListNode(0);
    head.next = pHead; //创建头节点,可能会出现第第一个结点与第二个就相等的情况
    ListNode pre = head;
    ListNode p = pHead;

    while (p != null){

        if (p.next != null && p.val == p.next.val){//删除自身结点,则可避免下一个结点与下下个结点也相同的情况
            while(p.next!=null && p.val == p.next.val)//必须采用while去找到最后一个结点,否则pre.next=p.next中p.next这个重复的结点并未被删除
                p = p.next;//找到最后一个相等结点
            pre.next = p.next; //删除中间所有相等结点
            p = p.next;//此时两者指向同一个结点
        }else {
            //连续两个值不相等
            pre = pre.next;
            p = p.next;
        }
    }
    return head.next; //为什么不能直接返回pHead?返回pHead时链表结点均不变
}

【八】复杂链表的复制

题目描述:输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。
(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)

解题:

  1. 遍历链表,复制每个结点,并将复制后的结点链接到原始结点之后,即N.next=N’
  2. 重新遍历,复制原始链表结点N的random指针给新结点N’,即N.random=N.random.next
  3. 拆分链表,将链表分为原链表和复制后的链表
public RandomListNode Clone(RandomListNode pHead)
{
    if (pHead == null)
        return null;

    RandomListNode oldNode = pHead;
    //1.遍历链表,复制结点的next结点
    while(oldNode != null){
        RandomListNode newNode = new RandomListNode(oldNode.val);
        RandomListNode oldNextNode = oldNode.next;
        newNode.next = oldNextNode;
        oldNode.next = newNode;
        oldNode = oldNextNode; //遍历
    }
    oldNode = pHead;
    //2.重新遍历,复制N的随机结点R给新节点N'
    while (oldNode != null){
        if (oldNode.random == null)  //此处if..else语句可换为条件 oldNode.next.random = oldNode.random==null?null:oldNode.random.next;
            oldNode.next.random = null; // oldNode.next即为新节点newNode
        else
            oldNode.next.random = oldNode.random.next; //该新随机结点已在上回遍历中被复制,此处不同于再新插入结点,直接指向即可
        oldNode = oldNode.next.next;// 移动到下一个原始结点(遍历操作)
    }
    oldNode = pHead;
    //3.拆分链表,奇偶互分
    RandomListNode pNewHead = pHead.next;
    while (oldNode != null){
        RandomListNode newNode = oldNode.next;
        oldNode.next = newNode.next;//从原始链表中删除了newNode
        newNode.next=  newNode.next==null?null:newNode.next.next;// //新链表结点必须判空,遍历至尾结点时需要赋值为null,否则出现空指针异常
        oldNode = oldNode.next;
    }
    return pNewHead;
}

PSgithub具体代码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值