链表是最基本的数据结构,面试官也常常用链表来考察面试者的基本能力,而且链表相关的操作相对而言比较简单,也适合考察写代码的能力。链表的操作也离不开指针,指针又很容易导致出错。综合多方面的原因,链表题目在面试中占据着很重要的地位。
数组是一种顺序表,index与value之间是一种顺序映射,以O(1)的复杂度访问数据元素。但是,若要在表的中间部分插入(或删除)某一个元素时,需要将后续的数据元素进行移动,复杂度大概为O(n)。链表(Linked List)是一种链式表,克服了上述的缺点,插入和删除操作均不会引起元素的移动,数据结构定义如下:
-
public class ListNode { String val; ListNode next; ListNode(int x) : val(x), next(NULL) {} }
常见的链表有单向链表(也称之为chain),只有next指针指向后继结点,而没有previous指针指向前驱结点。链表的插入与删除操作只涉及到next指针的更新,而不会移动数据元素。
LeetCode中关于链表的题目有以下五种类型题:
(1)链表之环(相交)相关题目:
- Given a linked list, determine if it has a cycle in it.
- 题目要求:给定一个链表,判断是否有环。
- 题目分析:这道题是快慢指针的经典应用。只需要设两个指针,一个每次走一步的慢指针和一个每次走两步的快指针,如果链表里有环的话,两个指针最终肯定会相遇。
- 题目解答:
class Solution {
public:
bool hasCycle(ListNode *head) {
if(head == nullptr) return false;
ListNode *fast = head, *slow = head;
while(fast && fast->next){
slow = slow->next;
fast = fast->next->next;
if(slow == fast) return true;
}
return false;
}
};
- Given a linked list, return the node where the cycle begins. If there is no cycle, return
null
. - 题目要求:给定一个链表,判断是否有环,如果有环返回环的起始点,否则返回空指针。
- 题目分析:这个求单链表中的环的起始点是之前那个判断单链表中是否有环的延伸,可参之前那道Linked List Cycle。这里还是要设快慢指针,不过这次要记录两个指针相遇的位置,当两个指针相遇了后,让其中一个指针从链表头开始一步一步的走,此时再相遇的位置就是链表中环的起始位置。
- 题目解答:
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
if(head == nullptr) return nullptr;
ListNode *fast = head, *slow = head, *start = head;
while(fast && fast->next){
fast = fast->next->next;
slow = slow->next;
if(slow == fast){
while(start != slow){
slow = slow->next;
start = start->next;
}
return start;
}
}
return nullptr;
}
};
160. Intersection of Two Linked Lists
- Write a program to find the node at which the intersection of two singly linked lists begins.
- 题目要求:这道求两个链表的交点题要求执行时间为O(n)。
- 题目分析:虽然题目中强调了链表中不存在环,但是我们可以用环的思想来做,让两条链表分别从各自的开头开始往后遍历,当其中一条遍历到末尾时,我们跳到另一个条链表的开头继续遍历。两个指针最终会相等,而且只有两种情况,一种情况是在交点处相遇,另一种情况是在各自的末尾的空节点处相等。
- 题目解答:
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode *p1 = headA, *p2 = headB;
while(p1 || p2){
if(p1 == p2) return p1;
if(p1 == nullptr) p1 = headB;
else p1 = p1->next;
if(p2 == nullptr) p2 = headA;
else p2 = p2->next;
}
return nullptr;
}
};
(2)链表之删除节点相关题目:
237. Delete Node in a Linked List
- Write a function to delete a node (except the tail) in a singly linked list, given only access to that node.
- 题目要求:删除链表的一个节点。
- 题目分析:这道题的处理方法是先把当前节点的值用下一个节点的值覆盖了,然后我们删除下一个节点即可。
- 题目解答:
class Solution {
public:
void deleteNode(ListNode* node) {
ListNode *temp = node->next;
node->next = temp->next;
node->val = temp->val;
delete temp;
}
};
203. Remove Linked List Elements
- Remove all elements from a linked list of integers that have value val.
- 题目要求:移除链表中所有元素等于val的节点。
- 题目分析:只需定义几个辅助指针,然后遍历原链表,遇到与给定值相同的元素,将该元素的前后连个节点连接起来,然后删除该元素即可,要注意的是当头指针元素等于val的情况。
- 题目解答:
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
while(head && head->val == val){
head = head->next;
}
if(head == nullptr) return nullptr;
ListNode *temp = head;
while(temp->next){
if(temp->next->val == val){
temp->next = temp->next->next;
}else{
temp = temp->next;
}
}
return head;
}
};
83. Remove Duplicates from Sorted List
- Given a sorted linked list, delete all duplicates such that each element appear only once.
- 题目要求:移除有序链表中的重复项,使其只出现一次。
- 题目分析:定义一个指针指向该链表的第一个元素,然后第一个元素和第二个元素比较,如果重复了,则删掉第二个元素,如果不重复,指针指向第二个元素。这样遍历完整个链表,则剩下的元素没有重复项。
- 题目解答:
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if(head == nullptr || head->next == nullptr) return head;
ListNode *temp = head;
while(temp->next){
if(temp->val == temp->next->val){
temp->next = temp->next->next;
}else{
temp = temp->next;
}
}
return head;
}
};
19. Remove Nth Node From End of List
- Given a linked list, remove the n-th node from the end of list and return its head
- 题目要求:这道题让我们移除链表倒数第N个节点,限定n一定是有效的,即n不会大于链表中的元素总数。
- 题目分析:我们需要用两个指针来帮助我们解题,slow和fast指针。首先fast指针先向前走N步,如果此时fast指向空,说明N为链表的长度,则需要移除的为首元素,那么此时我们返回head->next即可,如果fast存在,我们再继续往下走,此时slow指针也跟着走,直到fast为最后一个元素时停止,此时slow指向要移除元素的前一个元素,我们再修改指针跳过需要移除的元素即可。
- 题目解答:
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode *fast = head, *slow = head;
for(int i = 0; i < n; i++) fast = fast->next;
if(fast == nullptr) return head->next;
fast = fast->next;
while(fast){
fast = fast->next;
slow = slow->next;
}
slow->next = slow->next->next;
return head;
}
};
(3)链表之排序节点相关题目:
- Merge two sorted linked lists and return it as a new list. The new list should be made by splicing together the nodes of the first two lists.
- 题目要求:混合插入有序链表。
- 题目分析:具体思想就是新建一个链表,然后比较两个链表中的元素值,把较小的那个链到新链表中,由于两个输入链表的长度可能不同,所以最终会有一个链表先完成插入所有元素,则直接另一个未完成的链表直接链入新链表的末尾。
- 题目解答:
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode *head = new ListNode(-1), *temp = head;
if(l1 == nullptr) return l2;
if(l2 == nullptr) return l1;
while(l1 && l2){
if(l1->val < l2->val){
temp->next = l1;
l1 = l1->next;
}else{
temp->next = l2;
l2 = l2->next;
}
temp = temp->next;
}
if(l1 == nullptr) temp->next = l2;
if(l2 == nullptr) temp->next = l1;
return head->next;
}
};
- Sort a linked list in O(n log n) time using constant space complexity.
- 题目要求:排序链表。
- 题目分析:常见排序方法有很多,插入排序,选择排序,堆排序,快速排序,冒泡排序,归并排序,桶排序等等。。它们的时间复杂度不尽相同,而这里题目限定了时间必须为O(nlgn),符合要求只有快速排序,归并排序,堆排序,而根据单链表的特点,最适于用归并排序。
- 题目解答:
class Solution {
public:
ListNode* sortList(ListNode* head) {
if(head == nullptr || head->next == nullptr) return head;
ListNode *pre = head, *slow = head, *fast = head;
while(fast && fast->next){
pre = slow;
slow = slow->next;
fast = fast->next->next;
}
pre->next = nullptr;
ListNode *l1 = sortList(head);
ListNode *l2 = sortList(slow);
return mergeTwoLists(l1, l2);
}
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode *head = new ListNode(-1), *temp = head;
if(l1 == nullptr) return l2;
if(l2 == nullptr) return l1;
while(l1 && l2){
if(l1->val < l2->val){
temp->next = l1;
l1 = l1->next;
}else{
temp->next = l2;
l2 = l2->next;
}
temp = temp->next;
}
if(l1 == nullptr) temp->next = l2;
if(l2 == nullptr) temp->next = l1;
return head->next;
}
};
- Sort a linked list using insertion sort.
- 题目要求:直接插入排序链表。
- 题目分析:链表的插入排序实现原理很简单,就是一个元素一个元素的从原链表中取出来,然后按顺序插入到新链表中,时间复杂度为O(n2),是一种效率并不是很高的算法,但是空间复杂度为O(1),以高时间复杂度换取了低空间复杂度。
- 题目解答:
class Solution {
public:
ListNode* insertionSortList(ListNode* head) {
ListNode *dummy = new ListNode(-1), *cur = dummy;
while(head){
ListNode *temp = head->next;
cur = dummy;
while(cur->next && cur->next->val < head->val) cur = cur->next;
head->next = cur->next;
cur->next = head;
head = temp;
}
return dummy->next;
}
};
- Given a linked list and a value x, partition it such that all nodes less than x come before nodes greater than or equal to x. You should preserve the original relative order of the nodes in each of the two partitions.
- 题目要求:这道题要求我们划分链表,把所有小于给定值的节点都移到前面,大于该值的节点顺序不变,相当于一个局部排序的问题。
- 题目分析:将所有小于给定值的节点取出组成一个新的链表,此时原链表中剩余的节点的值都大于或等于给定值,只要将原链表直接接在新链表后即可。
- 题目解答:
class Solution {
public:
ListNode* partition(ListNode* head, int x) {
ListNode *node1 = new ListNode(-1), *p1 = node1;
ListNode *node2 = new ListNode(-1), *p2 = node2;
while(head){
if(head->val < x){
p1->next = head;
p1 = p1->next;
}else{
p2->next = head;
p2 = p2->next;
}
head = head->next;
}
p2->next = nullptr;
p1->next = node2->next;
return node1->next;
}
};
(4)链表之反转节点相关题目:
- Reverse a singly linked list.
- 题目要求:反转单链表。
- 题目分析:思路:就是将指针方向改变遍历下去,假设有三个节点,cur代表当前节点,pre代表前一节点,next代表后一节点,我们要做的就是将cur的下一个节点改为pre,当然直接赋值是不行的,会丢失next及以后的节点,所以必须用一个指针在操作之前先把它存起来,假设为tmp,然后我们把指针方向改变,即cur->next指向pre,再将pre和now指针顺序后移就可以了,进行下一次循环,直到cur节点为空。
- 题目解答:
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode *cur = head, *pre = nullptr, *temp = nullptr;
while(cur){
temp = cur->next;
cur->next = pre;
pre = cur;
cur = temp;
}
return pre;
}
};
- Given a singly linked list, determine if it is a palindrome.
- 题目要求:判断一个链表是否为回文链表。
- 题目分析:将链表分成两个部分,然后反转其中一个链表得到新的链表,然后对照各个位置的元素。
- 题目解答:
class Solution {
public:
bool isPalindrome(ListNode* head) {
if(head == nullptr || head->next == nullptr) return true;
ListNode *slow = head, *fast = head, *temp = nullptr;
while(fast->next && fast->next->next){
fast = fast->next->next;
slow = slow->next;
}
temp = reverse(slow->next);
while(temp){
if(temp->val != head->val) return false;
temp = temp->next;
head = head->next;
}
return true;
}
ListNode* reverse(ListNode* head){
ListNode *pre = nullptr, *temp = nullptr;
while(head){
temp = head->next;
head->next = pre;
pre = head;
head = temp;
}
return pre;
}
};
- Given a linked list, rotate the list to the right by k places, where k is non-negative.
- 题目要求:题目的意思是如果有一个链表,现在让你循环向右位移K步,得到一个新的开头的链表。
- 题目分析:先遍历整个链表获得链表长度n,然后此时把链表头和尾链接起来,在往后走n - k % n个节点就到达新链表的头结点前一个点,这时断开链表即可。
- 题目解答:
class Solution {
public:
ListNode* rotateRight(ListNode* head, int k) {
if(head == nullptr) return head;
ListNode *newhead = head, *tail = head;
int len = 1;
while(tail->next){
len++;
tail = tail->next;
}
tail->next = head;
if(k %= len){
for(int i = 0; i < len - k; i++){
tail = tail->next;
}
}
newhead = tail->next;
tail->next = nullptr;
return newhead;
}
};
(5)链表之其他相关题目:
- You are given two non-empty linked lists representing two non-negative integers. The digits are stored in reverse order and each of their nodes contain a single digit. Add the two numbers and return it as a linked list.
- 题目要求:有两个链表,它们表示逆序的两个非负数。计算出两个数的和之后,同样逆序输出作为一个链表。需要注意一点:有进位。
- 题目分析:就是建立一个新链表,然后把输入的两个链表从头往后撸,每两个相加,添加一个新节点到新链表后面,就是要处理下进位问题。还有就是最高位的进位问题要最后特殊处理一下。
- 题目解答:
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode *dummy = new ListNode(-1), *temp = dummy;
int carry = 0;
while(l1 || l2 || carry){
int sum = (l1 ? l1->val : 0) + (l2 ? l2->val : 0)+carry;
carry = sum /10;
temp->next = new ListNode(sum%10);
temp = temp->next;
l1 = l1 ? l1->next : 0;
l2 = l2 ? l2->next : 0;
}
return dummy->next;
}
};
- Given a linked list, swap every two adjacent nodes and return its head.
- 题目要求:两两翻转,不能借助其它的辅助空间。
- 题目分析:这道题不算难,是基本的链表操作题,我们可以分别用递归和迭代来实现。对于迭代实现,还是需要建立dummy节点,注意在连接节点的时候,最好画个图,以免把自己搞晕了。
- 题目解答:
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode *dummy = new ListNode(-1);
dummy->next = head;
ListNode *pre = dummy, *cur = head;
while(cur &&cur->next){
pre->next = cur->next;
cur->next = cur->next->next;
pre->next->next = cur;
pre = cur;
cur = cur->next;
}
return dummy->next;
}
};
- Reverse a linked list from position m to n. Do it in one-pass. Note: 1 ≤ m ≤ n ≤ length of list.
- 题目要求:反转链表中第m 到 第n 个结点。
- 题目分析:这道题的要求是只通过一次遍历完成,就拿题目中的例子来说,变换的是2,3,4这三个点,那么我们可以先取出2,用front指针指向2,然后当取出3的时候,我们把3加到2的前面,把front指针前移到3,依次类推,到4后停止,这样我们得到一个新链表4->3->2, front指针指向4。对于原链表连说,有两个点的位置很重要,需要用指针记录下来,分别是1和5,因为当2,3,4被取走时,原链表就变成了1->5->NULL,要把新链表插入的时候需要这两个点的位置。1的位置很好找,因为知道m的值,我们用pre指针记录1的位置,5的位置最后才能记录,当4结点被取走后,5的位置需要记下来,这样我们就可以把倒置后的那一小段链表加入到原链表中。
- 题目解答:
class Solution {
public:
ListNode* reverseBetween(ListNode* head, int m, int n) {
ListNode *newhead = new ListNode(-1);
newhead->next = head;//别忘记赋值
ListNode* pre = newhead;
for(int i = 0; i < m-1; i++)
pre = pre->next;
ListNode* cur = pre->next, *temp = nullptr;
for(int i = 0; i < n-m;i++){
temp = cur->next;
cur->next = temp->next;
temp->next = pre->next;//cur != pre->next
pre->next = temp;
}
return newhead->next;
}
};
138. Copy List with Random Pointer
- A linked list is given such that each node contains an additional random pointer which could point to any node in the list or null. Return a deep copy of the list.
- 题目要求:拷贝带有随机指针的链表。
- 题目分析:该方法可以分为以下三个步骤:1. 在原链表的每个节点后面拷贝出一个新的节点;2. 依次给新的节点的随机指针赋值,而且这个赋值非常容易 cur->next->random = cur->random->next;3. 断开链表可得到深度拷贝后的新链表
- 题目解答:
class Solution {
public:
RandomListNode *copyRandomList(RandomListNode *head) {
RandomListNode *new_head, *l1, *l2;
if(head == nullptr) return nullptr;
for(l1 = head; l1 != nullptr; l1 = l1->next->next){
l2 = new RandomListNode(l1->label);
l2->next = l1->next;
l1->next = l2;
}
new_head = head->next;
for(l1 = head; l1 != nullptr; l1 = l1->next->next){
if(l1->random != nullptr)
l1->next->random = l1->random->next;
}
for(l1 = head; l1 !=nullptr; l1 = l1->next){
l2 = l1->next;
l1->next = l2->next;
if(l2->next != nullptr) l2->next = l2->next->next;
}
return new_head;
}
};
如果各位看官们,大神们发现了任何错误,或是代码无法通过OJ,或是有更好的解法,或是有任何疑问,意见和建议的话,请一定要在帖子下面评论区留言告知博主啊,多谢多谢,祝大家刷得愉快,刷得精彩,刷出美好未来~