算法通关村第一关 | 青铜教程学习总结
1.理解Java是如何构造出链表的
链表的定义:单向链表像个铁链一样,元素之间互相连接,包含多个节点,每个节点有一个指向后继元素的next指针,最后一个元素的next指针指向null
链表的通常定义:
public class ListNode {
private int data;
private ListNode next;
public ListNode(int data) {
this.data = data;
}
public int getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
public ListNode getNext() {
return next;
}
public void setNext(ListNode next) {
this.next = next;
}
}
LeetCode创建链表
public class ListNode {
public int val;
public ListNode next;
ListNode(int x) {
val = x;
next = null;
}
}
ListNode listnode=new ListNode(1);
2.链表增加元素,首部、中间和尾部分别会有什么问题,该如何处理?
2.1 链表增加元素,如果增加的元素在首部
(1) 创建一个新的结点newNode
(2) newNode.next = head
(3) head = newNode
2.2 链表增加元素,如果增加的元素在中间
由于链表是单向的,不能返回去找,所以使用cur.next的值为目标值的时候判断,进行插入元素操作,这是最常用的策略
(1) 创建一个新元素newNode
(2) 若要在7的前面插入,cur.next = node(7)就应该停下来,cur.val = 15
(3) newNode.next = node(15).next
(4) node(15).next = newNode
注意(3)(4)顺序不能颠倒
由于每一个节点都有一个next,因此执行了node(15).next = newNode时候,15和7之间的连线就自动断开了,后续就丢掉了,无法连接
2.3 链表增加元素,如果增加的元素在尾部
(1) 创建一个新元素newNode
(2) node(40).next = newNode
/**
* 链表插入
* @param head 链表头节点
* @param nodeInsert 待插入节点
* @param position 待插入位置,从1开始
* @return 插入后得到的链表头节点
*/
public static Node insertNode(Node head, Node nodeInsert, int position) {
if (head == null) {
//这里可以认为待插入的结点就是链表的头结点,也可以抛出不能插入的异常
return nodeInsert;
}
//已经存放的元素个数
int size = getLength(head);
if (position > size +1 || position < 1) {
System.out.println("位置参数越界");
return head;
}
//表头插入
if (position == 1) {
nodeInsert.next = head;
// 这里可以直接 return nodeInsert;还可以这么写:
head = nodeInsert;
return head;
}
Node pNode = head;
int count = 1;
//这里position被上面的size被限制住了,不用考虑pNode=null
while (count < position - 1) {
pNode = pNode.next;
count++;
}
nodeInsert.next = pNode.next;
pNode.next = nodeInsert;
return head;
}
3.链表删除元素,首部、中间和尾部分别会有什么问题,该如何处理?
3.1 链表删除元素,如果删除的元素在首部
(1) head = head.next
3.2 链表删除元素,如果删除的元素在中间
(1) cur.next = cur.next.next
3.3 链表删除元素,如果删除的元素在尾部
(1) 找到删除节点的前驱节点,提前一个位置判断 cur.next = 40
(2) cur.next = null
/**
* 删除节点
* @param head 链表头节点
* @param position 删除节点位置,取值从1开始
* @return 删除后的链表头节点
*/
public static Node deleteNode(Node head, int position) {
if (head == null) {
return null;
}
int size = getListLength(head);
//思考一下,这里为什么是size,而不是size+1
if (position > size || position <1) {
System.out.println("输入的参数有误");
return head;
}
if (position == 1) {
//curNode就是链表的新head
return head.next;
} else {
Node cur = head;
int count = 1;
while (count < position - 1) {
cur = cur.next;
count++;
}
Node curNode = cur.next;
cur.next = curNode.next;
//上面两行可以直接简化成:cur.next=cur.next.next
}
return head;
}
4.双向链表是如何构造的,如何实现元素的插入和删除。
4.1 双向链表的构造
双向链表顾名思义就是既可以向前,也可以向后。有两个指针的好处自然是移动元素更方便
class DoubleNode {
public int data; //数据域
public DoubleNode next; //指向下一个结点
public DoubleNode prev;
public DoubleNode(int data) {
this.data = data;
}
//打印结点的数据域
public void displayNode() {
System.out.print("{" + data + "} ");
}
}
4.2 双向链表元素的插入
头部插入
//头部插入
public void insertFirst(int data) {
DoubleNode newDoubleNode = new DoubleNode(data);
if (first == null) {
last = newDoubleNode;
} else {//如果不是第一个结点的情况
//将还没插入新结点之前链表的第一个结点的previous指向newNode
first.prev = newDoubleNode;
}
newDoubleNode.next = first;
//将新结点赋给first(链接)成为第一个结点
first = newDoubleNode;
}
尾部插入
//尾部插入
public void insertLast(int data) {
DoubleNode newDoubleNode = new DoubleNode(data);
if (first == null) {
first = newDoubleNode;
} else {
newDoubleNode.prev = last;
last.next = newDoubleNode;
}
//由于插入了一个新的结点,又因为是尾部插入,所以将last指向newNode
last = newDoubleNode;
}
中间插入
public void insertAfter(int key, int data) {
DoubleNode newDoubleNode = new DoubleNode(data);
DoubleNode current = first;
while ((current != null) && (current.data != key)) {
current = current.next;
}
//若当前结点current为空
if (current == null) {
if (first == null) {
first = newDoubleNode;
last = newDoubleNode;
} else {
//2、找不到key值,则在链表尾部插入一个新的结点
last.next = newDoubleNode;
newDoubleNode.prev = last;
last = newDoubleNode;
}
} else {//第3种情况,找到了key值,分两种情况
if (current == last) {
//1、key值与最后结点的data相等
newDoubleNode.next = null;
last = newDoubleNode;
} else {
//2、两结点中间插入
newDoubleNode.next = current.next;
current.next.prev = newDoubleNode;
}
current.next = newDoubleNode;
newDoubleNode.prev = current;
}
}
4.3 双向链表元素的删除
头部删除
//删除首元素
public DoubleNode deleteFirst() {
DoubleNode temp = first;
//若链表只有一个结点,删除后链表为空,将last指向null
if (first.next == null) {
last = null;
} else {
//若链表有两个及以上的结点 ,因为是头部删除,则first.next将变成第一个结点,其previous将变成null
first.next.prev = null;
}
//将first.next赋给first
first = first.next;
//返回删除的结点
return temp;
}
尾部删除
//从尾部删除结点
public DoubleNode deleteLast() {
DoubleNode temp = last;
//如果链表只有一个结点,则删除以后为空表,last指向null
if (first.next == null) {
first = null;
} else {
//将上一个结点的next域指向null
last.prev.next = null;
}
//上一个结点称为最后一个结点,last指向它
last = last.prev;
//返回删除的结点
return temp;
}
中间删除
public DoubleNode deleteKey(int key) {
DoubleNode current = first;
//遍历链表寻找该值所在的结点
while (current != null && current.data != key) {
current = current.next;
}
//若当前结点指向null则返回null,
if (current == null) {
return null;
} else {
//如果current是第一个结点
if (current == first) {
//则将first指向它,将该结点的previous指向null,其余不变
first = current.next;
current.next.prev = null;
} else if (current == last) {
//如果current是最后一个结点
last = current.prev;
current.prev.next = null;
} else {
//当前结点的上一个结点的next域应指向当前的下一个结点
current.prev.next = current.next;
//当前结点的下一个结点的previous域应指向当前结点的上一个结点
current.next.prev = current.prev;
}
}
return current; //返回
}