前言
复盘一下最近遇到的面试算法题,本篇主要介绍的是链表反转的3种解法和思路,其实有在LeetCode或牛客网上刷题的也知道 链表反转的面试题频率非常高,是必会的一道算法题。
先定义一个单向链表,代码如下:
/**
* 链表节点
*/
private static class Node {
int data;
Node next;
Node(int data) {
this.data = data;
}
}
递归解题法
先上代码,代码如下:
/**
* 递归解题法(链表反转)
* @param head
* @return
*/
public static Node recursion(Node head) {
//在原链表上找到最后一个节点,然后就可以开始往前递归反转
if (head == null || head.next == null){
return head;
}
//下一个节点
Node temp = head.next;
//递归 反转
Node newHead = recursion(head.next);
temp.next = head;
head.next = null;
return newHead;
}
递归法是从最后一个Node开始,在弹栈的过程(递归其实就是栈)中将指针顺序置换的,具体过程如下图所示:
实现代码非常简练,如果没有理解的可以去自己debug模式调试一下,该方式的实际的时间复杂度是 O(n2) ,空间复杂度是O(1),效率较低。
面试官:这是一种解法,请问如何才能更高效呢?
栈特性解题法
递归方式的反转说白也就是栈的思想,现在直接用栈来实现一个高效的链表反转,代码如下:
/**
* 栈特性解法 (链表反转)
* @param node
* @return
*/
public static Stack<Integer> reverserLinkedList(Node node) {
//栈
Stack<Integer> nodeStack = new Stack<>();
//存入栈中,模拟递归开始的栈状态
while (node != null) {
nodeStack.push(node.data);
node = node.next;
}
return nodeStack;
}
代码非常简单,直接利用栈先进后出的特性就ok了,具体过程如下:
总结一下该解法的时间复杂度为 O(n),空间复杂度为O(n),这也是典型用空间来换时间的思路。
面试官:这种解法,时间上已经是最优的,请问如何在空间上优化呢?
双指针解题法
但凡链表相关的面试题,大多数都可以用指针法来解题,指针法就是在链表遍历的过程中将指针顺序置换具体代码如下:
/**
* 双指针解法 (链表反转)
* @param node
* @return
*/
public static Node reverse(Node node) {
//先前结点
Node pre = null;
//临时变量 下一个节点
Node next = null;
//遍历原链表
while (node != null) {
//临时记录原链表下一个节点
next = node.next;
//链表的下一个指向为 pre
node.next = pre;
//记录当前节点信息
pre = node;
//将原链表变为 next 下次遍历从第下个节点开始
node = next;
}
return pre;
}
具体解题思路过程如下:
总结一下该解法的时间复杂度为 O(n),空间复杂度为O(1),指针不算占有空间。
总结
理解算法面试题的思路才是正确的刷题方式,常常有小伙伴们组队刷题,但是总会有人抱怨刷题过几天后就忘记了,那是因为你是为了刷题而刷题(仅仅只是当做面试的八股文)根本没有思考,要多思考才能正真掌握它,思考后甚至可以做到举一反三且可以用到实际项目中,这个才是我们刷题的根本目的。