面试算法:Linked List-链 表
来源:互联网 发布:java新闻发布管理系统 编辑:程序博客网 时间:2024/06/05 02:59
很多面试相关的书籍都有链表和字符串操作相关的题,这类问题更考验编程基本功和对基础知识的掌握情况。由于之前很多题目做过就忘,再做时总是磕磕绊绊,说明没有真正消化。于是想到把做过的题整理出来,写上自己的一些做题感悟,多多总结常用的方法和技巧。如果能把问题向别人讲明白,那才说明真的理解了。本文中的部分代码参考了网上一些源码的写法,且在 leetcode 上测试通过。
链表常用方法和技巧
Dummy Node
哑节点的使用针对单链表没有前向指针的问题。保证head
节点不会在删除操作中丢失。当head
有变化(比如被删除或被修改),或者涉及到边界判断时,使用Dummy Node
可以很好的简化代码。Fast and slow pointer
快慢指针的快和慢指的是指针向后移动的步长。每次移动步长大的叫快指针,步长小的即为慢指针。
快慢指针有如下应用:- 寻找未知长度的特殊位置的某个节点,比如中间节点、倒数第
k
个节点。其中,寻找中间节点可以设置快指针*fast
移动速度是慢指针*slow
的两倍,两个指针都从单链表的头节点出发,当*fast
指向尾节点时,*slow
正好走到中间了。寻找倒数第k
个节点时,可以让*fast
指针先出发,移动k-1
步,然后同时移动*fast
和*slow
指针,当*fast
指针移动到尾节点时,*slow
指针正好移动到倒数第k
个节点。 - 判断单链表是否存在环。同样设置两个指针
*fast
、*slow
都指向单链表的头节点,其中*fast
的移动速度是*slow
的两倍。如果*fast = NULL
,说明该单链表以NULL
结尾,不存在环;如果*fast = *slow
,则快指针追上慢指针,说明该链表存在环。
- 寻找未知长度的特殊位置的某个节点,比如中间节点、倒数第
以下各问题会用到下面的单链表定义:
// Definition for singly-Linked Liststruct ListNode { int val; ListNode* next; ListNode(int x) : val(x), next(NULL) {}};
在O(1)时间删除链表节点
题目描述
237. Delete Node in a Linked ListWrite a function to delete a node (except the tail) in a singly linked list, given only access to that node.
Supposed the linked list is 1 -> 2 -> 3 -> 4 and you are given the third node with value 3, the linked list should become 1 -> 2 -> 4 after calling your function.思路
用该节点的后继节点覆盖当前节点,然后删除下一个节点即可。该节点不能是尾节点。C++
void deleteNode (ListNode *cur){ assert(cur != NULL); assert(cur->next != NULL); //不能是尾节点 ListNode* p = cur->next; cur->val = p->val; cur->next = p->next; delete p;}
反转链表
题目描述
206. Reverse Linked ListReverse a singly linked list.
思路
迭代。迭代的本质是遍历,迭代的外部操作是循环。对于每一次的循环,先把当前节点
head
指向直接后继的指针改为指向其直接前驱pre
,为此需先记录其直接后继head->next
的值。然后把pre
的值修改为head
的值,把head
的值修改为记录过的后继值,一步一步迭代下去。递归。类似于栈,遇到终止条件则返回到上一层递归的调用处,然后继续向下执行,执行完成后返回该层的再上一层,就这样一层一层地返回。递归的执行顺序类似于深度优先遍历。一边递归一边修改指针指向,递归完成时,翻转也完成。
这里运用到链表操作中一个重要的技巧:
Dummy Node
。 设置一个哑节点dummy
,让dummy
的next
指针指向链表的第一个节点。每次循环时把head
后面的head->next
节点插入到哑节点后面。
以第一次循环为例,把节点2
插入到dummy
之后,1
之前。
第二次循环时把3
插入到dummy
之后,2
之前。依此类推。
由于每次循环后链表的第一个节点都会变化,所以要先记录它的值,以便后续操作中在它的前面插入。
C++ - iteration
ListNode* reverseList(ListNode* head) { if (head == NULL || head->next == NULL) { return head; } ListNode *pre = NULL, *next; while (head != NULL) { next = head->next; head->next = pre; pre = head; head = next; } return pre;}
边界条件
当最后head
为NULL
时,整个翻转才完成。C++ - recursion
ListNode* reverseList(ListNode* head) { if (head == NULL || head->next == NULL) { return head; } ListNode *newHead = reverseList(head->next); head->next->next = head; head->next = NULL; return newHead;}
边界条件
head == NULL
:判断异常。head->next == NULL
:递归终止。C++ - Dummy Node
ListNode* reverseList(ListNode* head){ ListNode dummy(0); dummy.next = head; ListNode* p = &dummy; while (head && head->next) { ListNode *temp = dummy.next; //记录链表的第一个节点 p->next = head->next; head->next = p->next->next; p->next->next = temp; } return dummy.next;}
- 边界条件
当head
为空时,不执行循环返回为空值。当head->next
为空时,说明到了最后一个节点,退出循环。
判断单链表是否存在环
题目描述
141. Linked List CycleGiven a linked list, determine if it has a cycle in it.
Follow up:
Can you solve it without using extra space?思路
用
set
设访问标记。把单链表的节点一个一个放入set
,判断节点是否已经存在于set
中。如果set
中已存在该节点,说明单链表有环。使用快慢指针
fast
和slow
。两个指针都从表头开始往后走。设置slow
每次走一步,fast
每次走两步。如果fast
遇到null
,说明没有环,返回false
。如果slow=fast
,说明有环。时间复杂度为O(n) 。
C++ -set
bool hasCycle(ListNode* head){ set<ListNode*> visited; while (head) { set<ListNode*>::iterator iter = visited.find(head); if (iter == iter.end()) { visited.insert(head); } else { return true; } head = head->next; } return false;}
- C++ -Fast and slow pointer
bool hasCycle(ListNode* head){ ListNode *fast, *slow; fast = slow = head; while (fast && fast->next) { fast = fast->next->next; slow = slow->next; if (fast == slow) { return true; } } return false;}
扩展问题:单链表环的入口及长度
- 题目描述
142. Linked List Cycle IIGiven a linked list, return the node where the cycle begins. If there is no cycle, return null.
Note: Do not modify the linked list.
Follow up:
Can you solve it without using extra space? 思路
如上图:
slow
走到相遇点时走过的距离为distslow=x+y
fast
走到相遇点时走过的距离为distfast=(x+y+z)+y
由于fast
的速度是slow
的2倍,所以distfast=2distslow ,即2(x+y)=(x+y+z)+y ,可知x=z 。
当快慢指针到达相遇点时:
①. 只需让fast
指针继续从相遇点出发,slow
指针从表头出发,并保持相同的速度,当它们再次相遇时,即是环的入口。
②. 让fast
指针继续以两倍于slow
指针的速度前进,记录各自走过的距离。当它们再次相遇时,fast
刚好比slow
多跑一圈,此时用fast
走过的距离减去slow
走过的距离即可得到环的长度。C++ -Fast and slow pointer
ListNode *detectCycle(ListNode *head) { bool hasCycle = false; ListNode *fast, *slow; fast = slow = head; while (fast->next && fast->next->next) { fast = fast->next->next; slow = slow->next; if (fast == slow) { hasCycle = true; break; } } if (hasCycle == false) { return NULL; } else { slow = head; while (slow != fast) { slow = slow->next; fast = fast->next; } } return slow;}
链表倒数第k个节点
设置前后两个指针
pre
和cur
指向同一个起点,让前一个指针先走k-1
步,然后两个指针再同时一步一步移动,当cur
走到最后一个节点时,此时pre
正好指向倒数第k
个节点。
题目描述
19. Remove Nth Node From End of ListGiven a linked list, remove the nth node from the end of list and return its head.
For example,Given linked list: 1->2->3->4->5, and n = 2. After removing the second node from the end, the linked list becomes 1->2->3->5.
思路
运用上面的方法,使用前后两个指针。由于要删除第k
个节点,当后面一个指针移动到尾节点时,前一个指针应移动到倒数第k
个节点的前一个节点。然后pre->next = pre->next->next
。C++ -Fast and slow pointer
ListNode* removeNthFromEnd(ListNode* head, int n) { ListNode dummy(0); dummy.next = head; ListNode *pre = &dummy; while (--n) { head = head->next; } while (head->next) { /*移动head指针到尾节点*/ head = head->next; pre = pre->next; } pre->next = pre->next->next; return dummy.next;}
- 边界条件
这个题要格外注意边界条件,应注意链表中只有一个节点的情况。不然很容易出错,或者把代码写复杂。
为了使代码更简洁,减少不必要的边界判断,可以在表头前添加一个哑节点。让前一个指针pre
指向哑节点,让后一个指针移动k-1
步后再同时移动两个指针,当后一个指针移动到尾节点时,pre
指针刚好移动到倒数第k
个节点的前一个节点。而且这种做法保证了当链表中只有一个节点时,不必担心pre->next->next
的越界问题。
扩展问题:求链表的中间节点
- 题目描述
148. Sort ListSort a linked list in O(nlogn) time using constant space complexity.
思路
排序时间复杂度为O(nlogn) 的算法能想到快速排序和归并排序。排序时只需改变指针,所以不必像数组那样需要额外的存储空间,空间复杂度为O(1) 。找中间节点只需O(n2) 的线性时间,所以总时间复杂度为O(nlogn) ,满足题目要求。C++ -Fast and slow pointer
ListNode* sortList(ListNode* head) { if (head == NULL || head->next == NULL) { return head; } ListNode *slow, *fast; slow = fast = head; while (fast->next && fast->next->next) { fast = fast->next->next; slow = slow->next; } ListNode* mid = slow->next; slow->next = NULL; //把一段链表分为两段子链表 ListNode* list1 = sortList(head); ListNode* list2 = sortList(mid); return mergeList(list1, list2);}ListNode* mergeList(ListNode* list1, ListNode* list2){ ListNode dummy(0); dummy.next = list1; ListNode *p = &dummy; while (NULL != list1 && NULL != list2) { if (list1->val <= list2->val) { p->next = list1; list1 = list1->next; } else { p->next = list2; list2 = list2->next; } p = p->next; } if (NULL != list1) { p->next = list1; } else { p->next = list2; } return dummy.next;}
- 边界条件
求中间节点时,一时脑抽把while
中的循环条件写成了fast && fast->next
,结果一直RE
。可见边界很重要啊,对循环中涉及到fast->next->next
的操作一定要格外小心,不然很容易就越界啊各种错误。
判断两个链表是否相交
题目描述
160. Intersection of Two Linked ListsWrite a program to find the node at which the intersection of two singly linked lists begins.
For example, the following two linked lists:
begin to intersect at node c1.思路
如果两个链表长度相同,只要一个一个地对比下去就能找到。当链表长度不同时,只需要从较长的链表头结点往下遍历,直到与短链表长度相同时再一个一个地比对即可。C++
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) { if (headA == NULL || headB == NULL) { return NULL; } int lenA = 1, lenB = 1; ListNode *pA = headA, *pB = headB; while (pA->next != NULL) { lenA++; pA = pA->next; } while (pB->next != NULL) { lenB++; pB = pB->next; } if (pA != pB) { return NULL; } if (lenA > lenB) { for (int i = 0; i < lenA - lenB; i++) { headA = headA->next; } } else { for (int i = 0; i < lenB - lenA; i++) { headB = headB->next; } } while (headA != headB) { headA = headA->next; headB = headB->next; } return headA;}
Reference
面试精选:链表问题集锦
链表的反转问题:递归和非递归方式
求有环单链表中的环长、环起点、链表长
Proof of detecting the start of cycle in linked list
- 面试算法:Linked List-链 表
- leetcode:Reverse Linked List II (反转链表中的一部分)【面试算法题】
- 算法 Reverse Linked List
- C++ Linked list: Traversing a Linked list(遍历链表)
- Linked List 链表详解
- 【LeetCode-面试算法经典-Java实现】【092-Reverse Linked List II(反转单链表II)】
- 【LeetCode-面试算法经典-Java实现】【114-Flatten Binary Tree to Linked List(二叉树转单链表)】
- 【LeetCode-面试算法经典-Java实现】【141-Linked List Cycle(单链表中有环)】
- 【LeetCode-面试算法经典-Java实现】【142-Linked List Cycle II(单链表中有环II)】
- 【LeetCode-面试算法经典-Java实现】【203-Remove Linked List Elements(删除单链表中的元素)】
- 【LeetCode-面试算法经典-Java实现】【206-Reverse Linked List(反转一个单链表)】
- leetcode:Partition List (链表处理)【面试算法题】
- 【面试准备】letcode-Linked List Cycle ||
- 【面试准备】letcode-Linked List Cycle |
- (每日算法)LeetCode --- Reverse Linked List II(旋转链表的指定部分)
- 算法题——Linked List Cycle II(C++)链表中的环
- XOR linked list 异或链表
- 逆转链表 Reverse Linked List
- 将api工程deploy到私服
- Android常用命令
- Socket编程模型之完成端口模型
- char * 与 char [] 区别,char *[] 与 char[][]区别。字符串指针的数组与二维char数组区别。字符串常量赋予指针与字符数组区别。"字符串"等价于其首元素"字"的地址
- JavaScript对象学习笔记(一)
- 面试算法:Linked List-链 表
- python ord()与chr()用法以及区别
- time元素和pubdate属性
- 新闻采集
- java
- C++运算符重载
- sax解析
- 1102 Java-API常用类
- R语言随机森林初探