链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域,最后一个节点的指针域指向null。
- 单链表中的指针域只能指向节点的下一个节点。
- 双链表:每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。
(既可以向前查询也可以向后查询) - 循环链表,顾名思义,就是链表首尾相连。
链表中的节点在内存中不是连续分布的 ,而是散乱分布在内存中的某地址上。
要注意链表的节点是如何定义的,要能手撕。
Python定义链表
class ListNode:
def __init__(self, val, next=None):
self.val = val
self.next = next
203. 移除链表元素
哈哈不会写链表,之前上学就不会写。
直接看答案了。
语法:原来
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
这个是说已经定义好了不用再定义了的意思,我还以为这只是个提示呢
看完答案,真是好简单的语法,虽然有些好奇不用虚拟头结点会是什么样呢?
第二遍写:
算法理解错误:要明白dummy_head是头节点,cur是目前节点,这就是我们一直在操作cur节点但最后返回的是dummy_head的原因。
语法错误:Python中没有NULL,只有None
第三遍写:
算法理解错误:最后忘记把虚拟头结点去掉了,应该返回的是dummy_head.next
第四遍写正确。
隔了几天再来看,其实考的是链表的不同操作如何实现。大概抄了一遍,不打算背了,实际用可能会用LinkedList类,先把时间放在常考题目上。
看起来简单!
常考题目。
算法要点:这里没有使用虚拟头结点。想到前几天群里也有人问这个事情:“一般涉及到增删改操作,用虚拟头结点会方便很多。如果只用查的话,用不用都差别不大。”
算法技巧,常见解题思路:
这里一共有pre cur temp三个指针,所以查了一下这个解法为什么叫双指针:
双指针技巧可细分分为两类,一类是快慢指针,一类是左右指针。
前者解决主要解决链表中的问题,比如典型的判定链表中是否包含环、反转链表、找链表的中间节点、删除链表的倒数第 N 个结点;也用来解决数组中的问题,如移动/移除元素、删除有序数组中的重复项。
后者主要解决数组(或者字符串)中的问题,比如二分查找,滑动窗口。
那么这一题用到的就是快慢指针。
可以养成这样的思路:
链表 → 快慢指针
数组 → 左右指针
坦白讲我一开始的思路是获取链表长度,然后从最后一个开始处理。不过看起来大家都解法都是从链表的第一个开始往后处理。回家问问为什么不能那样写。
算法要点:注意都是先操作然后移动指针
第二遍写双指针:正确
这道题递归快一点点,但是内存消耗多很多。
在其他问题里,递归并不一定比双指针更快,双指针在很多线性扫描问题中更有效率。
递归的内存消耗大,主要是因为递归调用会占用调用栈的内存,且需要同时处理每次递归的结果。
第二遍写递归:
语法错误:递归写法是self.___(...)
为什么这里要加self? Java好像直接写方法名就可以了。
第三遍写递归:
注意cur == None不要写成cur == 0。
第四遍写递归没什么大问题了。