基础知识
- 链表
链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。可以分为以下三类:单向链表,双向链表,循环链表。
重点:
链表内还有一种特殊的节点称为哨兵(Sentinel)节点,也叫做哑元( Dummy)节点,它不存储数据,通常用作头尾,用来简化边界判断,如下图所示
2.与数组的区别
Leecode题目
203.移除链表元素
题目分析:
- 移除头节点的操作和其他节点的不同,所以引入哨兵节点(sentinel)简化边界操作。
- 移除链表和移除数组中的元素有相似之处,使用双指针。
class Solution {
public ListNode removeElements(ListNode head, int val) {
ListNode s = new ListNode(-1,head);
ListNode p1 = s; //走在后面(慢指针)
ListNode p2 = p1.next;//走在前面(快指针)
while(p2!=null){
if(p2.val!=val){
//快指针与目标值不同,p1和p2一起向前走
p1 = p1.next;
p2 = p2.next;
}else{
//快指针与目标值同,p1不动,p2向前走
p1.next = p2.next;
p2 = p2.next;
}
}
return s.next;
}
}
注意:
这个和数组的那个有一点的区别是,链表中的快慢指针一直都是
p
1.
n
e
x
t
=
p
2
p1.next=p2
p1.next=p2
代码改进:
满老师yyds,时间复杂度降低
public ListNode removeElements(ListNode head, int val) {
ListNode sentinel = new ListNode(-1, head);
ListNode p1 = sentinel;
ListNode p2;
while ((p2 = p1.next) != null) {
if (p2.val == val) {
p1.next = p2.next;
} else {
p1 = p1.next;
}
}
return sentinel.next;
}
707.设计链表
再次推荐我的满老师添加链接描述
class MyLinkedList {
private Node head;
private int size;
//定义一个节点的内部类
private class Node{
int val;
Node next;
public Node(int val,Node next){
this.val = val;
this.next = next;
}
}
public MyLinkedList(){
size = 0;
head = new Node(-1,null);
}
//注意这里的index>=size
public int get(int index) {
if(index <0||index>=size){ return -1;}
Node p = head;
for(int i=0;i<=index;i++){
p = p.next;
}
return p.val;
}
public void addAtHead(int val) {
head.next = new Node(val,head.next);
size++;
}
public void addAtTail(int val){
Node p ;
for(p = head; p.next !=null; p = p.next){
}
p.next = new Node(val,null);
size++;
}
private Node findByindex(int index){
if(index<-1||index>=size){
return null;
}
Node p = head;
for(int i =0;i<=index;i++){
p=p.next;
}
return p;
}
public void addAtIndex(int index, int val){
Node pre = findByindex(index-1);
if(pre== null){
return;
}
pre.next = new Node(val,pre.next);
size++;
}
//注意这里删除是需要同时满足pre和pre.next 都不是null
//如果pre为null,说明该节点,要么超出size,要么连虚拟节点都不是
//pre.next可能链表为空的情况,如size=0,index =0; pre是虚拟节点,而pre.next是null
public void deleteAtIndex(int index){
Node pre = findByindex(index-1);
if(pre!= null&&pre.next!=null){
pre.next = pre.next.next;
size--;
}else{}
}
}
复习的时候再重写一遍吧,调试了好久才写对
206.反转链表
1.分析
- 直接的思路:再创建一个新的链表,从旧链表依次拿到每个节点,创建新节点添加至新链表头部,完成后新链表即是倒序的(浪费空间)
public ListNode reverseList(ListNode o1) {
ListNode n1 = null; //新链表的节点
ListNode p = o1;
while (p != null) {
n1 = new ListNode(p.val, n1);
p = p.next;
}
return n1;
}
2.改变链表指针的走向
class Solution {
public ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode cur = head;
ListNode temp = null;
while (cur != null) {
temp = cur.next;// 保存下一个节点
cur.next = prev; //改变指针的方向
prev = cur;
cur = temp;
}
return prev;
}
}
- 递归
目的:依次输每一个节点入,改变指针方向,输出最后一个节点
注意:递归终止条件是 c u r r . n e x t = = n u l l curr.next == null curr.next==null,目的是到最后一个节点就结束递归
需要考虑空链表即 p == null 的情况
(参考:满老师)
class Solution {
public ListNode reverseList(ListNode head) {
//当节点的下一个节点为null时,停止迭代;
//如果没有节点直接返回
ListNode p =head;
if(p==null||p.next == null){
return p;
}
//开始递归
ListNode last = reverseList(p.next);
//*****递归函数内部要做事情*****
p.next.next = p;
p.next = null;
//*******
return last;
}
}
单向链表没有 prev 指针,但利用递归的特性【记住了】链表每次调用时相邻两个节点是谁