绪论:
针对部分算法题其算法阐述思路
1、移除链表元素:
示例:
思路:
1、
针对移除链表中某个节点,可以创建一个新的节点来一一接收旧节点中节点,如下图所示:
创建两个新节点 newhead、ptail 初始值为 NULL
2、
起始时,若旧节点中的 pcur->next 不等于 val(设为条件1) ,则将 pcur 赋给 newphead与ptai,pcur指向下一个节点,这样就完成了第一次循环。 此时pcur指向第二个节点,ptail与newphead指向第一个节点。
第二次进入循环时,旧节点仍满足条件1,首先 ptail->next 指向 pcur,再将ptail移向下一节点,同时pcur移向下一节点,如下图所示。
三次循环时,当前pcur指向节点不满足条件1,所以直接将pcur指向下一节点,ptail不变,如此循环直至末尾
3、
当pcur指向NULL时,跳出循环,此时新节点中,最后一个节点指向的地址要置为NULL。如下图所示
代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode ListNode;
struct ListNode* removeElements(struct ListNode* head, int val) {
ListNode* newphead ,*ptail;
newphead = ptail = NULL;
ListNode* pcur;
pcur = head;
while(pcur)
{
if(pcur->val != val)
{
if(ptail == NULL)
{
newphead = ptail = pcur;//我怎么会觉得单改变改变ptail就会改变newphead的?
} //这两个同为同一类型的指针变量,不存在传址调用的那层关
//系
else
{
ptail->next = pcur;
ptail = ptail->next;
}
}
pcur = pcur->next;
}
if(ptail) //至于为什么是tail 而不是ptail->next
{ //当要删除所有旧节点时,那么新节点全为NULL指针
ptail->next = NULL; //因此是ptail,不要想当然去用ptail->next
} //仔细思考一下,其实前者作为条件更好。
return newphead;
}
2、反转链表:
示例:
思路:
1、
创建三个结构体指针n1、n2、n3,其指向如下图所示:
注:n3指针的指向前要判断节点是否为NULL
2、
我们将n2->next指向n1,n2 赋给 n1,此时 1 和 2 节点已经断开联系,但可以通过 n3 指针,让 1 和 2 节点重新建立联系,即令 n2 =n3 ,这样 n2 便可以通过赋值的方式 指向下一个节点,n3 则通过链表方式,指向下一个节点。如下图所示:红色为赋值后节点。
3、
重复上述循环,直至n2为NULL时,跳出循环,此时,n1处于尾节点,也是新链表的头节点。如下图所示
代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode ListNode;
struct ListNode* reverseList(struct ListNode* head) {
ListNode* n1 = NULL;
ListNode* n2 = head;
if(head == NULL)
{
return head;
}
ListNode* n3 = head->next;//不能对空指针进行结构体访问操作
while(n2)
{
n2->next = n1;
n1=n2;
n2=n3;
if(n3) //当n3为空时,此时循环尚未结束,因此最后一次结构体访问操作不能正常运行
n3=n3->next;
}
return n1;
}
3、寻找链表的中间节点:
示例:![](https://i-blog.csdnimg.cn/direct/a13b5dd92d5b45458a7088f09e5cdea7.png)
思路:
此处涉及一个重要算法思路:快慢指针。
我们来举两个例子分析一下快慢指针:
例1:
首先定义两个结构体指针 fast 和 slow ,其中 slow 每次移动一个节点大小,fast 移动两个,最终,当fast移动到尾部时,此时slow正好在中间位置(节点数为奇数)。注:红色为移动后指针位置。
例2:
对于总结点个数为偶数个数时,slow正好处于中间节点的下一个位置处,如图所示:
注:对于总结点个数为偶数时,此时fast指向NULL
对于快慢指针,快指针运动的路程为2n,而慢指针运动的路程为n,因此慢指针总是指向中间位置,或者中间位置靠后的第一个位置。
根据上述思想,代码编写就很容易了。
代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode ListNode;
struct ListNode* middleNode(struct ListNode* head) {
ListNode* fast,*slow;
fast = slow = head;
while(fast && fast->next) //注意短路:不能先把fast->next的条件写在前面
{ //我们要先判断fast不能为空,才能去判断fast->next不能空
slow = slow->next; //因为空指针不能进行结构体成员访问操作
fast = fast->next->next;
}
return slow;
}
4、合并两个有序链表
示例:
思路:
思路与合并两个有序数组相类似,创建两个结构体指针分别指向两个链表,遍历比较,将较小的那个尾插到新节点当中。如图所示:
代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
if(list1 == NULL) //考虑到链表为空,优先判断,再执行后续操作可以优化程序
{
return list2;
}
if(list2 == NULL)
{
return list1;
}
ListNode* p1 = list1;
ListNode* p2 = list2;
ListNode* p3,*newhead;
newhead = p3 = (ListNode*)malloc(sizeof(ListNode));
//如果用动态内存开辟,返回的是同一个区域,我们对p3修改的同时也会
//对newhead修改
while(p1 && p2) //
{
if(p1->val <= p2->val)
{
p3->next = p1;
p1 = p1->next;
}
else
{
p3->next = p2;
p2 = p2->next;
}
p3 = p3->next;
}
if(p1)
{
p3->next = p1;
}
if(p2)
{
p3->next = p2;
}
ListNode* tmp = newhead->next;
free(newhead);
newhead = NULL;
return tmp;
}
注:
小插曲1:
为什么用动态内存开辟新的头节点呢?这是为了后续编程中,不用考虑头节点为空的情况,如果头节点是空,那么还得对新的头节点做一次判断,编程会麻烦些。其次,用动态内存开辟的新节点,有效数据是从新节点的next即,newnode->next 开始的,因此在编程过程中,只需在代码末提前保存 newnode->next 的值,并将临时节点释放,返回tmp即可。这一点在例题5中也有体现。
小插曲2:
newhead = p3 = (ListNode*)malloc(sizeof(ListNode));此处 newhead 和 p3 是指向同一块内存区域
所以说,对p3进行修改会作用到newhead上,与移除链表元素示例中的代码不同,动态内存开辟函数会将同一个地址传递给这两个参数,对这两个变量进行解引用或者结构体成员访问操作时,实质是在同一个地址上进行操作,因此两个变量会一同变化。
证明如下:
5、链表分割:
示例:
思路:
用动态内存函数创建两个结构体指针:smallhead 与 bighead,遍历链表,将小于x的赋到 smallhead 中,大于 x 的赋到 bighead 中,最后将 smallhead 的尾节点与 bighead 的头节点连接。如下图所示
需要注意的点:若 bighead 的尾节点不指向NULL,会存在环形链表的情况,即死循环,如下图所示
代码:
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};*/
class Partition {
public:
ListNode* partition(ListNode* pHead, int x) {
// write code here
ListNode* smallhead,*bighead;
smallhead = (ListNode*)malloc(sizeof(ListNode));
bighead = (ListNode*)malloc(sizeof(ListNode));
ListNode* stail = smallhead;
ListNode* btail = bighead;
ListNode* pcur = pHead;
while(pcur)
{
if(pcur->val < x)
{
stail->next = pcur;
stail = stail->next;
}
else
{
btail->next = pcur;
btail = btail->next;
}
pcur = pcur->next;
}
btail->next = NULL; //大链表尾节点置为NULL,避免死循环
stail->next = bighead->next;
ListNode* tmp = smallhead->next;
free(smallhead);
free(bighead);
smallhead = NULL;
bighead = NULL;
return tmp;
}
};
6、链表的回文结构:
示例:
思路:
先通过快慢指针找到中间节点 mid,再从中间节点开始,向后逆置链表,最后头尾分别向右向左遍历依次比较判断。如下图所示:
代码:
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};*/
class PalindromeList {
public:
ListNode* findmidnode(ListNode* head) //找到中间节点mid
{
ListNode* fast,*slow;
fast = slow = head;
while(fast && fast->next)
{
fast=fast->next->next;
slow=slow->next;
}
return slow;
}
ListNode* reversenode(ListNode* mid)//从中间节点位置处逆置链表
{
ListNode* n1,*n2,*n3;
n1 = NULL;
n2 = mid;
n3 = mid->next;
while(n2)
{
n2->next = n1;
n1 = n2;
n2 = n3;
if(n3)
n3 = n3->next;
}
return n1;
}
bool chkPalindrome(ListNode* A) {
// write code here
//找中间节点
ListNode* mid = findmidnode(A);
//从中间节点开始逆置
ListNode* ptail = reversenode(mid);
//头尾依次比较
ListNode* pcur = A;
while(ptail)
{
if(pcur->val != ptail->val)
{
return false;
}
pcur = pcur->next;
ptail = ptail->next;
}
return true;
}
};
7、相交链表的判断
示例:
思路:
遍历 A 与 B 两个链表,统计其节点个数,并做差得 gap ,将长的链表先移动 gap 个节点位置,然后遍历每个链表依次比较,指针相等或链表指向NULL;
代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode ListNode;
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
ListNode* p1 = headA;
ListNode* p2 = headB;
int a,b;
a = b = 0;
while(p1) //统计链表 A 中节点个数
{
a++;
p1 = p1->next;
}
while(p2) //统计链表 B 中节点个数
{
b++;
p2 = p2->next;
}
int gap = abs(a - b);
ListNode* longhead = headA;
ListNode* shorthead = headB;
if(a<b)
{
longhead = headB;
shorthead = headA;
}
while(gap--) //让长链表与短链表齐平
{
longhead = longhead->next;
}
while(longhead)
{
if(longhead == shorthead) //判断是存在相同节点
{
return longhead;
}
longhead = longhead->next;
shorthead = shorthead->next;
}
return NULL;
}
8、环形链表Ⅰ
示例:
思路:
通过快慢指针可以判断一个链表是否为环形链表,因为快指针始终可以追上慢指针。假设快指针与慢指针之间得距离为N,每一次移动,两者间的距离减小1,因此最终N总会变为0,即快指针追上慢指针。
代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode ListNode;
bool hasCycle(struct 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;
}
9、环形链表Ⅱ
示例:
思路:
要实现此程序,首先要知道:假设快慢指针第一次相遇的位置为 meet,此时若有一个指针 pcur1 从头开始,每次移动一个节点位置;另一个指针 pcur2 从meet位置开始移动,每次移动一个节点位置,则他们两个必定会在入环节点相遇。
从数学上证明如下图:
代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode ListNode;
ListNode* findmidnode(ListNode* head) //找快慢节点相遇节点处
{
ListNode* fast,*slow;
fast = slow = head;
while(fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
if(fast == slow)
{
return fast; //返回相遇节点
}
}
return NULL; //若为非环状链表 返回null
}
struct ListNode *detectCycle(struct ListNode *head) {
ListNode* meet = findmidnode(head);
if(meet == NULL)
{
return NULL;
}
ListNode* pcur1,*pcur2;
pcur1= head;
pcur2= meet;
while(pcur1 !=pcur2) //二者相遇出即为入环处
{
pcur1 = pcur1->next;
pcur2 = pcur2->next;
}
return pcur1;
}
10、随即链表的复制
示例:
思路:
1:断开原链表连接方式,建立新的连接关系,如下图所示,此处需要开辟新的节点。
2:变更新节点中 random 的指向,如图所示,可以通过新建立的链表关系访问先前节点中的random
3:断开新旧链表间的联系,连接新链表,如图所示:
代码:
/**
* Definition for a Node.
* struct Node {
* int val;
* struct Node *next;
* struct Node *random;
* };
*/
typedef struct Node node;
node* buynewnode(int x) //申请新的节点并初始化节点
{
node* Node = (node*)malloc(sizeof(node));
Node->val = x;
Node->random = Node->next = NULL;
return Node;
}
struct Node* copyRandomList(struct Node* head) {
node* pcur = head;
if(pcur == NULL)
{
return pcur;
}
while(pcur) //尾插新的节点
{
node* newnode = buynewnode(pcur->val);
newnode->next = pcur->next;
pcur->next = newnode;
pcur = newnode->next;
}
pcur = head;
while(pcur) //将新节点的random赋值
{
node* copy = pcur->next;
if(pcur->random != NULL)
{
copy->random = pcur->random->next;
}
pcur = copy->next;
}
pcur = head;
node* newhead = pcur->next;
node* ptail = newhead;
while(ptail->next) //断开新旧节点的关系,画图比较为什么条件为ptail->next;
{
pcur = ptail->next;
ptail->next = pcur->next;
ptail = ptail->next;
}
return newhead;
}