这周盘点一下面试中最容易被问到的链表类算法题,可能下一次面试中就会出现这些题目和技巧哦。前端
首先,链表是一种常见的数据结构,常见的有单链表、双向链表等等。
拿单链表来举例,对于每个节点可使用下面的数据结构表示:node
struct ListNode { val: any; // 节点的值 next: ListNode; // 该节点指向的下一个节点 }
下图能够简单的描述一个链表的结构
对于链表来讲,必定要掌握的操做就是添加节点和删除节点,由于这是全部技巧的基础。面试
若是要在下图中删除2这个节点,就能够进行以下操做:算法
pre.next = cur.next; cur.next = null;
由于须要遍历链表找到pre和cur,因此删除操做的时间复杂度是O(N),空间复杂度是O(1)数据结构
若是要在下图中添加2这个节点,就能够进行以下操做this
next = pre.next; pre.next = cur; cur.next = next;
添加新节点的时间复杂度是O(1),空间复杂度是O(1)spa
LeetCode.206,难度简单指针
反转一个单链表。code
示例:输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULLblog
这道题目也算是链表类题目的老江湖了,印象中从校招一直考到社招,出现频率之高使人咋舌。
对于例子1->2->3->4->5->NULL
来讲,遍历一遍链表,把每一个节点的next属性指向它的前一个节点便可,以下图所示:
对于每个节点来讲,须要知道它的前一个节点pre是谁,也须要知道它的下一个节点是谁(维持链表的遍历);下面我给出一个非递归的方法,固然也递归的方法,读者感兴趣能够自行实现一下。
/** * Definition for singly-linked list. * function ListNode(val) { * this.val = val; * this.next = null; * } */ /** * @param {ListNode} head * @return {ListNode} */ var reverseList = function(head) { if(head === null || head.next === null) return head; let pre = null, cur = head; while(cur !== null) { const next = cur.next; cur.next = pre; pre = cur; cur = next; } return pre; };
给定一个链表,返回链表开始入环的第一个节点。 若是链表无环,则返回 null。
为了表示给定链表中的环,咱们使用整数 pos 来表示链表尾链接到链表中的位置(索引从 0 开始)。 若是 pos 是 -1,则在该链表中没有环。
说明:不容许修改给定的链表。
示例 1:输入:head = [3,2,0,-4], pos = 1
输出:tail connects to node index 1
解释:链表中有一个环,其尾部链接到第二个节点。
示例 2:输入:head = [1,2], pos = 0
输出:tail connects to node index 0
解释:链表中有一个环,其尾部链接到第一个节点。
示例 3:输入:head = [1], pos = -1
输出:no cycle
解释:链表中没有环。
这也是一道经典的题目,可使用快慢指针的办法来解决之,代码以下所示;
那么为何使用快慢指针就能够检测出链表是否有环而且找到第一个入环节点呢?证实以下:
如图,设整个链表长度为L,环长度为R,且距离具备方向性,例如CB是C点到B点的距离,BC是B点到C点的距离,CB!=BC。当证实有环时,fast和slow都顺时针到了B点,则此时:
slow走的距离:AC+CB
fast走的距离:AC+k*R+CB(k=0,1,2...)
因为fast每次走2个节点,slow每次走1个节点,因此:
2(AC+CB) = AC+k*R+CB
AC+CB = k*R
AC+CB = (k-1)*R+R
AC = (k-1)*R+R-CB
AC = (k-1)*R+BC
从最终的表达式能够看出来,AC的距离等于绕环若干圈后再加上BC的距离,也就是说慢指针从A点出发以速度1前进、快指针从B点出发以速度1前进,则慢指针到C点时,快指针也必然到了。
/** * Definition for singly-linked list. * function ListNode(val) { * this.val = val; * this.next = null; * } */ /** * @param {ListNode} head * @return {ListNode} */ var detectCycle = function(pHead) { if(pHead === null || pHead.next === null || pHead.next.next === null) return null; var fast = pHead; var slow = pHead; while(fast.next !==null && fast.next.next !== null) { slow = slow.next; fast = fast.next.next; if(slow === fast) break; } if(fast === null || fast.next === null || fast.next.next === null) return null; // 有环,slow从新回到链表头 slow = pHead; // slow和fast从新相遇时,相遇节点就是入环节点 while(slow !== fast) { slow = slow.next; fast = fast.next; } return slow; };
LeetCode.328,难度中等
给定一个单链表,把全部的奇数节点和偶数节点分别排在一块儿。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。
请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。
示例 1:
输入: 1->2->3->4->5->NULL
输出: 1->3->5->2->4->NULL
示例 2:
输入: 2->1->3->5->6->4->7->NULL
输出: 2->3->6->7->1->5->4->NULL
说明:
应当保持奇数节点和偶数节点的相对顺序。
链表的第一个节点视为奇数节点,第二个节点视为偶数节点,以此类推。
以前说过,增删节点是链表的重要基础技巧,本道题就体现的很深入。
从题意得知,奇数节点都在前面,偶数节点都在后面,即把1->2->3->4->5->NULL
变成1->3->5->2->4->NULL
,以下图所示:
能够看到问题的关键是奇数节点和偶数节点交替抽出成两条独立的链表,最终再合成一条新的链表。
/** * Definition for singly-linked list. * function ListNode(val) { * this.val = val; * this.next = null; * } */ /** * @param {ListNode} head * @return {ListNode} */ var oddEvenList = function(head) { if(head === null || head.next === null || head.next.next === null) return head; let cur1 = head, cur2 = head.next; let evenHead = head.next; while(cur1.next && cur2.next) { cur1.next = cur2.next; cur1 = cur1.next; cur2.next = cur1.next; cur2 = cur2.next; } cur1.next = evenHead; return head; };
欢迎关注前端亚古兽(fe-yagushou),更多前端以及互联网周边知识推送。