面试算法题:链表的倒转

具体的代码调试和讲解,请参看视频:
如何进入google,算法面试技能全面提升指南

在算法面试中,链表出现的频率相当之高,一是因为链表是数据结构的基础,很多更复杂的高层数据结构的设计大多基于链表之上。其次,链表可以实现多种变化,因此使用链表来考察候选人,既能考察其技术基本功是否扎实,同时又能检验对方的思维灵敏性,因此,链表作为算法面试的常用手段也就不足为奇了。

有一道链表面试题,在我的面试经历中出现过很多次,也被很多知名的软件巨头用来招过人,虽然我遇见多次,但每次做该题的时候,总是不能解答到点子上。因此也无法在面试中得到高分,因此我觉得有必要把这道题拿出来进行详细的分析,让大家不要像我一样在面试中错失良机。

题目是这样的,给定一个链表,让你把链表实现反转。例如

1 -> 2 -> 3 -> 4 -> 5

反转成以下情形:

5 -> 4 ->3 -> 2 -> 1.

这道题初看起来似乎很简单,我当时的做法是这样的,想必有不少人的想法跟我一样,假定链表的数据结构如下:

class Node {
    int val;
    Node next;
}

我当时的想法是依赖三个指针来实现反转操作,指针p1指向第一个节点,指针p2指向第二个节点, 指针p3指向第三个节点,把p2.next 指向 p1, 然后p1 挪到p2, p2挪到p3, p3 挪到它的下一个节点,也就是:

 p1 = node1
 p2 = node1.next;
 p3 = p2.next

 p2.next = p1;
 p2 = p3;
 p1 = p2;
 p3 = p3.next;

上面的操作一直进行,知道遍历完整个链表为止。上面的办法是可行的,只不过答不到点上,面试官想看的不是这个做法。况且上面做法复杂,要考虑很多情况,例如要判断指针是否为空,要判断链表是否有三个以上的节点等等。我每次遇到这道题,都采用上面的做法,做完了都以为自己答的好,但面试结果总不是很理想,后来想到更好的解决办法后才知道面试不好的原因。

这道题其实有更简单,更巧妙的做法,假定链表已经转变为以下情况:

1 -> 2 <- 3 <- 4 <- 5

也就是从第一个结果开始,后面的节点已经导致完毕,接下来我们要做的,就是简单的一步,只要把节点1和2之间的指向关系倒转一下就可以了。于是这样就能形成一种递归的思路,如果我要导致的链表,有n 个节点,那么如果第一个节点后面的 n-1 个节点已经正确倒转了的话,我只要处理第一和第二个节点的指向关系就可以了。要使得后n-1个节点正确导致,那么先使得后n-2个节点正确导致,于是就这么递归下去,最后只剩下一个节点时,什么都不用做就已经倒转好了,这种思路简单明了,不需要像第一种一样考虑各种边界条件,这种做法才是面试官真正想要的,我们看看代码:

public class Node {
    public int val;
    Node next;
}


public class ListUtility {
    Node createList(int nodeNum) {
        if (nodeNum <= 0) {
            return null;
        }
        
        Node head = null;
        int val = 0;
        Node node = null;
        
        while (nodeNum > 0) {
            
            if (head == null) {
                head = new Node();
                head.val = val;
                node = head;
            } else {
                node.next = new Node();
                node = node.next;
                node.val = val;
                node.next = null;
            }
            
            val++;
            nodeNum--;
        }
        
        return head;
    }
    
    public void printList(Node head) {
        while (head != null) {
            System.out.print(head.val + " -> ");
            head = head.next;
        }
        
        System.out.println("null");
    }
}

Node 仅仅用来表示一个链表节点,ListUtility的作用是生成一个用于操作的链表,同时当给定链表头节点时,把链表打印出来。真正实现链表倒转作用的是下面代码:


public class ListReverse {
    private Node head;
    private Node newHead;
    
    public ListReverse(Node head) {
        this.head = head;
    }
    
    private Node recursiveReverse(Node node) {
        if (node == null || node.next == null) {
            newHead = node;
            return node;
        }
        
        Node head = recursiveReverse(node.next);
        head.next = node;
        node.next = null;
        
        return node;
    }
    
    
    public Node getReverseList() {
         recursiveReverse(head);
         return newHead;
    }
}

recursiveReverse做的就是递归性的去反转链表,如果当前只有一个节点,那么链表就已经反转完毕,如果不止一个节点,那么先把当前节点后面的链表反转,然后改变当前节点后下一个节点的指向关系,原来是当前节点的next指针指向下个节点,现在改成下个节点的next指针指向当前节点。

上面代码实现简洁,不用像开始使用三个指针时那样去考虑各种特色情况。由于反转过程只需遍历一次链表的所有节点,因此算法的复杂度是O(N).

我们看看运行结果:


public class LinkList {
    public static void main(String[] args) {
        ListUtility util = new ListUtility();
        Node head = util.createList(10);
        util.printList(head);
        
        ListReverse reverse = new ListReverse(head);
        util.printList(reverse.getReverseList());
    }
}

上面代码,先是生成含有10个节点的队列,并打印出来,然后把队列反转后再次打印出来,运行后结果如下:

0 -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> null
9 -> 8 -> 7 -> 6 -> 5 -> 4 -> 3 -> 2 -> 1 -> 0 -> null

通过运行结果可知,我们的算法设计是正确的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 一、 C/C++程序基础 面试例题1——分析代码写输出(一般赋值语句的概念和方法)。 面试例题2—...
    LuckTime阅读 6,128评论 2 42
  • 转载请注明出处:http://www.jianshu.com/p/c65d9d753c31 在上一篇博客《数据结构...
    Alent阅读 8,823评论 4 74
  • 1.把二元查找树转变成排序的双向链表 题目: 输入一棵二元查找树,将该二元查找树转换成一个排序的双向链表。 要求不...
    曲终人散Li阅读 8,631评论 0 19
  • 更详细的讲解请参看视频:如何进入google,算法面试技能全面提升指南 在有关链表的面试算法题中,检测链表是否有环...
    望月从良阅读 9,276评论 0 7
  • 我最近在学车,考试前经常听教练说一句话“平时练练好,考试只要运气不是特别差都能过,不用担心。” 我以前仅仅只是觉得...
    蚂蚁说说阅读 1,592评论 0 0