203.移除链表元素
建议:本题最关键是要理解虚拟头结点的使用技巧,这个对链表题目很重要。
题目:删除链表中等于给定值 val 的所有节点。
示例 1: 输入:head = [1,2,6,3,4,5,6], val = 6 输出:[1,2,3,4,5]
思路
这里以链表 1 4 2 4 来举例,移除元素4。
还要说明一下,就算使用C++来做leetcode,如果移除一个节点之后,没有手动在内存中删除这个节点,leetcode依然也是可以通过的,只不过,内存使用的空间大一些而已,但建议依然要养成手动清理内存的习惯。
这种情况下的移除操作,就是让节点next指针直接指向下下一个节点就可以了。那么因为单链表的特殊性,只能指向下一个节点,刚刚删除的是链表的中第二个,和第四个节点,那么如果删除的是头结点又该怎么办呢?
这里就涉及如下链表操作的两种方式:
- 直接使用原来的链表来进行删除操作。
- 设置一个虚拟头结点在进行删除操作。
来看第一种操作:直接使用原来的链表来进行移除。
移除头结点和移除其他节点的操作是不一样的,因为链表的其他节点都是通过前一个节点来移除当前节点,而头结点没有前一个节点。
所以头结点如何移除呢,其实只要将头结点向后移动一位就可以,这样就从链表中移除了一个头结点
这样移除了一个头结点,是不是发现,在单链表中移除头结点 和 移除其他节点的操作方式是不一样,其实在写代码的时候也会发现,需要单独写一段逻辑来处理移除头结点的情况。
那么可不可以 以一种统一的逻辑来移除 链表的节点呢。
其实可以设置一个虚拟头结点,这样原链表的所有节点就都可以按照统一的方式进行移除了。
来看看如何设置一个虚拟头。依然还是在这个链表中,移除元素1。
这里来给链表添加一个虚拟头结点为新的头结点,此时要移除这个旧头结点元素1。这样是不是就可以使用和移除链表其他节点的方式统一了呢?
最后呢在题目中,return 头结点的时候,别忘了 return dummyNode->next;
,这才是新的头结点
C代码如下:
struct ListNode* removeElements(struct ListNode* head, int val) {
typedef struct ListNode ListNode;
//创建虚拟头结点
ListNode* dummyHead=malloc(sizeof(ListNode));
dummyHead->next=head;
//创建一个curr节点
ListNode* curr=dummyHead;
while(curr->next){
if(curr->next->val==val){
//创建一个中间节点tmp
ListNode* tmp=curr->next;
curr->next=curr->next->next;
free(tmp);
}
else{
curr=curr->next;
}
}
head=dummyHead->next;
free(dummyHead);
return head;
}注意点:注意需要手动释放被删除的节点
707.设计链表
题目:
在链表类中实现这些功能:
- get(index):获取链表中第index个节点的值。如果索引无效,则返回-1。
- addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
- addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
- addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
- deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。
思路
这道题目设计链表的五个接口:
- 获取链表第index个节点的数值
- 在链表的最前面插入一个节点
- 在链表的最后面插入一个节点
- 在链表第index个节点前面插入一个节点
- 删除链表的第index个节点
可以说这五个接口,已经覆盖了链表的常见操作,是练习链表操作非常好的一道题目
206.反转链表
题目:反转一个单链表。
示例: 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL
思路
如果再定义一个新的链表,实现链表元素的反转,其实这是对内存空间的浪费。
其实只需要改变链表的next指针的指向,直接将链表反转 ,而不用重新定义一个新的链表,如图所示:
之前链表的头节点是元素1, 反转之后头结点就是元素5 ,这里并没有添加或者删除节点,仅仅是改变next指针的方向。
那么接下来看一看是如何反转的呢?
双指针法
反转链表比较简单,很容易想到,从前向后遍历链表,然后把每个结点的 next 指向前一个结点就好。
具体实现中,首先需要两个指针,一个 cur 指针在遍历到每个结点进行操作,一个 pre 指针指向 cur 的前一个结点。其次,由于 cur->next 指向 pre,导致原来的 cur 的下一个结点失去引用,所以还需要一个指针 temp 用来保存后一个结点。
C代码如下:
struct ListNode* reverseList(struct ListNode* head) {
struct ListNode* cur=head;
struct ListNode* pre=NULL;
while(cur){
//保存下一个节点
struct ListNode* tmp=cur->next;
//反转结点
cur->next=pre;
//更新pre和cur指针
pre=cur;
cur=tmp;
}
return pre;
}
注意点:想明白是先移动pre指针还是先移动cur指针。
写在最后:链表的第一天的题目还算比较简单,从代码中反映出的思路也很简洁。