203
自解
给你一个链表的头节点 head
和一个整数 val
,请你删除链表中所有满足 Node.val == val
的节点,并返回 新的头节点 。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* removeElements(struct ListNode* head, int val){
while(head != NULL && head->val == val)
{
//判断表头元素是否为val,是着表头已到下一个
head = head->next;
}
struct ListNode *beginnig = head;
while(beginnig != NULL)
{
//判断下一个结构体的元素是不是val,是则把下一个删除
while(beginnig->next != NULL && val == beginnig->next->val)
{
beginnig->next= beginnig->next->next;
}
//指向下一个结构体
beginnig = beginnig->next;
}
return head;
}
我的想法是,首先判断标头元素是否要删除,如果删除则表头下移一个。然后以第一个不删除的结构体为表头,对后面的元素进行遍历。如果相等则对后一个元素删除,不等,着将遍历结构体指向下一个结构体。
官方解
循环法:
struct ListNode* removeElements(struct ListNode* head, int val) {
struct ListNode* dummyHead = malloc(sizeof(struct ListNode));
dummyHead->next = head;
struct ListNode* temp = dummyHead;
while (temp->next != NULL) {
if (temp->next->val == val) {
temp->next = temp->next->next;
} else {
temp = temp->next;
}
}
return dummyHead->next;
}
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/remove-linked-list-elements/solution/yi-chu-lian-biao-yuan-su-by-leetcode-sol-654m/
来源:力扣(LeetCode)
时间复杂度O(n):需要便利整个链表。
空间复杂度O(1)
迭代法
struct ListNode* removeElements(struct ListNode* head, int val) {
if (head == NULL) {
return head;
}
head->next = removeElements(head->next, val);
return head->val == val ? head->next : head;
}
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/remove-linked-list-elements/solution/yi-chu-lian-biao-yuan-su-by-leetcode-sol-654m/
来源:力扣(LeetCode)
递归法比较难理解,以最后一层递归为例,head2 = removeElements(head->next, val)一定等于NULL,在return时,只需要考虑val想不想等,相等则删除掉head2,返回NULL,不相等则返回head2,这时,head1->next == head2, 然后判读head1的val相等吗,相等则删除head1,返回head2,不相等则返回head1。这个递归的思想,就是返回的永远是最新的一个不相等的节点,然后对上一级递归节点进行质检,若相等,则删除,不等则返回自身。
时间复杂度:O(n)遍历整个链表。
空间复杂度:O(n),递归需要调用栈。
总结
这个问题其实十分简单,只是自己把头结点和非头结点给割裂开了,自己其实知道哑结点,没有元素,只代表链表头部,但是自己不敢去运用这个东西,下次记得改正。
其次,就是把问题搞复杂了,现在仔细想想,当前节点的下一个节点的val无非有两种情况:
1. next->val == val 那么下一个节点应该被删除,那么就有temp->next = temp->next->next,这样就把相等的元素删除了,当next为最后一个元素时,就有temp->next = NULL.
2. 无非相等和不相等,不相等则就是else的情况,两种情况在一个while循环里面。每次只能执行一个,当下一个元素不删除时,那么当前节点就要先后移动一个了,即temp = temp->next。若next是最后一个节点,那么temp->next = NULL。这时候只需要设置循环条件为temp->next != NULL就行,这样最后一个元素就会退出。
头结点为空时,就要哑结点的next = NULL同样可以退出循环。
下次别想那么复杂,把两种情况分成太多种了。
237
请编写一个函数,用于 删除单链表中某个特定节点 。在设计函数时需要注意,你无法访问链表的头节点 head ,只能直接访问 要被删除的节点 。
题目数据保证需要删除的节点 不是末尾节点 。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/delete-node-in-a-linked-list
自解
说来惭愧,这道题一时间没有想出来怎么写,只能说被题目给唬住了,我们只能访问我们要删除的节点,无法访问头结点,按照固定的思维,肯定就想,我要删除这个节点,那么我就要找到我这个节点之前的节点,将之前的节点的next给改了,但是这题里面我又找不到之前的节点,那我应该怎么办啊?无从下手
官方解
看了官方的解释之后,有了一点眉目,简单两行代码就能实现,确实傻了。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
void deleteNode(struct ListNode* node) {
node->val = node->next->val;
node->next = node->next->next;
}
这只需要两步,第一步,把要删除的节点的数据替换成下一个节点的数据,然后把要删除的节点的next换成下下个节点。
总结
挺有意思的,好一个借刀杀人,要删除这个,我直接把我换成下一个,然后把下一个给删了。既然不能先删除自己,那就把自己整容成儿子,再假装自己就是儿子来养活孙,神理解。把自己伪装成别人,然后把别人干掉,前一个节点,和后面的节点都不知道这件事,细思极恐!
61
给你一个链表的头节点 head
,旋转链表,将链表每个节点向右移动 k
个位置。
示例 1:
自解
这个题目,我一共有三个思路,每一个思路都是在之前的基础上加以改进的
//当K很大的时候直接完蛋
struct ListNode* rotateRight(struct ListNode* head, int k){
if(head == NULL || head->next == NULL)
{
return head;
}
else
{
struct ListNode* dummyNode = (struct ListNode*) malloc(sizeof(struct ListNode));
for(int i = 0; i<k ; i++)
{
dummyNode->next = head;
while(dummyNode->next->next->next != NULL)
{
dummyNode->next = dummyNode->next->next;
}
dummyNode->next->next->next = head;
head = dummyNode->next->next;
dummyNode->next->next = NULL;
}
return head;
}
}
这是第一种思路,当头结点为空或者只有一个头结点即head->next为空时,就直接返回头结点即可,通过用||或表达式,当判断第一个表达式为真时,会直接结束判断,不会判断第二个式子。
然后创建一个哑结点,哑结点的next指向head,然后通过哑结点找到倒数第二个节点,这个节点的下一个节点,就是右移一次的头结点了,这个节点就成了尾节点。这时的head还是上一次的头结点,将他设置成最后一个节点的下一个节点,然后将head设置成最后一个节点,再通过哑结点修改倒数第二个节点为右移后的最后一个节点。就完成了一次移动。循环移动k次即可。
但是这种解法有个很大的缺陷,当K很大的时候,你得移动很多次,一个链表一共只有3个元素,要你移动20000次,很多无用功。因此,为了改善这一点,可以先求出链表长度,然后用K对长度取余,因为移动一个链表长度就还原了。所以有了一下的改进代码:
// 要改进,用K对链表长度取余
struct ListNode* rotateRight(struct ListNode* head, int k){
if(head == NULL || head->next == NULL)
{
return head;
}
else
{
struct ListNode* dummyNode = (struct ListNode*) malloc(sizeof(struct ListNode));
dummyNode->next = head;
int count = 1;
while(head->next != NULL)
{
head = head->next;
count++;
}
head = dummyNode->next;
for(int i = 0; i< k%count ; i++)
{
dummyNode->next = head;
while(dummyNode->next->next->next != NULL)
{
dummyNode->next = dummyNode->next->next;
}
dummyNode->next->next->next = head;
head = dummyNode->next->next;
dummyNode->next->next = NULL;
}
return head;
}
}
与思路1差不多,只是多了一个链表遍历求得链表长度,然后取余,再按照同样的步骤依次挪动就行。
但是到了这里,发现任然可以继续改进,既然我已经知道了链表的长度了,K取余就能知道要移动多少步,那我可以用长度-移动的步数,就能得到第几个元素是移动后的最后一个元素了。不用一次一次的去移动。
所以有了思路3,先通过移动head指正遍历链表,得到链表长度,并且最后head会指向最后一个元素,设置head->next = dummyNode->next就能将最后一个节点与当前的头结点连接。然后将head设置成当前的头结点,通过循环找到移动后的最后一个节点,先将最后一个节点的下一个节点,即移动后的头结点,设置成dummyNode->next。然后将其next设置成NULL,再返回 dummyNode->next就是移动后的头结点了。
struct ListNode* rotateRight(struct ListNode* head, int k){
struct ListNode* dummyNode = (struct ListNode*) malloc(sizeof(struct ListNode));
dummyNode->next = head;
if(head == NULL || head->next == NULL)
{
return head;
}
else
{
int count = 1;
while(head->next != NULL)
{
head = head->next;
count++;
}
head->next = dummyNode->next;
head = dummyNode->next;
for(int i = 1; i<count - k%count ; i++)
{
head = head->next;
}
dummyNode->next = head->next;
head->next = NULL
return dummyNode->next;
}
}
总结
实际上就是考虑到一个移动链表长度还原,然后从指定位置断开就好了,只不过自己一开始就没想到,走了不少弯路而已。