目录

1. 删除重复节点

例1. 删除链表中所有重复节点

例2. 删除链表中重复节点,重复元素只留一个

2. 反转链表

例3. 反转整个链表(Reverse linked list)(基础积木块)

例4. 反转链表中的指定区间(Reverse linked list II)

3. 对链表进行重新排序

例5. 分隔链表_小于指定值的放前面,大的放后面

例6. 查找链表的中间节点(基础积木块)

例7. 合并两个有序链表(基础积木块)

例8. 对链表进行排序(Sort list)(组合题)

例9. 前后交叉重新排序链表(Reorder list)(组合题)

例10. 判断链表中是否有环(基础积木块)

找出环形链表的入口节点

例11. 指定位置翻转链表(Rotate list)

例12. 合并k个排序链表

4.链表的复制

例13. 复杂链表(带随机指针)的复制

5. 链表的转换

例14. 将排序链表转成平衡二叉树


本文记录面试题中针对链表的常见操作和考法。代码均在 lintcode 或者 leetcode 上测试通过。

1. 删除重复节点

例1. 删除链表中所有重复节点

描述:存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除链表中所有存在数字重复情况的节点,只保留原始链表中 没有重复出现 的数字。返回同样按升序排列的结果链表。(来源:lintcode 113 · 删除排序链表中的重复数字(二) = leetcode82. 删除排序链表中的重复元素 II (中等))

示例 1:

输入:head = [1,2,3,3,4,4,5]    输出:[1,2,5]

示例 2:

输入:head = [1,1,1,2,3]   输出:[2,3]

代码

因为删除的是所有重复的节点,只要重复全都删除。所有链表的头可能会发生变化。所以为了方便起见,我们在head前再加一个节点(命名为dummy),然后将原来的head节点当做跑腿的(用于遍历链表),在先将head初始化成dummy之后(head = dummy)比较 head->next 和 head->next->next的值,这时即使删除了head->next,我们还有head可以进行继续遍历。在删除节点过程中,真正的头节点始终是dummy->next。

/*** Definition for singly-linked list.* struct ListNode {*     int val;*     ListNode *next;*     ListNode() : val(0), next(nullptr) {}*     ListNode(int x) : val(x), next(nullptr) {}*     ListNode(int x, ListNode *next) : val(x), next(next) {}* };*/
class Solution {
public:ListNode* deleteDuplicates(ListNode* head) {if (head == nullptr || head->next == nullptr) { //处理0个或1个节点的情况return head;}ListNode * dummy = new ListNode(0);dummy->next = head; //链表前加一个哑节点head = dummy;       //原来的头结点指向新的头结点,//head此时是一个跑腿的(用于遍历的指针)while (head->next != nullptr && head->next->next != nullptr) {if (head->next->val == head->next->next->val) {int val = head->next->val;while (head->next != nullptr && head->next->val == val) {ListNode * tmp = head->next;   // 先拿到待删除节点head->next = head->next->next; // 覆盖式删除重复的节点,(同时自动指向下一个节点)delete tmp;                    // 删除重复节点}} else {head = head->next;                  //不重复,指向下一个}}ListNode* result= dummy->next;delete dummy;return result;}
};

拓展1:第二个while循环中,若只是用一句 head->next = head->next->next;进行的删除会造成内存泄漏,这个使用java语言时因存在自动回收机制不必考虑,但使用c++就要自己回收内存。所以先把要待删除节点拿出来,然后通过head的next链接到下下个节点,然后删除重复的节点。

第二个while中也可以使用一句话完成该功能:delete std::exchange(head->next, head->next->next);

拓展2:使用delete操作,是比较耗费时间的,所以,上述dummy也可以定义成 ListNode 类型,而不是 ListNode* 类型。改变的地方不多,如下所示。(下面的例题相同)

class Solution {
public:ListNode* deleteDuplicates(ListNode* head) {if (head == nullptr || head->next == nullptr) { //处理0个或1个节点的情况return head;}ListNode dummy;dummy.next = head;head = &dummy; // ...中间while一样// ListNode* result= dummy->next;    // 不再需要// delete dummy;                     // 不再需要return dummy.next;}
};

例2. 删除链表中重复节点,重复元素只留一个

描述:存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除所有重复的元素,使每个元素 只出现一次 。返回同样按升序排列的结果链表。

(来源: leetcode 83. 删除排序链表中的重复元素 (简单))

示例 1:

输入:head = [1,1,2,3,3]   输出:[1,2,3]

代码1

例1代码 第二个while这句 (while (head->next != nullptr && head->next->val == val)) 改成:

while (head->next != nullptr && head->next->next != nullptr && head->next->val == head->next->next->val)

以删除重复节点中的第一个即可。

代码2

因为重复节点还要留1个,所以head头节点时不会变的,所以不用定义dummy节点。其它同例1,定义cur节点作为遍历指针,比较 curcur->next 的值,若值相等则循环删除 cur->next (重复节点中的第二个)。注意:在对某节点取值(->val)时,务必切记要判断该节点是否为空

class Solution {
public:ListNode* deleteDuplicates(ListNode* head) {if (head == nullptr || head->next == nullptr) { //当只有0个或1个节点时return head;}ListNode *cur = head;                            //定义用于遍历的指针while (cur != nullptr && cur->next != nullptr) { // while内部逻辑等同于上题(删除所有重复节点), 只是 少了一层 ->nextif (cur->val == cur->next->val) {int val = cur->val;while (cur->next != nullptr && cur->next->val == val) { //特别注意:取val前一定要判断自己是否为空!!!ListNode* tmp = cur->next;   // 取出待删除节点cur->next = cur->next->next; // 当前指向下下个节点delete tmp;                  // 删除重复节点}} else {head = head->next;}}return head;}
};

2. 反转链表

例3. 反转整个链表(Reverse linked list)(基础积木块)

描述:定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。(来源:leetcode 206. 反转链表)
示例:


输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL

代码

反转链表,必须倒背如流。很基础的操作。这里需要定义一个前置节点pre, 然后循环遍历  pre 和当前节点(head), 在倒序连接前先将下一个节点存入tmp节点中。最终返回的是pre节点

class Solution {
public:ListNode* reverseList(ListNode* head) {ListNode * pre = null;   // 定义一个尾结点,玩的就是 pre 和 head 两个节点while (head != nullptr) {ListNode * tmp = head->next;    // 对当前节点进行颠倒前,先把下一个节点存起来,别丢了。head->next = pre;               // 倒着连起来pre = head;                     // 这两个指针均后移一位head = tmp;}return pre;                         // 注意返回值。这跟上述while中的条件有关。}
};

例4. 反转链表中的指定区间(Reverse linked list II)

描述:给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。

(来源:leetcode 92. 反转链表 II = lintcode 36 · 翻转链表(二))

示例 1:


输入:head = [1,2,3,4,5], left = 2, right = 4   输出:[1,4,3,2,5]

示例 2:

输入:head = [5], left = 1, right = 1   输出:[5]

代码

核心反转部分同反转整个链表,这里的拓展部分在于,要定义4个点preNode 反转开始之前的节点,lNode反转开始的左侧节点,rNode反转结束的右侧节点,postNode 反转结束节点后的第一个节点,以便将反转后的节点跟原来的整个链表中的其他节点连接起来。

class Solution {
public:ListNode* reverseBetween(ListNode* head, int left, int right) {if (head == nullptr || left >= right) {return head;}ListNode * dummy = new ListNode(0); //因为头结点可能变换,所以就定义dummy结点dummy->next = head;head = dummy;                   //head指向新的头结点(dummy)for (int i = 1; i < left; i++) { // 特别注意: i从1开始,目的是找到第m个节点的前一个节点 //找到第left个node之前的nodeif (head == nullptr) {  // 取节点属性前,先判断是否为空!return nullptr; }head = head->next;}ListNode * preNode = head;ListNode * lNode = head->next; //左节点ListNode * rNode = head->next; //右节点ListNode * postNode = rNode->next;for (int i = left; i < right; i++) {if (postNode == nullptr) {  // 比如:只有两个节点的情况return nullptr; }ListNode * tmp = postNode->next; //反转连接关系postNode->next = rNode;rNode = postNode;                //指向下一对反转节点postNode = tmp;}preNode->next = rNode;              //将反转的链表接上lNode->next = postNode;ListNode * result = dummy->next;delete dummy;return result;        }
};

3. 对链表进行重新排序

例5. 分隔链表_小于指定值的放前面,大的放后面

描述:给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。你应当 保留 两个分区中每个节点的初始相对位置。

(来源:leetcode 86. 分隔链表 = lintcode 96 · 链表划分)

示例 1:


输入:head = [1,4,3,2,5,2], x = 3
输出:[1,2,2,4,3,5]

代码

这道题对指针的理解很有帮助。定义2个dummy节点(rightDummy leftDummy),分别作为两个子链表的头部,定义两个游走节点指针(left right)分别去连接指定的值。最后将两个子链表连接起来。清理内存。返回。

/*** Definition for singly-linked list.* struct ListNode {*     int val;*     ListNode *next;*     ListNode() : val(0), next(nullptr) {}*     ListNode(int x) : val(x), next(nullptr) {}*     ListNode(int x, ListNode *next) : val(x), next(next) {}* };*/
class Solution {
public:ListNode* partition(ListNode* head, int x) {if (head == nullptr) {return nullptr;}ListNode * leftDummy = new ListNode(0);ListNode * rightDummy = new ListNode(0);ListNode * left = leftDummy;    //定义两个用于遍历的指针(跑腿的)后面即使改变了left right的值,两个分链表的头leftDummy rightDummy还在。ListNode * right = rightDummy;while (head != nullptr) {if(head->val < x) {left->next = head;  //将当前节点连接到左半部分left = head;        //指针指向下一个起始位置} else {right->next = head; //将当前节点连接到右半部分right = head;       //指针指向下一个起始位置}head = head->next;      //处理原始链表中的下一个节点}right->next = nullptr;          // 结尾设为空left->next = rightDummy->next;  // 连接两个子链表ListNode * result = leftDummy->next; //清理内存delete leftDummy;delete rightDummy;return result;}
};

例6. 查找链表的中间节点(基础积木块)

描述:给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。(来源:leetcode 876. 链表的中间结点 简单)
样例 1:
输入:1->2->3->4->5->null   输出:3->4->5->null
样例 2:
输入:1->2->3->4->5->6->null   输出:4->5->6->null

代码

倒背如流!基本操作,是其他复杂题的基础。采用先统计个数n,再遍历到n/2的方式也是可以的,但需要消耗O(n)空间。而采用快慢指针的方式,则可以节省掉O(n)的空间。即快慢指针初始化指向head, 然后快指针一次走2步,慢指针一次走1步,到快指针走到链表结尾(nlllptr)时,慢指针指向的即为链表的中间节点。特别注意这里的简洁写法 while (fast != nullptr && fast->next != nullptr) {...}

(类似的采用快慢指针的例题还有 Remove Nth Node From End of List  、Linked List Cycle I, II 、Rotate List 等)

class Solution {
public:ListNode* middleNode(ListNode* head) {if (head == nullptr || head->next == nullptr) { // 先处理下0个或1个节点的情况 //这里根据下面的处理,其实可以省略。return head;}ListNode * slow = head;ListNode * fast = head; //当2个中间节点要求返回第1个中节点时,fast初始化为 head->nextwhile (fast != nullptr && fast->next != nullptr) { //注意:这2个条件可以一起写,使用了&&的屏蔽功能(a&&b ,a为false时直接返回不再判断b条件)fast = fast->next->next;slow = slow->next;}return slow;}
};

例7. 合并两个有序链表(基础积木块)

描述:将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 (来源:leetcode 21. 合并两个有序链表)

示例 1: 输入:l1 = [1,2,4], l2 = [1,3,4]   输出:[1,1,2,3,4,4]

代码

倒背如流!基本操作,是其他复杂题的基础。定义一个dummy节点,依次添加上去节点。别忘了还有一个到结尾,另一个还有剩余时,要将剩余的节点添加到尾部(tail) (拓展,若是合并两个无序链表呢:先排序(下一个例题)再合并)

class Solution {
public:ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {ListNode * dummy = new ListNode(0);ListNode * tail = dummy;            //tail是个跑腿的(用于指向待处理链表的最后一个元素)while(list1 != nullptr && list2 != nullptr) {if (list1->val < list2->val) {tail->next = list1;list1 = list1->next; } else {tail->next = list2;list2 = list2->next;}tail = tail->next;      //指向此时链表中的最后一个节点}if (list1 != nullptr) {tail->next = list1;}if (list2 != nullptr) {tail->next = list2;}ListNode * result = dummy->next;delete dummy;return result;}
};

例8. 对链表进行排序(Sort list)(组合题)

描述:给定链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。要求时间复杂度 O(NlogN). (来源:leetcode 剑指 Offer II 077. 链表排序)

示例 1:

输入:head = [4,2,1,3]   输出:[1,2,3,4]

代码(归并排序)

本题要求使用 O(NlogN) 对链表进行排序。对于排序算法 O(NlogN)时间复杂度的方法常用的有2种,快速排序归并排序小型公司面试常考,必须掌握的基本算法!)这里采用归并排序方法。即先找到链表的中点,然后分成两部分进行排序然后进行合并;对于其中的一半,再分成两部分进行排序,再进行合并。(是否看到了分治的影子)所以这道题 = 查找链表的中间节点 + 分治法(左右分别排序) + 合并两个有序链表 。特别注意:这里查找链表的中间节点时,若偶数个节点(即中间节点有2个),则应该(此题的排序方法是“必须”)返回第1个中间节点!!即 fast指针 初始化是 fast = head->next;

class Solution {
public:ListNode* sortList(ListNode* head) {if (head == nullptr || head->next == nullptr) {return head;}ListNode * middle = getMiddle(head); ListNode * right = sortList(middle->next); //注意:这里使用的是middle的后一个节点作为后半部分middle->next = nullptr;ListNode * left = sortList(head);return mergeList(left, right); //合并两个有序链表(因为经过sortList子链表已经有序)}ListNode * getMiddle(ListNode * head) {ListNode * slow = head;ListNode * fast = head->next; //特别注意:对应偶数个节点,这里返回的是第1个中间节点while (fast != nullptr && fast->next != nullptr) {fast = fast->next->next;slow = slow->next;}return slow;}ListNode * mergeList(ListNode * list1, ListNode * list2) {// 参考例7,一模一样。合并两个有序链表}
};

例9. 前后交叉重新排序链表(Reorder list)(组合题)

描述: 给定一个单链表L: L0→L1→…→Ln-1→Ln, 重新排列后为:L0→Ln→L1→Ln-1→L2→Ln-2→…  必须在不改变节点值的情况下进行原地操作。(来源:lintcode 99 · 重排链表)

样例 1:


输入:list = 1->2->3->4->null
输出:1->4->2->3->null
样例 2:


输入:list = 1->2->3->4->5->null
输出:1->5->2->4->3->null

代码

本题是一个组合题 = 获取链表中点 + 反转链表 + 合并两个链表。其他指定注意的是:获取中点依然采用获取第一个中点(对于偶数个节点时); 合并链表时交叉合并,可采用对2趋于( index%2  )的方式增加代码的可读性。

    void reorderList(ListNode * head) {// write your code hereif (head == nullptr || head->next == nullptr) { //不要忘了!不要忘了!return;}ListNode * middle = getMiddle(head);ListNode * tail = middle->next;tail = reverse(tail);middle->next = nullptr; // 不要少了!head = mergeList(head, tail); //交叉合并链表,不论大小。return;}ListNode * getMiddle(ListNode * head) {if (head == nullptr || head->next == nullptr) {return head;}ListNode * slow = head;ListNode * fast = head->next; //若是2个中间节点,返回第一个中间节点while (fast != nullptr && fast->next != nullptr) {fast = fast->next->next;slow = slow->next;}return slow;}ListNode * reverse(ListNode * head) {ListNode * pre = nullptr;while (head != nullptr) {ListNode * tmp = head->next;head->next = pre;pre = head;head = tmp;}return pre;}ListNode * mergeList(ListNode * list1, ListNode * list2) {ListNode * dummy = new ListNode(0);ListNode * tail = dummy;int index = 0;while (list1 != nullptr && list2 != nullptr) { //合并时,按照题目要求list1在前。if (index % 2 == 0) { //注意:这里的处理挺好玩!直接写6行代码也可以,不过没有这样更可读tail->next = list1;list1 = list1->next;} else {tail->next = list2;list2 = list2->next;}tail = tail->next;++index;}if (list1 != nullptr) {tail->next = list1;}if (list2 != nullptr) {tail->next = list2;}ListNode * result = dummy->next;delete dummy;return result;}

例10. 判断链表中是否有环(基础积木块)

描述:给定一个链表,判断它是否有环。(来源:lintcode 102 · 带环链表 = leetcode 141. 环形链表 简单)

代码

快慢指针法。若有环,快指针必然有一天会追上慢指针。若无环,快指针必然会走到终点。注意 while循环条件是 while(fast != slow) {...}

class Solution {
public:bool hasCycle(ListNode *head) {if (head == nullptr || head->next == nullptr) {return false;}ListNode * slow = head;ListNode * fast = head->next;while (fast != slow) {      //注意:循环条件:只有快慢指针不相等,就有一直循环if (fast == nullptr || fast->next == nullptr) { //若无环,必然会走到终点,返回falsereturn false;}fast = fast->next->next;                        //一路追赶slow = slow->next;}return true;}
};

找出环形链表的入口节点

描述: 给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。(来源:leetcode 142. 环形链表 II)

代码

判断环形链表时,两个相遇的节点必定在环内,则就可以统计环的长度n,再定义一组快慢指针,快指针先走n步,然后两个指针一起走,则当两者再次相遇时的节点,即为入口节点。(为什么?数学证明是比较复杂的,面试问到该题,可以要求面试官换一个,因为这种题刷过就会,没刷过是不可能在现场证明白的)(代码略,参考 剑指 Offer II 022. 链表中环的入口节点)

例11. 指定位置翻转链表(Rotate list)

描述:给定一个链表,旋转链表,使得每个节点向右移动k个位置,其中k是一个非负数。0 <= k <= 2 ^109.(来源:lintcode 170 · 旋转链表 = leetcode 61. 旋转链表)
样例 1:


输入:1->2->3->4->5  k = 2
输出:4->5->1->2->3
样例 2:
输入:3->2->1  k = 1
输出:1->3->2

代码

本题 = 快慢指针 +  反转链表中个别节点连接顺序。先将快慢指针同时指向head, 对右移次数k对链表长度取余 n = k%len; 快指针先走n步,然后 快慢指针一起走,当快指针 fast 指向原链表最后一个节点时,慢指针 slow 指向前半部分的最后一个节点。然后将前半部分连接到后半部分即可。注意:前半部分的最后一个节点的next要置为nullptr. 另外,先处理下整除的情况(n==0时),直接返回原链表。这样能简化思考过程。

class Solution {
public:ListNode * rotateRight(ListNode * head, int k) {if (head == nullptr || head->next == nullptr) {return head;}int len = getLength(head);int n = k % len; if (n == 0) {   //  特别注意:先处理特情况,后面的代码会好写一点。return head;}ListNode * slow = head;ListNode * fast = head; //fastnode先走n步for (int i = 0; i < n; i++) {fast = fast->next;}while (fast->next != nullptr) { //一起向后走,直到fast指向最后一个节点!此时,slow指向前半段的最后一个节点fast = fast->next;slow = slow->next;}ListNode *newHead = slow->next; //新的头slow->next = nullptr;           //新的尾fast->next = head;              //将前半段连接到后半段后面return newHead;}int getLength(ListNode * head) {int len = 0;while (head != nullptr){len++;head = head->next;}return len;}
};

例12. 合并k个排序链表

描述:合并k个排序链表,并且返回合并后的排序链表。尝试分析和描述其复杂度。

(来源:lintcode 104 · 合并k个排序链表 中等= leetcode 23. 合并K个升序链表 =leetcode  剑指 Offer II 078. 合并排序链表 困难)
样例 1:
输入:lists = [2->4->null,null,-1->null]
输出:-1->2->4->null
样例 2:
输入:lists = [2->6->null,5->null,7->null]
输出:2->5->6->7->null

代码

合并k个有序的链表 = 合并两个有序链表 + 分治法。over。

class Solution {
public:/*** @param lists: a list of ListNode* @return: The head of one sorted list.*/ListNode *mergeKLists(vector<ListNode *> &lists) {// write your code hereif (lists.size() == 0) {return nullptr;}return mergeListCore(lists, 0, lists.size()-1); //传入的是开始和结尾链表的索引值}// 分治法 合并多个链表ListNode * mergeListCore(vector<ListNode*> lists, int start, int end) {if (start == end) { //特别注意:返回条件是开始和结尾都指向1个链表时return lists[start];}int mid = start + (end - start) / 2;  //除以2等于右移1位! >>1ListNode * left  = mergeListCore(lists, start, mid);ListNode * right = mergeListCore(lists, mid+1, end);return mergeTwoLists(left, right);}//合并两个有序链表ListNode * mergeTwoLists(ListNode * list1, ListNode * list2) {//参考例7 一模一样。}
};

4.链表的复制

例13. 复杂链表(带随机指针)的复制

描述:给出一个链表,每个节点包含一个额外增加的随机指针,其可以指向链表中的任何节点或空的节点。返回其链表的深度拷贝。挑战: 可否使用O(1)的空间。 (来源:lintcode 105 · 复制带随机指针的链表 = leetcode 剑指 Offer 35. 复杂链表的复制 = leetcode 138. 复制带随机指针的链表)

代码

这是一道组合题,等于链表节点的插入 + 链表指定节点的遍历 + 链表的拆分。思路:因为要求O(1)的空间复杂度,所以不能使用vector hash stac等数据结构,只能在原始的链表上进行操作。同时应该注意到,链表的深度拷贝之后原始的链表不应该被改变。综上,问题可以分为3步如下:1)赋值每一个节点插入在自己的后面,同时连接上后一个节点;

假设原始链表如下图所示:

第 1 步:复制每一个节点插入在自己的后面

第 2 步:根据原始节点复制random指针;( head->next->random = head->random->next; )

第 3 步:将长链表拆分成2个链表,返回新的链表头指针。(重点记忆!)

/*** Definition for singly-linked list with a random pointer.* struct RandomListNode {*     int label;*     RandomListNode *next, *random;*     RandomListNode(int x) : label(x), next(NULL), random(NULL) {}* };*/
class Solution {
public:/*** @param head: The head of linked list with a random pointer.* @return: A new head of a deep copy of the list.*/RandomListNode *copyRandomList(RandomListNode *head) {if (head == nullptr) {return nullptr;}copyNext(head);copyRandom(head);return splitRandomList(head);}void copyNext(RandomListNode * head) {while (head != nullptr) {RandomListNode * newNode = new RandomListNode(head->label);newNode->next = head->next;head->next = newNode;head = head->next->next;}}void copyRandom(RandomListNode * head) {while (head != nullptr) {if (head->random != nullptr) {  // random可能有,可能没有,所以不能作为while循环条件,要在while中判断。head->next->random = head->random->next;}head = head->next->next;        // 不论是否有random都要向下继续遍历}}RandomListNode * splitRandomList(RandomListNode * head) {RandomListNode * newHead = head->next;    //先把新的链表头节点拿出来while (head != nullptr) {       //使用head去遍历,交叉连接即可! 注意原始的链表要恢复,因为copy链表不应该破坏原链表RandomListNode * tmp = head->next;head->next = tmp->next;if (tmp->next != nullptr) { //不能少!!!tmp->next = tmp->next->next;}head = head->next;}return newHead;}
};

5. 链表的转换

例14. 将排序链表转成平衡二叉树

描述:给定一个单链表,其中的元素按升序排序,将其转换为高度平衡的二叉搜索树。本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。(来源:leetcode 109. 有序链表转换二叉搜索树 = lintcode 106 · 有序链表转换为二叉搜索树)

示例:给定的有序链表: [-10, -3, 0, 5, 9], 一个可能的答案是:[0, -3, 9, -10, null, 5], 它可以表示下面这个高度平衡二叉搜索树:

0
     / \
   -3   9
   /   /
 -10  5

代码1

采用分治法 + 找链表中点的前一个节点。注意,这里找的是链表的中点的前一个节点,这样可以将中点当成是本层的root节点,把中点的后面当成是右子树,中点的前面当成是左子树。找中点的前一个节点的目的是为了更好的分割链表: middleAhead->next = nullptr; 该方法总的时间复杂度是O(NlogN). (分析:T(N) = 2T(N/2) + O(N) 即将T(N)问题转换成2个T(N/2)的问题,而每一步消耗O(N);或者说:因为每次要找中点消耗O(logN)时间,总N个节点,所以...)

/*** Definition for singly-linked list.* struct ListNode {*     int val;*     ListNode *next;*     ListNode() : val(0), next(nullptr) {}*     ListNode(int x) : val(x), next(nullptr) {}*     ListNode(int x, ListNode *next) : val(x), next(next) {}* };*/
/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:TreeNode* sortedListToBST(ListNode* head) {if (head == nullptr) {return nullptr;}ListNode * midAhead = getMiddleAhead(head);  //获取中点之前的节点,以作为根节点if (midAhead == nullptr) {return nullptr;}if (midAhead->next == nullptr) { //特别注意:要处理1个节点的情况TreeNode * root = new TreeNode(midAhead->val);return root;}TreeNode * root = new TreeNode(midAhead->next->val);     //注意根节点是谁TreeNode * right = sortedListToBST(midAhead->next->next);midAhead->next = nullptr;                                // 处理根节点之前的节点TreeNode * left = sortedListToBST(head);root->left = left;root->right = right;return root;}ListNode * getMiddleAhead(ListNode * head) { // 1个节点返回第一个,2个返回第1个,3个返回第1个,4个返回2个,5个返回第2个,6个返回第3个if (head == nullptr || head->next == nullptr ) { //特别注意:要处理1个节点的情况return head;} ListNode * slow = head;ListNode * fast = head->next->next;  //特别注意:获取最中间节点的前一个节点while (fast != nullptr && fast->next != nullptr) {fast = fast->next->next;slow = slow->next;}return slow;}
};

代码2

怎么使用O(N)的时间复杂度完成该题呢?分析上述代码可知,上述代码找中点消耗O(logN)是否可以使用O(1)的时间知道中点呢?答案是可以的。即先计算链表的长度size,然后 左子树遍历size/2, 右子树遍历size-1-size/2 (减1减的是根节点)。这样就可以O(1)时间找到中点,将该题的时间复杂度变成O(N). 代码 = 分治法 + 统计链表长度 (+ 自底向上遍历)。

class Solution {
public:ListNode * current;   //特别注意:这里使用了一个全局的成员变量!!TreeNode* sortedListToBST(ListNode* head) {if (head == nullptr) {return nullptr;}current = head; //初始化当前处理节点int  size = getListSize(head);return sortedListToBSTCore(size);}TreeNode* sortedListToBSTCore(int size) {if (size <= 0) {return nullptr;}TreeNode * left  = sortedListToBSTCore(size / 2);           //前半段TreeNode * root  = new TreeNode(current->val);              //创建根节点current = current->next;   //注意:这里是代码的精髓所在 //有点像是二叉树的中序遍历TreeNode * right = sortedListToBSTCore(size - 1 - size/2);  //后半段root->left  = left;root->right = right;return root;}int getListSize(ListNode * head) { int size = 0;while (head != nullptr) {++size;head = head->next;}return size;}
};

可选:

算法笔记_面试题_19.链表_模板及示例十几道相关推荐

  1. Algorithms_基础数据结构(04)_线性表之链表_单向循环链表约瑟夫环问题

    文章目录 大纲图 链表的经典面试题目 如何设计一个LRU缓存淘汰算法 约瑟夫问题 结构 分析 大纲图 链表的经典面试题目 如何设计一个LRU缓存淘汰算法 tip:单向链表 约瑟夫问题 N个人围成一圈, ...

  2. Algorithms_基础数据结构(02)_线性表之链表_单向链表

    文章目录 大纲图 链表的经典面试题目 如何设计一个LRU缓存淘汰算法 约瑟夫问题 顺序表VS 链表 链表的定义 链表的特点 常见的链表结 单向链表 单向链表的查找 单向链表的插入 头插 尾部插入 中间 ...

  3. Algorithms_基础数据结构(03)_线性表之链表_双向链表

    文章目录 大纲图 双向链表 双向链表的基本结构 双向链表的基本操作 头插 尾插 中间部位插入 删除头部 删除尾部 删除中间位置的数据 查找 更新 Code 总结 大纲图 双向链表 Algorithms ...

  4. 算法笔记_183:历届试题 九宫重排(Java)

    目录 1 问题描述 2 解决方案   1 问题描述 问题描述 如下面第一个图的九宫格中,放着 1~8 的数字卡片,还有一个格子空着.与空格子相邻的格子中的卡片可以移动到空格中.经过若干次移动,可以形成 ...

  5. 算法笔记_172:历届试题 波动数列(Java)

    目录 1 问题描述 2 解决方案   1 问题描述 问题描述 观察这个数列: 1 3 0 2 -1 1 -2 ... 这个数列中后一项总是比前一项增加2或者减少3. 栋栋对这种数列很好奇,他想知道长度 ...

  6. 算法笔记_188:历届试题 危险系数(Java)

    目录 1 问题描述 2 解决方案   1 问题描述 问题描述 抗日战争时期,冀中平原的地道战曾发挥重要作用. 地道的多个站点间有通道连接,形成了庞大的网络.但也有隐患,当敌人发现了某个站点后,其它站点 ...

  7. 【我的算法笔记】后序非递归遍历模板及其应用

    目录 前言 非递归后序遍历算法模板 求树的深度 打印值为x的节点的所有祖先 求节点t,s最近公共祖先节点 输出从每个叶子节点到根节点的逆路径 前言 本篇文章主要介绍非递归后序遍历的算法,并且在这个算法 ...

  8. paddle 图标注_标注外包业务预算评估模板(示例)

    RT, 此处采用纯文本描述,PPT/表格 更佳 评估基准: 以10月项目供应商报价作为基准 标注目标(周边车场采集的): 伪3D(1框+3点) 行人(1框) freespace(1多边) 轮档(1多边 ...

  9. codeup墓地目录(算法笔记习题刷题笔记)

    在线codeup contest 地址:http://codeup.cn/contest.php Contest100000575 - <算法笔记>3.1小节--入门模拟->简单模拟 ...

  10. 算法笔记CodeUp第一至第六章刷题记录

    文章目录 <算法笔记>2.2小节--C/C++快速入门->顺序结构 1.例题1-1-1 按要求输出信息(1) 2.例题1-1-2 按要求输出信息(2) 3.例题1-2-1 求两个整数 ...

最新文章

  1. UTF-8,UTF-16和UTF-32
  2. 转: ubuntu apt-get 与 aptitude 用法与区别
  3. sublime4 安装pretty json 并绑定快捷键
  4. ABAP事物码SFW5里business function前面的双箭头图标是什么含义
  5. esxi 部署模板_vSphere使用模板部署虚拟机
  6. typecho 全站ajax,typecho全站静态化方案
  7. 学习Linux第一次上课笔记
  8. 深入理解JVM之JVM内存区域与内存分配
  9. error: Setup script exited with error: Unable to find vcvarsall.bat
  10. 第一次冲刺个人博客04
  11. 20200719每日一句
  12. 中原地区第一款开源产品SmartAdmin和《Smart前后台代码规范》正式发布
  13. Vue JSON校验格式化编辑框 -- jsoneditor
  14. 用计算机编程解决土方调配,LINGO在土方调配计算问题应用.doc
  15. Ubuntu18.04 iso文件下载地址
  16. Rant ?? 我爱每一片绿叶
  17. QPushButton去掉虚线框(焦点框)
  18. Sigil制作epub,正则表达式的使用
  19. nalu格式annex-B和avcc
  20. Chrome无痕模式

热门文章

  1. zabbix 监控mysql模板_Zabbix监控MYSQL模板-阿里云开发者社区
  2. SqlParameter类——带参数的SQL语句
  3. chrome浏览器再次打开黑屏一段时间
  4. xxx系统的6大质量属性战术
  5. 用Maven新建Web项目时报错
  6. [高中作文赏析]渴望从前
  7. 不规矩的xml与JAVA对象互相转换的小技巧-使用Marshaller
  8. 登陆模块防止恶意用户SQL注入攻击
  9. 非关系型数据库和关系型数据库区别(转载)
  10. C++ main函数的几点细节(转载)