一、移除链表元素
题目链接:
移除链表元素
那么根据题目的要求我们大致明白这道题要做什么,就是将一个链表中,和指定的值相等的元素的节点删除,然后返回删除后的新的链表,然后题目给我们传入的参数是链表的头节点和指定的元素。
解法一:
这里其实很多人学习了前面的单链表的实现的话,是很容易想到的,我们前面学习了,链表删除指定节点的方法,还有查找指定元素的位置。刚刚好在这道题搭配使用,我们可以使用一个函数,将在这个函数中将这两个函数调用,然后直到我们查找指定元素位置的函数返回值为空的时候,那么就说明这个链表中等于这个指定数据的节点已经删除完了,那么就可以返回最新的链表了,如果不为空,那么我们就先进行删除,然后再递归这个函数。
但是我们这么操作,就很容易造成堆栈,然后导致栈溢出,所以我们解这道题就不使用这个方法。
解法二:
在这道题中,其是要求返回新的链表的头节点,并没有要求在原来的链表上进行删除,那么我们可以创建一个新的链表,然后遍历原链表,不相等的元素,那么我们就进行尾插,不过要注意的是,这个新的链表在初始的时候,我们第一次进行尾插,那么此时要让我们表示新链表的指针指向这个节点,然后那个往下走的指针也是,也要指向这个节点,然后在后续的尾插就让另外一个指针进行变化即可,然后完成后,就返回这个头节点即可。
我们提交看看:
我们通过测试用例来分析:
我们发现输出的结果中,还是有最后一个6,那么这是为啥呢?
当pur走到最后一个节点的时候,那么此时,val不符合if的条件,那么其直接就走到了
pur=pur->next这个语句,然后循环结束,那么我们的新链表,再最后的5打印后,其实其5这个节点中,其指向下一节点的指针,还是指向着6这个节点,那么我们这个链表还是会包括6这个节点,所以我们在遍历完后,一定要对尾节点指向下一个节点的指针置为空。
那么我们对尾节点置空,又导致另外一个错误了,那就是当我们的原链表为空的时候,那么我们就对一个空指针进行解引用了,所以我们要使用一个if判断当前的尾指针是否为空,如果不为空,那么我们才进行置空操作。
如下:
可以看到我们这样修改就通过了。
二、合并两个有序链表
题目链接:合并两个有序链表
这个题目和我们上一篇中的合并两个有序数组有点类似,这道题目就是其有两个链表,然后两个链表的元素都是按照升序的情况排序的,然后我们现在要将其合并到一个链表中,且这个新的链表也要为升序的情况。
我们前面在数组的情况下是使用两个指针来完成的,那么这道题我们该如何解决呢?
那么这个题目没有说不可以创建一个新的链表,那么我们可以创建一个新的链表,然后创建两个指针,一个是src1指向第一个链表,一个src2指向第二个链表,然后对其每个节点进行遍历且比较,小的就尾插到新的链表中。那么我们循环的条件是啥呢?
就是当其中一个链表已经遍历完,其元素已经全部尾插到新的链表中的时候,那么此时的比较就可以结束了,然后剩下的没尾插完的链表,我们再插入即可,但是我们此时就不在需要使用循环插入了,我们只需要指向其下一个节点即可。这样就可以找到其剩余的节点了,而且尾节点也是指向空的。
方法如下:
提交看看:
可以看到当链表1为空,链表2不为空的时候,那么此时就会出错,那么我们的链表1为空,那么就直接到最后两个if了,那么第一个if进不去,就到第二个了,然后此时的newlist就为空,那么我们此时是对一个空指针进行解引用了,那么就使得程序错误了。同理当链表2为空,链表1不为空也会出现这种错误。
所以这两种情况我们要特殊处理。
不过我们会发现上面的代码会有很多重复进行的操作,那么我们有没有上面办法可以使得上面的代码变得简洁一些呢?
我们发现上面很多重复的动作都是因为一个特殊情况,就是链表为空的情况导致的。
那么我们可以在开始就创建一个链表,但是这个链表申请的时候是不存储东西的,其头节点是一个空的节点,那么我们就不在需要考虑链表为空的情况了,可以直接在这个链表进行为尾插的操作了,然后在最后我们将这个链表的头节点的下一个节点返回即可。
不过这个空节点,是我们使用函数malloc申请的,那么我们在使用完后,要记得释放掉,不然会造成空间的浪费,我们在释放前就将其下一个节点先保存起来即可。
优化后:
三、反转链表
题目链接:反装链表
题目的要求就是要我们将一个链表反过来,就是其走的方向和原来的反过来,头节点变为尾节点,尾节点变成头节点
解法一:
我们很容易就可以想到的是,我们创建一个新的链表,然后我们遍历原链表,然后将原链表的数据头插到新的链表即可。
代码如下:
解法二:
解法二就是使用指针的方法,在原链表上进行操作,就直接对链表的指向进行修改。那么我们要如何进行修改呢?
我们创建三个指针n1,n2,n3,一个指向空,一个指向链表的头节点,一个指向链表头节点的下一个节点。
然后我们就让这个头节点的指向下一个节点的指针指向n1,也就是n2->next=n1,然后三个指针都往后走一个节点的位置,即n1=n2,n2=n3,n3=n3->next;那么我们再画图就可以知道,此时的n1是在头节点的位置了,然后我们再让n2->next=n1,那么就使得我们第二个节点指向下一个节点的指针指向的是头节点了,那么此时是第二个节点指向第一个节点了,然后我们反复这样操作,指到n2走到尾节点,然后再对这个尾节点进行修改,然后再走一步,那么此时n2就为空了,那么就不在需要修改了。那么此时就结束循环即可。
不过我们看到上面题目的测试用例,会发现有个是空表,因为我们知道我们对于一个空指针进行解引用是错误的,那么为了避免这种错误,我们对于传入的是空链表的情况特殊处理一下。
那么走到最后,我们要返回的是新的链表的头节点,那么此时n2是在新的头节点的后面一个位置了,然后n1是在n2的前一个节点的位置的,那么此时n1的位置刚刚好就是反转后的链表的头节点。
还有就是,我们的循环条件是n2到空的时候,那么n3早就已经为空了,那么我们此时就不可以再对其解引用了,所以我们使用一个if进行处理。
代码如下:
四、链表的中间节点
题目链接:链表的中间节点
解法一:
题目的描述也很简单,就是有一个链表,让我们找他中间的位置。然后如果是偶数个的节点,那么其中间节点就是两个,那么我们取后面的一个。
那么我们很快就可以想到一个思路:
就是先让这个链表进行遍历,然后我们创建一个整型变量,记录其遍历的次数,那么就是这个链表的节点数,然后我们将这个节点数除2,那么就是我们的中间节点了。
比如:当前链表为三个节点,那么除二,就是1.5,然后我们是整型数据,那么其结果其实是1,那么就往下走一次就是中间的节点了,现在为4个节点,那么我们走两次就到第三个节点,就是题目要求的那个节点了。
代码如下:
解法二:
我们可以创建两个指针,一个快指针,一个慢指针,我们要找的是中间节点,那么我们的慢指针就一次走两步,慢指针就一次走一步,那么等到快指针走到尾的时候,那么就可以结束了,那么此时慢指针的位置就在链表的中间节点了,不过我们要注意的是。那么我们的循环条件就是当快指针走到尾的时候就结束循环,但是我们会发现,当链表的节点数为偶数的时候,那么此时我们的快指针是最后走到尾节点的时候,还会再往下走一步的,那么我们的快指针此时就为空了,那么此时就要停止循环了。然后奇数个的时候,那么此时我们的快指针就刚刚好在尾节点上,那么此时我们也应该结束循环了,那么此时我们循环结束的条件可以为->next,此时其下一个节点为空,刚刚好可以结束循环。
代码如下:
五、判断链表是不是回文结构
题目链接:链表回文结构
前面我们对于数组,字符串,都有学习过回文结构,也实现过判断其是否为回文结构,所以这里我们不过多介绍回文结构。
下面我们来解题
解法一:
我们如果先不理题目的要求,那么我们很快可以想到的是,遍历链表,将链表的数据存储到一个普通的数组中,然后就可以使用我们前面的方法进行判断了。
但是我们的题目要求我们的空间复杂度为O(1),那么我们如果这样做的话,空间复杂度就为O(n)了,就不符合题目的要求了,但是我们再看题目的后面,其链表的长度小于等于900,那么我们一次给够900个空间,那么空间复杂度不就是O(1)了吗。
代码如下:
我们知道上面的题目,其给出了这个链表的最大的长度,那么我们的空间直接给最大,那么其空间复杂度就直接为O(1)了,那么如果其并没有给出这个情况的话,那么我们这么操作是达不到要求的。那么我们下面看一个可以达到要求的解法。
解法二:
我们链表不能直接进行比较就是因为我们的链表是无法通过当前节点找到前面的节点,只能找到下一个节点,不能和数组那么直接通过下标这样进行比较。
不过我们发现回文结构的话,我们从中间位置开始,往两边走,其都是一样的,那么我们上面有道题不就是寻找链表的中间节点吗,但是我们该如何从中间节点往前访问呢?只能说中间节点是头节点才可以进行遍历了呀。哎,我们上上道题就是将链表进行反转,那么我们可以在中间节点这个位置,将后面的部分进行反转,然后再和前面的节点进行比较,不就刚刚好吗。
代码如下: