LeetCode 707:设计链表

题目描述:设计链表

解题思路:

一、单指针实现
首先我们回顾链表的数据结构,它至少要有一个头指针,指向链表的第一个Node,数据和指向下一结点的指针存放在Node中。

//头指针
private Node head;

//静态内部类Node,用于存放实际的数据
private static final class Node{
	private int val;

	private Node next;

	public Node(int val){
		this.val = val;
	}
}

1、addAtHead(int val) 头部插入新结点
如下图,我们在链表头部插入值为4的新结点
(1)、当链表元素为0的时候,新结点相当于链表的第一个结点,直接创建新结点node,头指针指向新结点node即可
在这里插入图片描述
(2)、当链表元素>0的时候,创建新结点,将新结点的next指针指向head,接着让头指针指向新结点node即可。
在这里插入图片描述
总体来说,头部插入新结点一共分为3步:
1)、根据参数构建新结点node
2)、将新结点node的next指针指向head
3)、将头指针head指向新结点node
代码仅供参考:

/**
* 链表头部插入结点
* @param val 待插入的值
*/
public void addAtHead(int val){
	Node node = new Node(val);
	node.next = head;
	head = node;
}

2、addAtTail(int val) 尾部插入新结点
尾部插入新结点我们还是分两种情况来讨论
(1)、如果链表为空的时候,与头部插入新结点情况类似,创建新结点,将头指针指向新结点即可
在这里插入图片描述
(2)、链表元素个数大于0的情况,为了在链表尾部插入新结点,首先要找到链表的尾部结点。声明一个指针cur,初始化为head头指针,遍历cur指针,当cur.next == null的时候,我们认为cur指针指向的结点即为当前链表的尾结点,之后创建新结点,将cur的next指针指向新结点即可。
在这里插入图片描述
总的来说,链表尾部插入新结点共分为2步:
1)、找到链表的尾部结点
2)、将尾部结点的next指针指向新结点
注意链表为空的情况
代码仅供参考:

public void addAtTail(int val){
	Node node = new Node(val);
	Node cur = head;
	while(cur!=null){
		if(cur.next == null){
			cur.next = node;
			return;		
		}
		cur = cur.next;
	}
	//链表为空时,头指针直接指向新结点
	head = node;
}

3、addAtIndex(int index,int val) 指定索引index前插入新结点
我们按照索引index的取值范围分3种情况讨论:
(1)、index小于等于0时,等价于在头部插入新结点,不再赘述
(2)、index大于0并且小于等于链表长度。
这种情况下下标指针i从1开始,同样声明一个指向头指针head的指针prev(表示下标为i的结点的前置结点),初始阶段cur=head;i=1。可以看出下标索引i比指针prev快一步,什么意思呢?就是说我们在链表的迭代过程中,只要i == index,我们新建结点node,将node的next指针指向prev的next指针,同时让prev的next指针指向新结点node即可。
1)、下图演示了index>0 && index < size(链表长度)的情况,将新结点4插入到index=2前面:
在这里插入图片描述
2)、下图演示了 index 等于 size 的情况,将新结点4插入到index=3前面:
在这里插入图片描述
(3)、index大于链表长度,什么都不做即可

综上所述 在指定索引前插入新结点,重点要找到索引index前置结点的位置,整体上分为这几步:
1)、判断index是否小于等于0,直接采用链表头插入新结点的方式
2)、index位于0和链表长度之间时。迭代prev指针,直到索引i == index,创建新结点,令node.next = prev.next 并且 prev.next = node即可
代码参考如下:

public void addAtIndex(int index , int val){
	if(index <= 0){
		addAtHead(val);
		return;
	}
	Node prev = head;
	int i = 1;
	for(;prev!=null;prev=prev.next,i++){
		if(i == index){
			Node node = new Node(val);
			node.next = prev.next;
			prev.next = node;
			return;
		}
	}
}

4、deleteAtIndex(int index) 删除指定索引index处的结点
仍然根据index的取值分几种情况来讨论
(1)、index 小于 0 的情况,直接返回
(2)、index 等于 0 的情况,相当于删除头结点,直接将头指针指向头指针的next结点即可
1)、声明指针 cur = head
2)、取cur的next结点
3)、将head指向cur的next结点即可
在这里插入图片描述
(3)、index 大于 0 的情况,先寻找索引i == index的前置结点,之后将前置结点的next指针指向i所代表结点的next指针即可。
1)、下图所示,删除i = 1所在的结点:
在这里插入图片描述
2)、下图所示,删除i == 3,因为超过了链表长度的最大索引值2,所以什么都不做:
在这里插入图片描述
综上所述,删除指定索引处的结点,首先要根据index取值来区分不同的场景:
1)、index < 0 直接返回
2)、index == 0 将head指针指向head.next
3)、index > 0 先找到索引结点的前置结点prev,注意if终止条件为i==index && prev.next!=null(将索引越界的情况一并处理)
参考代码如下所示:

public void deleteAtIndex(int index){
	if(index < 0){
		return;
	}
	if(index == 0 && head != null){
		Node cur = head.next;
		head = cur;
		return;
	}
	Node prev = head;
	int i = 1;
	for(;prev != null;prev=prev.next,i++){
		if(i == index && prev.next != null){
			Node cur = prev.next;
			prev.next = cur.next;
			return;
		}
	}
}

5、get(int index) 查询指定索引index处结点的值
依然分三种情况来讨论:
(1)、index < 0 返回空
(2)、index 大于等于0 并且小于 链表长度-1;从head结点依次迭代,当指针i == index终止即可。
下图演示查找链表长度范围内的元素,即i=2的情况:
在这里插入图片描述
(3)、index 大于等于链表长度
下图演示index 越界情况,即查找index = 3的元素,遍历完链表均找不到下标i == index的情况,直接返回-1
在这里插入图片描述
综上所述,查找指定索引处的元素,分为下面几步:
1)、声明下标i = 0,当前结点指针cur = head;
2)、当cur不为空时直接迭代,cur = cur.next 直到 cur为空,同时i++
3)、当i==index时直接返回cur结点的值即可
参考代码如下:

public int get(int index){
	if(index < 0){
		return -1;
	}
	Node cur = head;
	int i =0;
	for(;cur!=null;i++,cur=cur.next){
		if(i == index){
			return cur.val;
		}
	}
	return -1;
}

总结:
单指针实现的链表,由于大部分情况下需要从头开始遍历链表,其平均时间复杂度为O(N)

操作时间复杂度说明
addAtHeadO(1)
addAtTailO(N)需要遍历链表找到尾结点
addAtIndexO(N)需要遍历链表找到指定下标结点的前置结点
deleteAtIndexO(N)需要遍历链表找到指定下标结点的前置结点
getO(N)需要遍历链表找到指定下标结点的前置结点

二、头尾双指针实现

//头指针
private Node head;

//尾指针 
private Node tail;

//静态内部类Node,用于存放实际的数据
private static final class Node{
	private int val;

	private Node next;

	public Node(int val){
		this.val = val;
	}
}

1、addAtHead(int val) 头部插入新结点
在头部插入新结点,分两种情况来讨论:
(1)、当是个空链表时,这时头指针和尾指针均指向空,创建完新结点后,我们将头指针和尾指针均指向新结点即可,如下图所示,向空链表中插入新结点1:
在这里插入图片描述
(2)、当链表有结点时,由于是在头部插入新结点,只需要将新结点的next指针指向head的next指针,同时将head指向node即可,如下图所示,在拥有一个结点的链表头部插入值为2的新结点:
在这里插入图片描述
综上所述:从头部插入新结点分这几步:
1)、新建结点node
2)、将新结点node的next指针指向head
3)、将head指向新结点node
4)、如果tail为空,则将tail指向新结点node
参考代码如下:

public void addAtHead(int val){
	Node node = new Node(val);
	node.next = head;
	head = node;
	if(null == tail){
		tail = node;
	}
}

2、addAtTail(int val) 尾部插入新结点
依然分两种情况讨论:
(1)、链表为空时,同头部插入结点,不再赘述
(2)、链表不为空时,分为如下几步:
1)、新建结点node
2)、直接得到尾结点tail指针,将tail的next指向结点node
3)、将尾指针指向结点node
如下图所示,在拥有一个元素1的链表尾部,插入新结点2:
在这里插入图片描述
参考代码如下:

public void addAtTail(int val){
	Node node = new Node(val);
	//空链表时将头指针指向新结点
	if(null == head){
		head = node;
	}else{
		//链表不为空时将尾结点的next指针指向新结点
		tail.next = node;
	}
	tail = node;
}

3、addAtIndex(int index,int val) 指定位置前插入新结点
按照index的取值范围分三种情况来讨论:
(1)、当index小于等于0时,相当于从头部插入结点,不再赘述

(2)、当index 大于0 分两种情况:

1)、一种是 index>0 && index <= size(链表长度),假设在已知有3个元素的链表中index=2前插入新结点
Step1:声明下标i=1,声明前置指针prev=head;
在这里插入图片描述
Step2:判断i 是否等于index,如果不相等,令i++,prev=prev.next,直到prev!=null 并且i==index
在这里插入图片描述
Step3:新建结点node,让node结点的next指针指向prev的next指针
在这里插入图片描述
Step4:将prev的next指针指向node结点
在这里插入图片描述
注意: 当index为链表的尾结点时,不要忘记调整tail指针指向node,如下所示:
在这里插入图片描述
2)、index大于链表长度时,什么都不做

综上所述:指定位置前插入新结点,分两种情况:
(1)、index <= 0 直接参考头部插入结点
(2)、index > 0 时
1)、声明前置指针prev=head,i = 1
2)、当cur!=null时,迭代prev,同时累加下标变量i
3)、当i == index时 创建新结点node
4)、将node的next指针指向prev的next指针
5)、将prev的next指针指向node
6)、当prev == tail时,令 tail = node
参考代码如下:

public void addAtIndex(int index,int val){
	if(index <= 0 ){
		addAtHead(val);
		return;
	}
	Node prev = head;
	int i = 1;
	for(;prev!=null;prev=prev.next,i++){
		if(i == index){
			Node node = new Node(val);
			node.next = prev.next;
			prev.next = node;
			if(prev == tail){
				tail = node;
			}
			return;
		}
	}
}

4、deleteAtIndex(int index) 删除指定位置的结点
依然分三种情况来讨论:
(1)、当index < 0 什么都不做
(2)、当index == 0 ,表示删除头结点
1)、链表为空时,什么都不做
2)、链表不为空时,声明指针prev=head,将head设置为prev.next即可,如下图所示:
在这里插入图片描述
如果链prev tail 表示尾结点,需要重置tail结点为head,如下图所示:
在这里插入图片描述
(3)、当index > 0时,删除指定位置的元素
声明前置指针prev=head,i = 1;当prev!=null一直迭代,当i
index && prev.next != null时停止,如下图所示在有3个结点的链表index=3的为之前插入结点:
在这里插入图片描述
注意: 当prev.next == tail时,表示移除的是原链表的尾结点,需要重置tail指针
参考代码如下:

public void deleteAtIndex(int index){
	if(index < 0){
		return;
	}
	if(index == 0 && head != null){
		Node node = head;
		head = node.next;
		// 表示原链表仅有一个元素,重置尾结点tail指针
		if(null == head){
			tail = head;
		}
		return;
	}
	int i = 1;
	Node prev = head;
	for(;prev!=null;prev=prev.next,i++){
		if(i == index && prev.next != null){
			Node node = prev.next;
			if(prev.next == tail){
				tail = prev;
			}
			prev.next = node.next;
			return;
		}
	}
}

5、get(int index) 获取指定位置的结点值
同单指针获取指定位置结点值类似,不再赘述
参考代码如下:

public int get(int index){
	if(index < 0){
		return -1;
	}
	int i = 0;
	Node cur = head;
	for(;cur!=null;cur=cur.next,i++){
		if(i == index){
			return cur.val;
		}
	}
	return -1;
}

首尾双指针实现的链表,其平均时间复杂度为O(N),仅仅addAtTail由于增加了尾指针,不需要遍历链表获取尾结点

操作时间复杂度说明
addAtHeadO(1)
addAtTailO(1)
addAtIndexO(N)需要遍历链表找到指定下标结点的前置结点
deleteAtIndexO(N)需要遍历链表找到指定下标结点的前置结点
getO(N)需要遍历链表找到指定下标结点的前置结点
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值