您的位置:首页 > 其它

[Leetcode] Week %d ------ Linked List

2017-10-08 10:36 267 查看
经历了整个九月份的转专业流程终于如愿来到了CS相关的专业,当然这也意味着自己以后的博客会经常更新了,这次我就跟着自己在大学里面的课程进度写一下关于链表OJ的解法吧。在后面我会再加上一篇关于指针操作的博客,因为在学习链表和树的时候最大的敌人就是指针,我被指针的问题烦扰了好几天,现在总结出了一些比较好的对应方法能让自己避开这个大坑,希望可以帮助到大家。

一.Linked List Cycle

Given a linked list, determine if it has a cycle in it.

Follow up:

Can you solve it without using extra space?

题目大意:不用额外的空间,我们要去判断一个给定的链表是否内含一个环。

这道题目作为第一道题我觉得非常有意思,因为它似乎不能用常规的想法去做。比较常规的思路是由于链表的特点是每个节点的地址都不一样,那么记录头结点的地址然后再创建新的指针不断遍历链表,直至发现到有相同的地址为止即可判断其有环。

但是!题目的意思很明显,要我们判断一个链表中是否内含一个环。也就是说,这个环可能就是整个链表,也有可能是仅包含了部分节点的环。因此,我们这种想要借助一个指针遍历通过寻找相同值从而判断是否含环的做法是不现实的。这个时候我就想到了平时我们绕圈跑步的时候如果一个人跑得慢另一个人跑得快,那么快的人总会追上慢的人,这是环的闭合性导致的。那么我们也可以借助这个特性来判断一个链表是否含环。

/**
* Definition for singly-linked list.
* struct ListNode {
*     int val;
*     ListNode *next;
*     ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool hasCycle(ListNode *head) {
if(head==null) return false;
ListNode * slowptr = head;
ListNode * fastptr = head;
while(fastptr->next!= NULL && fastptr->next->next!= NULL)//判断该链表是否有尽头
{
slowptr = slowptr->next;
fastptr = fastptr->next->next;//快指针每次比慢指针多走两步,若进入环中则必会追及
if(slowptr == fastptr) return true;
}
return false;
}
};


这道题比较经典,而且快慢指针这个好工具我们在后面的题目还是会遇到的,这道题作为我们的入门第一题意义非常好。

二.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.

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.

题目大意:给一个节点,我们需要在这个链表中删除这个节点(非尾节点)

我们经常会将数组与链表联想在一起,因为它们是我们入门数据结构的前两个必学的‘表’,但是链表与数组又有着很大的不同,最不同的就是链表的实质其实是对节点在系统地址的操作,而数组的实质是对元素在数组中的地址的操作。因此,当我们需要删除数组中的元素时,需要遍历数组从而找到要删除的元素,但是要删除链表中的节点时,由于我们已经拥有了要删除的节点(其地址),那么我们只需要通过系统地址直接对其进行操作即可。

在链表中我们添加或删除一个节点时都要考虑到要让前一个节点的next指针能指向新节点,后面的指针则视情况而动。但是在链表中,我们没法真的删除这个节点,这个节点的内存还是在的,我们只能够选择跳过它或者是覆盖它。我们只需要用p节点(待删除)的下一节点覆盖掉p节点即可。因此我们的真正要写的代码只有一行:

/**
* Definition for singly-linked list.
* struct ListNode {
*     int val;
*     ListNode *next;
*     ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
void deleteNode(ListNode* node) {
*node = *node->next;
}
};


切记这里不能直接将node->next的地址赋值给node,因为如果直接修改地址的话,那么会发生这个指针无法被修改的情况。整个数组也就没有变化。

但是说实话,这道题比较扯淡,不怎么现实,因为没有人会给你一个节点然后要你删除,而且这道题目居然连head节点都不给你,就显得十分无脑了。常见的都是给你一个val或者是指定链表中要删除节点的位置,所以这里附上自己写的删除某个位置的节点的代码。

#include<iostream>
using namespace std;

struct Node
{
char word;
Node * next;
};

class Linklist
{
public:
Linklist();
~Linklist();
void Pushback(char a[]);
void Output_length();
bool Empty();
void Output_Elem(int loc);
void Output_Loc(char tar);
void Insert(int loc, char m);
void Output_List();
void Delete_Elem(int loc);

private:
Node * head;
int length;
};

Linklist::Linklist()
{
head = new Node;
head-&g
f91b
t;word = 'a';
head->next = NULL;
length = 0;
}
void Linklist::Delete_Elem(int loc)
{
Node *ptemp = head, *pdelete;
while (loc-- > 1)
ptemp = ptemp->next;
pdelete = ptemp->next;
ptemp->next = pdelete->next;
delete pdelete;
pdelete = NULL;
length--;
}


三.Remove Duplicates from Sorted List

Given a sorted linked list, delete all duplicates such that each element appear only once.

For example,

Given 1->1->2, return 1->2.

Given 1->1->2->3->3, return 1->2->3.

题目大意:我们需要删除有序列表中重复的元素并返回新的列表。

这题跟第二题的删除节点联系比较密切,初始想法就是我们从列表的头结点开始,设置一个pre指针负责固定在有重复元素的第一个元素位置上,一个p指针负责向前探路找到第一个与pre指针指向的元素不同的节点,然后跳过中间重复元素的节点,令pre节点的next指针直接指向p节点,最后再令pre跳至p节点进行再次循环直至p节点为空。

代码如下:

/**
* Definition for singly-linked list.
* struct ListNode {
*     int val;
*     ListNode *next;
*     ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if(head == NULL || head->next == NULL) return head;
ListNode * pre = head;
ListNode * p = head;
while(p->next != NULL)
{
p = p->next;
if(p->val == pre->val)  continue;
else
{
pre->next = p;
pre = pre->next;
}
}
if(p->val == pre->val)  pre->next = NULL;//针对当p节点为最后一个节点时的情况进行判断
return head;
}
};


四.Intersection of Two Linked Lists

Write 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.

Notes:

If the two linked lists have no intersection at all, return null.

The linked lists must retain their original structure after the function returns.

You may assume there are no cycles anywhere in the entire linked structure.

Your code should preferably run in O(n) time and use only O(1) memory.

题目大意:我们需要判断两个链表是否有重合的部分,若有则返回重合的第一个节点,若无则返回NULL。

这道题也是让人眼前一亮的题目,用常规的想法去做的话肯定是很麻烦而且也不一定能正确的。鉴于两个链表在重合点之前长度可能会不一样,因此我们不能纯粹地用两个指针来遍历一遍并检查相等。这道题其实最麻烦的地方就在于第一次遍历时两个指针走的步数不一定会一样,也就无法保证同时到达。那么我们需要怎么样来保证两个指针相遇时的步数相等呢?

两个给定链表的重合与非重合部分长度是不变的,那么我们能否做到使得两个指针走的步数与这几个长度值有一个共同的等式呢?我们假设,如果这两个链表有重合部分,那么A指针从链表1头结点出发,B指针从链表2头结点出发。当走完第一次遍历达到空节点时,A指针所走步数=1链表非重合长度 + 重合长度,B指针所走步数 = 2链表非重合长度 + 重合长度。我们只需要在A步数上加上2链表非重合长度,B步数上加上1链表非重合长度即可使其二者相等。此时A、B指针必定是在重合点相遇。要实现这个想法只需将A指针搬至2链表头结点,B指针搬至1链表头结点。

因此得到代码如下:

/**
* Definition for singly-linked list.
* struct ListNode {
*     int val;
*     ListNode *next;
*     ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode *p1 = headA, *p2 = headB;
while(p1 != p2){
p1 = p1?p1->next:headB;
p2 = p2?p2->next:headA;
}
return p1;
}
};


五.Remove Linked List Elements

Remove all elements from a linked list of integers that have value val.

Example

Given: 1 –> 2 –> 6 –> 3 –> 4 –> 5 –> 6, val = 6

Return: 1 –> 2 –> 3 –> 4 –> 5

题目大意:删除含有给定值的节点并返回新的链表

这道题也是与前面的思路差不多,只是在寻找待删除节点时需要遍历而已,在找到之后就进行覆盖待删除节点的操作。

代码如下:

/**
* Definition for singly-linked list.
* struct ListNode {
*     int val;
*     ListNode *next;
*     ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode * p = head;
ListNode * pre;
while(head!= NULL && head->val == val)    {head = p->next, p = head;}
while(p != NULL)
{
if(p->val == val)   pre->next = p->next,p = pre->next;
else
{   pre = p;
p = p->next;
if(p!= NULL && p->val == val) pre->next = p->next, p =pre->next;
}
}
return head;
}
};


六.Reverse Linked List

Reverse a singly linked list.

题目大意:我们需要将一个单向链表反转

这道题一开始让我想起之前我一个高中同学给我的一道反转数组的题目,但是链表反转又有着一个比较麻烦地方就是无法回头,一旦进入到后面的节点,如果没有一个节点是保持跟踪前一个节点,那么就会有些麻烦。那么我们为了保守起见,就应该至少创建一个前节点。这道题我的灵感就是基于这种保守的想法然后再根据我们在遍历时就应该改变经过的点的next指针指向。最容易改变的应该就是head与tail这两个节点。因此我用了一种3指针结队过河拆桥的方法。第一个指针负责向前探路直至到达最后一个节点,中间指针则是负责将后面指针过渡到后一个指针以及改变其next指针指向前节点,前面的指针则负责向中间节点提供next指针指向点并改变头结点的任务。

因此基于这个方法我写出了如下代码:

/**
* Definition for singly-linked list.
* struct ListNode {
*     int val;
*     ListNode *next;
*     ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode * p1 = head;
ListNode * p2 = NULL;
ListNode * p3 = NULL;
if(p1 == NULL)  return head;
if(p1->next != NULL)  p2 = p1->next;
else    return head;
if(p2->next!= NULL)   p3 = p2->next;
else
{
p2->next = p1,p1->next = NULL;
return p2;
}
p1->next = NULL;
while(p3->next != NULL)
{
p2->next = p1;
p1 = p2;
p2 = p3;
p3 = p3->next;
}
p2->next = p1;
p3->next = p2;
return p3;
}
};


在做OJ时为了应对多种可能出现的情况,对给出的链表一定要进行全面的判断并作出正确的操作,当然我的代码不是最简洁的,但是个人觉得能有这种全面考虑的思路还是不错的,反正到以后的工业界工作的话没有bug是第一要义~(~ ̄▽ ̄)~ 在此思路上去简化代码是我想要追求的更高境界,只能慢慢加油了~

七.Palindrome Linked List

Given a singly linked list, determine if it is a palindrome.

Follow up:

Could you do it in O(n) time and O(1) space?

题目大意:我们需要用O(n)的时间复杂度与O(1)的空间复杂度的方法判断一个链表是否为回文链表(顺着看与反着看都是一样的)

由于链表不像数组一样可以从后往前遍历从而进行判断,那么我们就需要将后半段的链表反过来再与前半段的链表进行判断。反转链表函数我在上一题已经实现过了,难点就在于如何获得后半段链表。这个时候我们就想起了我的好朋友——快慢指针。 假设链表长度为N,那么后半段链表的开头节点位置就是第N/2 + 1个。 如果我们设定快指针一次走两步,慢指针一次走一步,那么当快指针走到链表倒数第三个时,慢指针就停下了,此时只要将慢指针所指节点的下一个节点作为后半段链表的头结点即可。至于比较两个链表是否相等,只需要不断遍历比较即可。

/**
* Definition for singly-linked list.
* struct ListNode {
*     int val;
*     ListNode *next;
*     ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool isPalindrome(ListNode* head) {
if(head==NULL||head->next==NULL)
return true;
ListNode* slow=head;
ListNode* fast=head;
while(fast->next!=NULL&&fast->next->next!=NULL){
slow=slow->next;
fast=fast->next->next;
}
slow->next=reverseList(slow->next);  //令慢指针指向节点的下一个节点作为后半段链表头结点
slow=slow->next;
while(slow!=NULL){    //遍历两个链表并进行比较
if(head->val!=slow->val)
return false;
head=head->next;
slow=slow->next;
}
return true;
}
ListNode* reverseList(ListNode* head) {  //省去了条件判断
ListNode* pre=NULL;
ListNode* next=NULL;
while(head!=NULL){
next=head->next;
head->next=pre;
pre=head;
head=next;
}
return pre;
}
};


八.Merge Two Sorted Lists

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

题目大意:将两个有序列表的元素按升序融合为一个新列表

这一题其实并不难,只不过比较特殊的就是要决定哪个元素当新链表的开头,由于没有限制不能申请新的空间,因此可以开一个新链表再往内赋值。如果是不允许申请新空间的话,也只是在处理head头结点处会比较麻烦。

详细解释我在代码中以注释形式给出:

/**
* Definition for singly-linked list.
* struct ListNode {
*     int val;
*     ListNode *next;
*     ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if(l1 == NULL)  return l2;
if(l2 == NULL)  return l1;
if(l1 == NULL || l2 == NULL)    return NULL;  //对链表特殊情况的判断

ListNode * p1 = l1;
ListNode * p2 = l2;
ListNode * head = NULL;   //声明一个指针一定要先赋值(NULL或已有的指针值)
if(p1->val < p2->val)   //开始根据两个链表开始元素的大小来决定哪个元素当新链表头结点的值
{
head = l1;
p1 = p1->next;
}
else
{
head = l2;
p2 = p2->next;
}
ListNode * p = head;   //声明一个追踪指针作为插入节点的前节点

while(p1 != NULL && p2 != NULL)
{
if(p1->val < p2->val)
{
p->next = p1;
p = p->next;
p1 = p1->next;
}
else
{
p->next = p2;
p = p->next;
p2 = p2->next;
}
}

if(p1 == NULL && p2 == NULL)     return head;
else if(p1 == NULL && p2 != NULL)   p->next = p2;
else if(p1 != NULL && p2 == NULL)   p->next = p1;
//当遍历其中一个链表已经至结尾空节点时,则另一个链表的值可以直接嫁接上去

return head;
}
};


九.Sort List

Sort a linked list in O(n log n) time using constant space complexity.

题目大意:我们需要用时间复杂度为O(n log n)的方法对链表进行排序

可以先观察题目要求的O(n log n )时间复杂度,通常我们最麻烦的排序算法时间复杂度是O(n^2),也就是在每一个元素遍历的同时遍历整个数组。但是这个O(n log n)的时间复杂度就告诉我们,我们需要利用类似二分法的方法进行排序。

在前面的题目我们已经知道,要获得一个链表的均分两部分,我们要用快慢指针中的慢指针获得。如果我们用递归的话,我们可以设想到最终会将整个长度为N的链表分裂成N个节点,我们就从小的做起,先将相邻两个节点进行有序融合(第八题中的Merge),获得N/2个子链表,然后再对相邻的子链表进行Merge得到N/4个子链表以此类推,最终就会得到最终的有序链表。

代码如下:

/**
* Definition for singly-linked list.
* struct ListNode {
*     int val;
*     ListNode *next;
*     ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* sortList(ListNode* head) {
if(head == NULL || head->next == NULL)
return head;

ListNode* head1 = head;
ListNode* head2 = getMid(head);     //通过快慢指针获得中间数组进行第一次分组
head1 = sortList(head1);    //递归至每一小块只有一个元素
head2 = sortList(head2);
return merge(head1, head2);
}
ListNode* merge(ListNode* head1, ListNode* head2)
{
ListNode* newhead = new ListNode(0);
ListNode* newtail = newhead;
while(head1 != NULL && head2 != NULL)
{
if(head1->val <= head2->val)
{
newtail->next = head1;  //将小的元素放入新链表的表头
head1 = head1->next;
}
else
{
newtail->next = head2;
head2 = head2->next;
}
newtail = newtail->next;    //负责跟踪目前要插入元素的前一个位置
newtail->next = NULL;
}
if(head1 != NULL)
newtail->next = head1;
if(head2 != NULL)
newtail->next = head2;
return newhead->next;
}
ListNode* getMid(ListNode* head)   //用快慢指针获得两段链表
{
ListNode* fast = head->next;
ListNode* slow = head->next;
ListNode* prev = head;
while(true)
{
if(fast != NULL)
fast = fast->next;
else
break;
if(fast != NULL)
fast = fast->next;
else
break;
prev = slow;
slow = slow->next;
}
prev->next = NULL;
return slow;
}
};


十.Insertion Sort List

Sort a linked list using insertion sort.

题目大意:需要我们用插入法对链表进行插入排序

这个对于我来说也是一个新的排序算法。其他博客上已经有写得非常好的解释了,那么大家可以去搜一下他们的博客去看,我就不班门弄斧了。

下面附上这道题的代码:

/**
* Definition for singly-linked list.
* struct ListNode {
*     int val;
*     ListNode *next;
*     ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* insertionSortList(ListNode *head) {
ListNode *dummy = new ListNode(INT_MIN), *cur = head, *prev = dummy;
dummy->next = head;
while (cur) {
if (cur->val >= prev->val) {
prev = cur;
cur = cur->next;
continue;
}
prev->next = cur->next;
ListNode *tmp = dummy;
while (cur->val > tmp->next->val) {
tmp = tmp->next;
}
cur->next = tmp->next;
tmp->next = cur;

cur = prev->next;
}
return dummy->next;
}
};


这个算法非常需要自己去画图理解指针的迁移,个人感觉更多的是只能意会不能言传,如果还是有不懂的可留言评论,我会一一解答。(不过好像太高估自己这篇文章的阅读量了 (/ω\)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: