剑指offer之链表篇

剑指offer之链表篇

剑指offer

    剑指offer是一本很棒的面试宝典,里面讲述了面试官对应聘者的要求与期望,也讲述了面试环节,面试官与应聘者的心理。

    但更多的是讲述面试官对应聘者的技术要求,编码要求。

    从基础知识、高质量代码、解决问题的思路、优化时空效率与各项能力这几个章节展开叙述,为大量程序员提供了提升能力、查漏补缺的机会,我也从中受益匪浅,为此感谢剑指offer的作者,总结分享编程知识。

    

牛客网

    牛客网是一个专业IT笔试面试备考平台,我就是在这个平台上做的剑指offer练习,也感谢牛客网,给我编码提供了便利,建议备考面试的程序员到上面去看看,里面整合的试题资源确实丰富,而且还有很多其他在线编程题目,如华为、小米编程题目,ACM,JS等等...


链表简述

    链表是一种重要的数据结构,用来存储数据信息。其特点是本身存储一个单元的信息,然后有一个指针指向下一个节点。如:

剑指offer之链表篇_第1张图片

这是一个链表

剑指offer之链表篇_第2张图片

这也是一个链表

    原本我以为一个链表就是一串节点,做了剑指offer后发现错了,每一个节点都叫一个链表,如A链表就是有A->B->C三个元素,B链表就是有B->C连个元素,C链表只有C自己一个元素。

    剑指offer有一题叫做链表中倒数第K个节点(下面有),若你的输出只是一个链表中的单位(即只有一个节点,指针为空),那你最后的结果就错了。

    链表可以存储各种类型的数据,可以是一个字符、一个整形、浮点型等任何基本数据类型,甚至可以是对象(java中是对象的引用)。

    


   

从尾到头打印链表

    题目:输入一个链表的头结点,从尾到头反过来打印出每个节点的值。

    链表从头到尾打印比较简单,本来我是想将指针改变方向,这样把链表倒转过来,然后本来末尾的节点变成了头结点,这样就可以了,但是这样就会破坏了链表本来的数据结构,答案就出错了。

    所以,最简单的实现方法就是用一个栈去存储链表元素,然后逐个输出就可以了。

    

import java.util.Stack;
import java.util.ArrayList;
public class Solution {
    public ArrayList printListFromTailToHead(ListNode listNode) {
        ArrayList list = new ArrayList<>();
        Stack stack = new Stack<>();
        while( listNode != null ){
            stack.push(listNode.val);
            listNode = listNode.next;
        }
        while( !stack.empty() ){
            list.add(stack.pop());
        }
        return list;
    }
}
    

在线编程要我的结果放到一个ArrayList中,所以我还要一个个存进给定的容器中,作为返回值。

    

链表中倒数第K个节点

    问题:输入一个链表,输入该链表中倒数第K个节点。

    这一个问题跟上一个链表问题一样,用一个栈作为容器去存储链表节点,然后再弹出K次就可以得到倒数第K个节点了,这是我的做法。

    书本上的做法是用两个指针,第一个先走K步,然后第二个再开始走,两个一起走,当第二个走到底,那么第一个指针指向的节点就是我们要的结果,因为它距离第二个指针(也就是链表的末尾)的长度是K。

    我的做法需要用一个栈去存储,空间性能没有书本上的好,需要额外的空间为O(n)。

import java.util.Stack;
public class Solution {
    public ListNode FindKthToTail(ListNode head,int k) {
        if(k == 0||head == null){
            return null;
        }
		Stack stack = new Stack<>();
        ListNode result = null;
        while( head.next != null ){
            stack.push(head);
            head = head.next;
        }
        stack.push(head);
        for(int i=0;i

改进了程序,不使用栈:

public class Solution {
    public ListNode FindKthToTail(ListNode head,int k) {
        if(k == 0||head == null){
            return null;
        }
        ListNode pre = head;
        ListNode result = head;
		for(int i = 0; i < k - 1 ; i ++){
            if( pre.next == null ){
                return null;
            }
            pre = pre.next;
        }
        while( pre.next != null ){
            pre = pre.next;
            result = result.next;
        }
        return result;
    }
}

    

反转链表

    问题:定义一个函数,输入一个链表头节点,反转该链表并输出反转后的头节点。

    定义3个指针,一个指针指向下一个节点(next),一个指针记录当前节点,一个指针指向前一个节点(pre)。

    next找到并且记录下一个节点,然后当前节点(head)与前一个节点(pre)反转,即实现反转。然后两个指针向前移动,pre指向head,head指向next。

    最后返回pre指向末尾链表。

public class Solution {
    public ListNode ReverseList(ListNode head) {
	ListNode pre = null;
        ListNode next = null;
        while(head != null){
            next = head.next;
            head.next = pre;
            pre = head;
            head = next;
        }
        return pre;
    }
}

合并两个排序链表

    问题:输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是按照递增排序的。

    我的思路是:考虑两个链表是否为空,然后确定头节点,再循环比较两链表中大小,确定顺序,最后就是续接上未遍历完的链表。
    书上是:用递归,考虑是否为空,比较当前链表值,然后返回其中一个链表节点,递归调用,再次比较下一个节点。

    

我的代码

public class Solution {
    public ListNode Merge(ListNode list1,ListNode list2) {
        if(list1 == null && list2 != null){
            return list2;
        }
        if(list2 == null && list1 != null){
            return list1;
        }
        if(list1 == null && list2 == null){
            return null;
        }
        ListNode thisList = null;
        ListNode head = null;
        if( list1.val < list2.val ){
            thisList = list1;
            head = thisList;
            list1 = list1.next;
        }else{
            thisList = list2;
            head = thisList;
            list2 = list2.next;
        }
        while(list1 != null && list2 != null){
            if(list1.val < list2.val){
                thisList.next = list1;
                list1 = list1.next;
                thisList = thisList.next;
            }else{
                thisList.next = list2;
                list2 = list2.next;
                thisList = thisList.next;
            }
        }
        while(list1 != null){
            thisList.next = list1;
            list1 = list1.next;
            thisList = thisList.next;
        }
        while(list2 != null){
            thisList.next = list2;
            list2 = list2.next;
            thisList = thisList.next;
        }
        return head;
    }
}


    

复杂链表的复制

    问题:复杂链表中,除了一个next指针外,还有一个Random指针,指向该链表中的随机链表节点,要求复制该链表。

    我的思路:先用next指针创建一条链表,然后从头节点开始遍历,寻找随机指针指向的节点,时间复杂度是O(n2)。

    书上的方法:1.用一个哈希表存放<当前给定的链表节点,我创建好的链表节点>,然后就是一一对应的把random指针指向对应的键值对链表节点就可以了(空间换时间)。2.把创建好的链表链接到给定的链表后面,然后就如下图,再设置指针指向就可以了。

剑指offer之链表篇_第3张图片

我的代码

public class Solution {
    public RandomListNode Clone(RandomListNode pHead)
    {
        if(pHead == null){
            return null;
        }
        RandomListNode head = new RandomListNode(pHead.label);
		myClone(head,pHead);        
        return head;
    }
    private void myClone(RandomListNode myLink,RandomListNode pHead){
        RandomListNode recordmyLink = myLink;
        RandomListNode recordpHead = pHead;
        while(pHead.next != null){
            RandomListNode node = new RandomListNode(pHead.next.label);
            myLink.next = node;
            myLink = myLink.next;
            pHead = pHead.next;
        }
        myLink = recordmyLink;
        pHead = recordpHead;
        while(pHead != null){
            RandomListNode soon = recordpHead;
            RandomListNode changeMyLink = recordmyLink;
            while(soon != null){
                if(soon == pHead.random){
                    myLink.random = changeMyLink;
                }
                changeMyLink = changeMyLink.next;
                soon = soon.next;
            }
            myLink = myLink.next;
            pHead = pHead.next;
        }
    }
}

优化后的代码(哈希表法)

import java.util.*;
public class Solution {
    public RandomListNode Clone(RandomListNode pHead)
    {
        if( pHead == null ) 
            return pHead;
        boolean flag = true;
        HashMap hash = 
            new HashMap();
        RandomListNode newHead = new RandomListNode(pHead.label);
        RandomListNode soon = newHead;
        RandomListNode record = pHead;
        while( record != null ){
            RandomListNode node = new RandomListNode(record.label);
            soon.next = node;
            soon = soon.next;
            if( flag ){
                flag = false;
                newHead = node;
            }
            hash.put(record,node);
            record = record.next;
        }
        for( RandomListNode key : hash.keySet() ){
            hash.get(key).random = hash.get(key.random);
        }
        return newHead;
    }
}


两个链表的第一个公共节点

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

    我的思路:使用两个辅助栈,然后遍历两个链表,将他们的链表节点都压入栈中,逐个弹出比较(需要额外内存O(m+n))。

    书上的最优方法:遍历一遍两链表,知道两个链表的长短,第二次遍历时,较长链表先走上若干步,然后两链表同时开始遍历,知道找到第一个公共节点。

    

我的代码

import java.util.Stack;
public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        if( pHead1 == pHead2 ){
            return pHead1;
        }
 		Stack stack1 = new Stack<>();
        Stack stack2 = new Stack<>();
        while( pHead1 != null ){
            stack1.push(pHead1);
            pHead1 = pHead1.next;
        }
        while( pHead2 != null ){
            stack2.push(pHead2);
            pHead2 = pHead2.next;
        }
        ListNode record = null;
        while( !stack1.empty() && !stack2.empty() ){
            ListNode thisNode = stack1.pop();
            if(stack2.pop() == thisNode){
                record = thisNode;
                continue;
            }else{
                return record;
            }
        }
        return record;
    }
}


优化后

public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
 		if( pHead1 == null || pHead2 == null)
            return null;
        if( pHead1 == pHead2 )
            return pHead1;
        int length1 = 0;
        int length2 = 0;
        ListNode record1 = pHead1;
        ListNode record2 = pHead2;
        while( pHead1 != null || pHead2 != null ){
            if( pHead1 != null ){
                length1 ++;
                pHead1 = pHead1.next;
            }
            if( pHead2 != null ){
                length2 ++;
                pHead2 = pHead2.next;
            }
        }
        if( length1 > length2 ){
            int count = length1 - length2;
            while( count > 0 ){
                record1 = record1.next;
                count --;
            }
            while( record1 != null && record2 != null ){
                if( record1 == record2 ){
                    return record1;
                }
                record1 = record1.next;
                record2 = record2.next;
            }
        }else{
            int count = length2 - length1;
            while( count > 0 ){
                record2 = record2.next;
                count --;
            }
            while( record1 != null && record2 != null ){
                if( record1 == record2 ){
                    return record1;
                }
                record1 = record1.next;
                record2 = record2.next;
            }
        }
        return null;
    }
}

链表中环的入口

    问题:一个链表中包含环,如何找出环的入口节点?

    我的思路:遍历链表,每次操作:将当前链表节点指针与容器中的链表节点做判断,若相等,则为环入口,若不相等,将当前链表存入容器。(需要额外的内存O(n)与额外的时间O(n2))

    书的方法:找到环点数,然后用双指针移动。(无需额外内存与额外的时间消耗O(1))

我的代码

import java.util.ArrayList;
public class Solution {
     
    public ListNode EntryNodeOfLoop(ListNode pHead)
    {
        if(pHead == null){
            return null;
        }
        ArrayList list = new ArrayList<>();
        ListNode thisNode = pHead;
        list.add(thisNode);
        while(thisNode != null){
            for(int i=0;i

优化后的代码

public class Solution {

    public ListNode EntryNodeOfLoop(ListNode pHead)
    {
        if( pHead.next == null )
            return null;
        ListNode meetingNode = meetingNode(pHead);
        int count = 1;
        ListNode record = meetingNode;
        while( record.next != meetingNode ){
            record = record.next;
            count ++;
        }
        ListNode record1 = pHead;
        ListNode record2 = pHead;
        for( int i = 0; i < count ; i ++ ){
            record1 = record1.next;
        }
        while( record1 != record2 ){
            record1 = record1.next;
            record2 = record2.next;
        }
        return record1;
    }
    private ListNode meetingNode(ListNode pHead){
        if( pHead == null )
            return null;
        ListNode pSlow = pHead.next;
        if( pSlow == null )
            return null;
        ListNode pFast = pSlow.next;
        while( pFast != null && pSlow != null ){
            if( pFast == pSlow )
                return pFast;
            pSlow = pSlow.next;
            pFast = pFast.next;
            if( pFast != null )
                pFast = pFast.next;
        }
        return null;
    }
}



删除链表中重复的节点

    问题:在一个排序的链表中,如何删除重复的节点?

    我的思路:捣鼓了很久,都没有做出来,终于用了必杀技:位图法。记录每一个节点的特征向量,做好统计,然后再遍历统计结果,重新创建链表。时间复杂度是O(n),空间复杂度也是O(n)。

    书本的方法:直接删除。

我的代码

public class Solution {
    public ListNode deleteDuplication(ListNode pHead)
    {
		if(pHead == null||pHead.next == null){
            return pHead;
        }
        ListNode node = pHead;
        int length = 0;
        while(node.next != null){
            length ++;
            node = node.next;
        }
        int[] record = new int[100];
        node = pHead;
        while(node != null){
            record[node.val]++;
            node = node.next;
        }
        ListNode soon = new ListNode(0);
        ListNode result = soon;
        for(int i=0;i




你可能感兴趣的:(《剑指offer》)