前言
这是算法学习链表篇的第二篇Blog,由于个人的懒癌+开学琐事,所以拖延了超级久才发布第二篇学习博客,学习是一个持续、长久的过程,希望以后自己能战胜拖延hhh,话不多说,我们继续深究链表,深入学习叭~ 本博客参考了代码随想录,特此声明
一、链表设计大师
我们在链表操作时往往会使用他人写好的函数,我们直接调用即可,这样操作固然方便,但是这不利于我们理解、学习链表,所以,我们不妨来写5个常见的链表操作,你准备成为大师了么?
Leetcode 707设计链表 设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:val 和 next。val 是当前节点的值,next 是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。假设链表中的所有节点都是 0~index 的。 你需要实现:
- get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
- addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
- addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
- addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
- deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。
时间和能力有限,仅仅手撕理解单链表的5大操作
单链表的定义:
class ListNode{
int val;
ListNode next;
public ListNode(){
}
public ListNode(int val){
this.val=val;
}
}
操作细节定义:
class MyLinkedList {
int size;
ListNode head;
public MyLinkedList() {
size=0;
head=new ListNode();
}
public int get(int index) {
if(index<0||index>=size)
return -1;
ListNode currentNode=head;
for(int i=0;i<=index;i++){
currentNode=currentNode.next;
}
return currentNode.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>size)
return;
if(index<0)
index=0;
size++;
ListNode prev=head;
for(int i=0;i<index;i++){
prev=prev.next;
}
ListNode temp=new ListNode(val);
temp.next=prev.next;
prev.next=temp;
}
public void deleteAtIndex(int index) {
if(index<0||index>=size){
return;
}
size--;
ListNode prev=head;
for(int i=0;i<index;i++){
prev=prev.next;
}
prev.next=prev.next.next;
}
}
发现 其实大多数人学习链表时,多多少少都会被指针弄的天花乱坠,我也不例外,那么如何理解代码呢?
for(int i=0;i<=index;i++){
currentNode=currentNode.next;
}
如何理解currentNode=currentNode.next? 我们从右往左解读:意为将当前结点的下一结点赋为当前结点
ListNode temp=new ListNode(val);
temp.next=prev.next;
prev.next=temp;
如何理解temp.next=prev.next?(temp为新建结点,prev为前驱结点) 我们从左往右解读:当前结点的下一结点指向的是前驱结点的下一结点,即如图所示:
如何理解prev.next=temp? 我们依旧从左往右解读:prev前驱结点的下一结点是新结点,就是平移了一条线:
这样,我们就完成了一个结点的新增,也就是说,在左端没有.next操作下我们从右往左解读会更好理解,而在左端存在.next操作下我们从左往右解读编写也许会更加容易理解,当然啦,这只是小编的经验,如果存在错误或者有更好的方法欢迎指正!
二、小试牛刀
1.反转链表
leetcode 206题: 题目: 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。 参考Carl哥的动图,以及上面个人的理解,关键代码终于能自己写了!
class Solution {
public ListNode reverseList(ListNode head) {
ListNode prev=null;
ListNode current=head;
ListNode temp=null;
while(current!=null){
temp=current.next;
current.next=prev;
prev=current;
current=temp;
}
return prev;
}
}
我们不妨参考一下上面的理解,发现可以理解通,并加以训练
2.两两交换链表中的结点
leetcode 24题 给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
示例:
使用虚拟头结点两两交换的方案:
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode dummyNode=new ListNode();
dummyNode.next=head;
ListNode prev=dummyNode;
while(prev.next!=null&&prev.next.next!=null){
ListNode temp=head.next.next;
prev.next=head.next;
head.next.next=head;
head.next=temp;
prev=head;
head=head.next;
}
return dummyNode.next;
}
}
3.删除链表的倒数第N个结点
leetcode 19:删除链表的倒数第N个结点 给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。 进阶:你能尝试使用一趟扫描实现吗
示例:
如何解决此链表问题: 我们通常可以想到一种解决方案,那就是两次扫描链表,第一次记录长度n,然后将需要删除的结点的位置标明:n-N+1,再从第二次遍历中删除该结点即可:
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummyNode=new ListNode();
dummyNode.next=head;
int length=0;
ListNode prev=dummyNode;
ListNode currentNode=head;
while(currentNode!=null){
length++;
currentNode=currentNode.next;
}
currentNode=head;
int index=length-n+1;
int i=0;
while(currentNode!=null){
if(i==index-1){
prev.next=currentNode.next;
currentNode.next=null;
}else{
i++;
}
prev=currentNode;
currentNode=currentNode.next;
}
return dummyNode.next;
}
}
或者是使用快慢指针,这种思想只需要遍历一次
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummyNode=new ListNode();
dummyNode.next=head;
ListNode slow=dummyNode;
ListNode fast=dummyNode;
while(n-->0){
fast=fast.next;
}
ListNode prev=null;
while(fast!=null){
prev=slow;
slow=slow.next;
fast=fast.next;
}
prev.next=slow.next;
slow.next=null;
return dummyNode.next;
}
}
4.链表相交
leetcode面试题:02.07 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。
示例:
题目理解:题目中要求的相等并不是简单的数值相等,而是结点,指针的相等,即链表相交后它们后面的结点都相等,那么,我们需要将链表较长的一端先走完多余的部分,因为这样才能同步接下来的一些步骤:
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
int len1=0,len2=0;
ListNode tempA=headA;
ListNode tempB=headB;
while(tempA!=null){
len1++;
tempA=tempA.next;
}
while(tempB!=null){
len2++;
tempB=tempB.next;
}
tempA=headA;
tempB=headB;
int subNum=len1-len2;
if(subNum<0){
subNum=-subNum;
ListNode temp=tempA;
tempA=tempB;
tempB=temp;
}
while(subNum-->0){
tempA=tempA.next;
}
while(tempA!=null){
if(tempA==tempB){
return tempA;
}
tempA=tempA.next;
tempB=tempB.next;
}
return null;
}
}
总结
链表篇到这里基本就已经完结了,后续或许有新的心得或者好玩的题目会继续补充,本文中若有错误也欢迎大家指正!
|