一、基础知识
根据代码随想录上面介绍基础知识点,我们需要清楚的点:
1 链表和数组之间的差异;
2 链表结构体怎么定义;
3 链表的类型,一般使用较多的是单向链表,双向链表也需要了解。
4 链表的相关操作:插入 删除 搜索;
5 链表操作时候,一般head节点不作为数据存储单元,只是作为链表头存在,所以在设计时候,需要注意。
6 链表的相关题目中 ,头节点的插入或者删除,都需要特别注意。
链表和数组差异:
二、链表删除
给定一个链表 删除指定的value,设计到链表的两个操作方式:(根据代码随想录上面思路)
- 直接使用原来的链表来进行删除操作。
- 设置一个虚拟头结点在进行删除操作。
所以考虑时候,不使用虚拟头结点的话,就需要从头开始遍历,链表需要注意的是,头结点的value等于target,那么直接指向下一个节点,free掉当前的节点;
/**注意条件先后,head不为空,才能取值**/
while(head && head->val == val)
{
ListNode* tmp = head;
head = head->next;
free(tmp);
}
/**如果head->val 一直都是target, 那么一直往下移动,直到不等于target value**/
然后head更新到第一个不等于target的节点,继续遍历,直到链表结束。
/**非头结点情况**/
listNode* cur = head;
while(cur && cur->next)
{
if(cur->next->val == val)
{
//free
listNode* tmp = cur->next;
cur->next = cur->next->next;
free(cur);
}
else
{
cur = cur->next;
}
}
另外一个思路: 如果增加了虚拟头节点,那么从下一个节点开始遍历
listNode* pre = (listNode*)malloc(sizof(listNode));
pre->next = head;
listNode* cur = pre;
while(cur && cur->next)
{
if(cur->next->val == val)
{
listNode* tmp = cur->next;
cur->next = cur->next->next;
free(tmp);
}
else
{
cur = cur->next;
}
}
return pre->next;
三、创建链表
链表创建过程,遇到的问题感觉特别多,需要注意几个方面:
1 head作为头节点,不参与存储;!!!特别重要
2 插入和删除时候,头部处理
3 插入是在index之前插入,且是0 base;
首先,创建链表,申请一个头节点。
其次,头部插入,每次申请一个新节点,作为新的头结点;
然后,尾部插入,找到最末端节点,申请新节点,插入末端,并指向NULL;
然后,获取index位置节点value,需要考虑的是,index是否会超界,如果超了,return -1;
int myLinkedListGet(MyLinkedList* obj, int index) {
//head除去开始 base0
MyLinkedList* cur = obj->next;
for(int i = 0; i < index; i++)
{
if(cur == NULL)//一旦cur为空,说明index是超过了链表的长度的
{
return -1;
}
cur = cur->next;
}
/**需要判断,刚好index次后,cur=NULL退出**/
return cur==NULL ? -1: cur->val;
/**下面是代码随想录的写法:**/
// MyLinkedList *cur = obj->next;
// for (int i = 0; cur != NULL; i++){
// if (i == index){
// return cur->val;
// }
// else{
// cur = cur->next;
// }
// }
// return -1;
}
然后,插入value到index位置,需要判断头结点位置,以及删除index位置的元素:
void myLinkedListAddAtIndex(MyLinkedList* obj, int index, int val) {
/**添加到index之前位置**/
//indx == 0 添加到头
if(index == 0)
{
myLinkedListAddAtHead(obj, val);
return;
}
//index >0
MyLinkedList* cur = obj->next;
for(int i = 1; cur != NULL; i++)
{
if(index == i)
{
MyLinkedList* newnode = (MyLinkedList*)malloc(sizeof(MyLinkedList));
newnode->next = cur->next;
cur->next = newnode;
newnode->val = val;
return;
}
else
{
cur = cur->next;
}
}
}
void myLinkedListDeleteAtIndex(MyLinkedList* obj, int index) {
/**删除 idx = 0,头结点都没有参与存储**/
if(index == 0)
{
MyLinkedList *tmp = obj->next;
if (tmp != NULL){
obj->next = tmp->next;
free(tmp);
}
return;
}
MyLinkedList* cur = obj->next;
for(int i = 1; cur && cur->next; i++)
{
if(index == i)
{
MyLinkedList* tmp = cur->next;
if(tmp)
{
cur->next = tmp->next;
free(tmp);
}
return;
}
else
{
cur = cur->next;
}
}
}
三、翻转链表
翻转链表,首先想的是用另外一个链表保存,然后再输出,类似于栈的处理;
另外,双指针法也可以解决,需要虚拟一个节点,pre和cur两个,每一次遍历,翻转一下:
struct ListNode* reverseList(struct ListNode* head) {
//双指针法
// struct ListNode* pre = NULL;
// struct ListNode* cur = head;
// //遍历
// while(cur)
// {
// struct ListNode* tmp = cur->next;
// cur->next = pre;
// //pre->next = NULL;
// pre = cur;
// cur = tmp;
// }
// return pre;
//递归
//1 边界条件:
if(head == NULL) return NULL;
if(head->next == NULL) return head;
struct ListNode * end = reverseList(head->next);//找到递归的最后一层 往前推
head->next->next = head;
head->next = NULL;
return end;
}
还有就是使用递归,递归不太好理解,这样去思考的:找到递归的最后一层,往前推(迭代是正向推),递归的最后一层的处理,调用完成后,后面回复函数现场时候,所有的函数处理都是这样的操作。