剑指offer之链表

目录

面试题6:从尾到头打印链表

面试题18-1:删除链表中重复的节点

面试题18-2:在O(1)时间内删除链表节点

面试题22:链表中倒数第k个结点

面试题23:链表中环的入口节点

面试题24:反转链表

面试题25:合并两个排序的链表

面试题35:复杂链表的复制

面试题52:两个链表的第一个公共结点


面试题6:从尾到头打印链表

题目:输入一个链表,按链表从尾到头的顺序返回一个ArrayList。

分析:典型的“后进先出”,可以用栈来实现。

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

面试题18-1:删除链表中重复的节点

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

分析:注意头节点也可能被删除!!!

    public ListNode deleteDuplication(ListNode pHead) {
        ListNode preHead = new ListNode(0);
        preHead.next = pHead;
        ListNode pre = preHead;
        ListNode cur = pHead;
        while(cur != null && cur.next != null){
            if(cur.val != cur.next.val)
                pre = cur;
            else{
                while(cur.next != null && cur.next.val == cur.val)
                    cur = cur.next;
                pre.next = cur.next;
            }
            cur = cur.next;

        }
        return preHead.next;
    }

面试题18-2:在O(1)时间内删除链表节点

题目:给定单向链表的头指针和一个节点指针,定义一个函数在O(1)时间内删除该节点。

分析:如果要删除的节点i不是尾节点,可先把i的下一个节点j的内容复制,然后把i的指针指向节点j的下一个节点;如果是尾节点,就需要从链表的头节点开始遍历。注意:如果链表只有一个节点,即删除的节点既是头节点也是尾节点,需要将头指针设置为null。

面试题22:链表中倒数第k个结点

题目:输入一个链表,输出该链表中倒数第k个结点。

分析:假设链表中有n个结点,则倒数第k个结点也就是第n-k+1个结点。定义两个指针,第一个指针先走k-1步,在走第k步时,第二个指针也从头开始遍历。由于两个指针始终相差k-1步,当第一个指针走到最后一个结点时,第二个指针刚好指向第n-k+1个结点。注意讨论结点为null以及k为0的情况。

    public ListNode FindKthToTail(ListNode head,int k) {
        if(head == null || k == 0)
            return null;
        ListNode quick = head,low = head;
        int i = 1;
        while(i < k && quick != null){
            quick = quick.next;
            i++;
        }
        while(quick != null && quick.next != null){
            quick = quick.next;
            low = low.next;
        }
        if(quick == null)
            return null;
        return low;
    }

面试题23:链表中环的入口节点

题目:给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。

分析:先用快慢指针来确定链表中是否包含环;在找到环中任意一个节点之后,可确定环中的节点数目k,我们先定义两个指针p1、p2均指向链表的头节点,如果让指针p1先走k步,再两个指针同时走,当p2到达环的入口节点时,p1已经围绕环走了一圈,又回到了入口节点,也就是说指针p1、p2相遇时即为环的入口节点。

    public ListNode EntryNodeOfLoop(ListNode pHead) {
        ListNode slow = pHead,fast = pHead;
        while(fast != null && fast.next != null){
            slow = slow.next;
            fast = fast.next.next;
            if(slow == fast)
                break;
        }
        if(fast == null || fast.next == null)
            return null;
        int loop = 1;
        fast = fast.next;
        while(slow != fast){
            fast = fast.next;
            loop++;
        }
        slow = pHead;fast = pHead;
        while(loop > 0){
            fast = fast.next;
            loop--;
        }
        while(slow != fast){
            fast = fast.next;
            slow = slow.next;
        }
        return fast;
    }

面试题24:反转链表

题目:输入一个链表,反转链表后,输出新链表的表头。

分析:定义三个指针,分别指向当前遍历到的节点、它的前一个节点以及后一个节点。常犯错误:返回的反转之后的头节点不是原始链表的尾节点。

循环实现

   public ListNode ReverseList(ListNode head) {
        if(head==null || head.next == null)
            return head;
        ListNode pNode = head;
        ListNode pPrev = null;
        while(pNode!=null){
            ListNode pNext = pNode.next;
            pNode.next = pPrev;
            pPrev = pNode;
            pNode = pNext;
        }
        return pPrev;
    }

递归实现:

   public ListNode ReverseList(ListNode head) {
        if(head==null || head.next == null)
            return head;
        ListNode temp = head.next;
        head.next = null;
        ListNode newHead = ReverseList(temp);
        temp.next = head;
        return newHead;
    }

面试题25:合并两个排序的链表

题目:输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。

分析:递归求解即可。

    public ListNode Merge(ListNode list1,ListNode list2) {
        if(list2 == null)
            return list1;
        if(list1 == null)
            return list2;
        ListNode head = null;
        if(list1.val <= list2.val){
            head = list1;
            head.next = Merge(list1.next,list2);
        }
        else{
            head = list2;
            head.next = Merge(list1,list2.next);
        }
        return head;
    }

面试题35:复杂链表的复制

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

分析:第一步,根据原始链表的每个结点N创建对应的N',并且将N’链接在N的后面;

第二步,设置复制出来的结点的m_pSibling。假设原始链表上的N的m_pSibling指向结点S,那么其对应复制出来的N’是N的m_pNext指向的结点,同样S’也是S的m_pNext指向的结点;

第三步,将这个长链表拆分成两个链表。

     public RandomListNode Clone(RandomListNode pHead){
        cloneNodes(pHead);
        connectRandomNodes(pHead);
        return reconnectNodes(pHead);
    }

    private void cloneNodes(RandomListNode pHead) {
        RandomListNode cur = pHead;
        while(cur != null){
            RandomListNode clone = new RandomListNode(cur.label);
            clone.next = cur.next;
            cur.next = clone;  //将N’链接在N的后面
            cur = clone.next;
        }
    }

    private void connectRandomNodes(RandomListNode pHead) {//设置复制出来的结点的random
        RandomListNode cur = pHead;
        while(cur != null){
            RandomListNode clone = cur.next;
            if(cur.random != null){
                clone.random = cur.random.next;
            }
            cur = clone.next;
        }
    }

    private RandomListNode reconnectNodes(RandomListNode pHead) {
        RandomListNode cur = pHead;
        RandomListNode cloneHead = null;
        RandomListNode cloneNode = null;
        if(cur != null){
            cloneHead = cloneNode = cur.next;
            cur.next = cloneNode.next;
            cur = cur.next;
        }
        while(cur != null){
            cloneNode.next = cur.next;
            cloneNode = cloneNode.next;
            cur.next = cloneNode.next;
            cur = cur.next;
        }
        return cloneHead;
    }

面试题52:两个链表的第一个公共结点

题目:输入两个单链表,找出它们的第一个公共结点。

分析:如果两个单链表有公共节点,那么这两个链表从某一节点开始,它们的next指针都指向同一个节点,因此它们之后的节点都是重合的。可以首先遍历两个链表得到链表的长度得到长度差x,然后在较长的链表上先走x步,再同时在两个链表上遍历,找到第一个相同的节点就是它们的第一个公共节点。

    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        if (pHead1 == null||pHead2 == null) {
            return null;
        }
        int count1 = countLen(pHead1);
        int count2 = countLen(pHead2);
        ListNode shortHead = pHead1,longHead = pHead2;
        if(count1 > count2){
            longHead = pHead1;shortHead = pHead2;
        }
        int flag = Math.abs(count1 - count2);
        while(flag > 0){
            longHead = longHead.next;
            flag--;
        }
        while(shortHead != longHead){
            shortHead = shortHead.next;
            longHead = longHead.next;
        }
        return shortHead;
    }

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值