每日算法学习记录
链表基础理论
链表是计算机系统的基础数据结构,是逻辑数据结构。在人眼看起来,链表都是接在一起的,自然而然,好像和数组没什么两样,但实际上节点各自的地址可以没有任何规律和顺序,可以“随心所欲”。单链表是下面这个样子,本文默认讨论的链表都是单链表。
这就要求链表的每个节点除了储存数据,还要储存它下一个节点的信息,也就是下一个节点的地址。
如果要用Java定义一个链表节点ListNode
类,它应该长成这样:
class ListNode{
int val;
ListNode next;
ListNode(int val, ListNode next) {};
}
因此,我们在定义链表的时候,只需要手里有一个头节点head
,通过一个指针p
,就可以遍历一整个链表了。遍历操作可以是p = p.next
,当p
为空的时候,就说明遍历到尾节点了,因为再没有下一个节点了。
203、移除链表节点
思路分析
要删除一个节点,那么我们只需要将当前节点有关的信息都从链表中移除就可以了。那么当前节点的信息储存在链表的哪个位置呢?答:上一个节点。
因此我们要做的,必然是修改上一个节点的next
域,改成谁?当然是改成下一个节点了。
那这时候就会发现一个问题:如果要删头节点,头节点哪有上一个节点啊?
要处理这个问题,一般有两种方法,一个是单独处理删头节点的情况,一个是它没有上一个节点,那我就给它创造一个虚拟节点。那么博主推荐大部分都采用的第二个虚拟头节点法。
dummyHead = new ListNode(-1, head)
那么我们可以用图像展现一下删除过程:
盘点一下需要完成的操作逻辑:
- 输入一个链表的头节点
head
- 定义一个虚拟头节点
dummyHead
,这个节点的next
赋值为head
- 定义一个指针
p
,初始值赋值为dummyHead
- 判断
p.next
所指向的节点是否为空,是的话跳到步骤8
- 比较
p.next
所指向的节点的数据是否等于要删除的数据值,否的话跳到步骤7
- 获取
p.next
所指向的节点的下一个节点的地址,赋值给p.next
,返回步骤2
- 将
p
指针挪向下一个节点 - 返回
dummyHead
的下一个节点
代码展现
public ListNode removeElements(ListNode head, int val) {
head = new ListNode(0, head);
ListNode p = head;
while (p.next != null) {
if (p.next.val == val) {
p.next = p.next.next;
} else {
p = p.next;
}
}
return head.next;
}
707、设计链表
思路分析
设计一个自定义链表,可以写单链表,也可以写双链表。本文仅分析单链表写法,但也给出了双链表的代码展现。首先,自定义链表需要包含以下方法:
方法 | 描述 |
---|---|
MyLinkedList | 初始化 MyLinkedList 对象。 |
get(index) | 获取链表中第 index 个节点的值。如果索引无效,则返回-1 。 |
addAtHead(val) | 在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。 |
addAtTail(val) | 将值为 val 的节点追加到链表的最后一个元素。 |
addAtIndex(index,val) | 在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index 小于0 ,则在头部插入节点。 |
deleteAtIndex(index) | 如果索引 index 有效,则删除链表中的第 index 个节点。 |
我们仍然需要先定义这个链表的节点类型ListNode
。
get
方法只需要从头节点开始,往后简单遍历,等到计数达到index
,即可返回节点的数据值。
addAtIndex
、deleteAtIndex
方法都需要区分对头节点的操作和对非头节点的操作,因此同样可以使用虚拟头节点进行简化。
因此MyLinkedList
类需要包含两个成员属性:虚拟头节点head
、链表大小size
代码展现
//单链表
class MyLinkedList{
ListNode head;
int size;
private class ListNode {
ListNode next;
int val;
public ListNode() {
}
public ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
public MyLinkedList() {
head = new ListNode(-1, null);
size = 0;
}
public int get(int index) {
if(index<0||index>=size){
return -1;
}else{
ListNode p=head;
while(index-->=0){
p=p.next;
}
return p.val;
}
}
public void addAtHead(int val) {
addAtIndex(0,val);
}
public void addAtTail(int val) {
addAtIndex(size,val);
}
public void addAtIndex(int index, int val) {
if(index<0||index>size){
return;
}
ListNode p=head;
while(index-->0){
p=p.next;
}
ListNode tmp=p.next;
if(tmp==null){
p.next=new ListNode(val,null);
}else{
p.next=new ListNode(val,tmp);
}
size++;
}
public void deleteAtIndex(int index) {
if(index<0||index>=size){
return;
}
ListNode p=head;
while(index-->0){
p=p.next;
}
p.next=p.next.next;
size--;
}
}
双链表则要比单链表多维护一个尾节点,但是也因此使得遍历、插入、删除操作更为高效、简单。
//双链表
class MyLinkedList{
final private ListNode _head;
final private ListNode _tail;
private int _size;
private class ListNode{
int val;
ListNode next,prev;
public ListNode(int val){
this(val,null,null);
}
public ListNode(int val,ListNode next,ListNode prev){
this.val=val;
this.next=next;
this.prev=prev;
}
public ListNode(){}
}
public MyLinkedList() {
_head=new ListNode(0);
_size=0;
_tail=new ListNode(0,null,_head);
_head.next=_tail;
}
private ListNode get(int index,boolean mark){
if(index>=_size||index<0){
return null;
}
ListNode p;
if(index<=_size>>1){
p=_head;
for(int i=0;i<=index;i++){
p=p.next;
}
}else{
p=_tail;
for(int i=_size-1;i>=index;i--){
p=p.prev;
}
}
return p;
}
public int get(int index) {
ListNode p = get(index, true);
if(p==null) return -1;
return p.val;
}
public void addAtHead(int val) {
ListNode tmp=_head.next;
_head.next=new ListNode(val,tmp,_head);
tmp.prev=_head.next;
_size++;
}
public void addAtTail(int val) {
ListNode tmp = _tail.prev;
_tail.prev=new ListNode(val,_tail,tmp);
tmp.next=_tail.prev;
_size++;
}
public void addAtIndex(int index, int val) {
if(index==_size){
addAtTail(val);
}else if(index<_size){
ListNode p=get(index,true);
if(p==null) return;
p.prev.next=new ListNode(val,p,p.prev);
p.prev=p.prev.next;
_size++;
}
}
public void deleteAtIndex(int index) {
ListNode p=get(index,true);
if(p==null) return;
p.prev.next=p.next;
p.next.prev=p.prev;
_size--;
}
}
24、两两交换链表中的节点
思路分析
少废话直接上图,这个图很清晰得展现了两两交换链表节点的3个修改步骤。
代码展现
public ListNode swapPairs(ListNode head) {
if(head==null) return head;
head=new ListNode(0,head);
ListNode pre=head;
ListNode cur=head.next;
while(cur!=null&&cur.next!=null){
pre.next=cur.next;
cur.next=pre.next.next;
pre.next.next=cur;
pre=pre.next.next;
cur=pre.next;
}
return head.next;
}
总结和思考
以上就是今天的算法学习记录,我们通过分析思路和实现代码的方式,解决了这道题目。希望对大家的算法学习有所帮助,谢谢阅读!