算法-链表

1.链表理论基础

链表是一种通过指针串联在一起的线性结构,每个节点由两部分组成,一个是数据域,一个是指针域(存放指向下一个节点的指针)。
最后一个节点的指针指向null(空指针)。
链接的入口点称为列表的头结点也就是head。
在这里插入图片描述

链表的类型

单链表

单链表中的节点只能指向节点的下一个节点。

双链表

每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。
双链表 既可以向前查询也可以向后查询。
在这里插入图片描述

循环链表

链表首尾相连(最后一个节点的指针指向第一个节点)
在这里插入图片描述

链表的存储方式

数组是在内存中是连续分布的,但链表在内存中不是连续分布的。
链表是通过指针域的指针链接在内存中各个节点。
链表中的节点在内存中不是连续分布的 ,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。

链表的操作

删除节点

在这里插入图片描述将C节点的next指针 指向E节点。
在C++里最好是再手动释放这个D节点,释放这块内存。
其他语言有自己的内存回收机制,就不用自己手动释放。

添加节点

在这里插入图片描述

数组和链表

在这里插入图片描述

数组在定义的时候,长度就是固定的,如果想改动数组的长度,就需要重新定义一个新的数组。

链表的长度可以是不固定的,并且可以动态增删, 适合数据量不固定,频繁增删,较少查询的场景。

2.移除链表元素

LeetCode

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。
示例1:
输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]

直接使用原来的链表来进行删除操作。

若头结点等于所给值,需要移除头结点,移除头结点和移除其他节点操作不同,因为移除其他节点都是通过上一个节点来移除当前节点的(上一个节点指针指向下一个节点),而头结点没有上一个节点。
则移除头结点的操作比较特殊,将头结点向后移动一位,就移除了头结点,就有了新的头结点(返回)。则在写代码时需要单独写一段逻辑去移除头结点。

设置一个虚拟头结点在进行删除操作。

设置一个虚拟头结点,在删除头结点时可以通过虚拟头节点,将虚拟头结点的指针指向头结点的下一个节点即删除头结点,新的头结点仍为虚拟头结点的下一个节点(ret.next)

var removeElements = function(head, val) {
    const ret = new ListNode(0, head);
    let cur = ret;
    while(cur.next) {
        if(cur.next.val === val) {
            cur.next =  cur.next.next;
            continue;
        }
        cur = cur.next;
    }
    return ret.next;
};

3.设计链表

leetcode

设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性: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 个节点。

使用虚拟头结点进行操作

4.反转链表

leetcode

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

示例1:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]

双指针法
再定义一个新链表实现链表元素反转,会造成空间的浪费。

改变链表节点的next指针指向,指针指向与原来相反,就可以实现链表的反转,不用再定义一个新的链表。
在这里插入图片描述首先定义一个cur指针,指向头结点,定义一个pre指针,初始化为null。
反转时,使用tmp指针保存cur->next节点,接下来开始将cur->next指针进行反转,使cur->next指针指向pre(null节点),完成了头指针的反转。
接下来使pre为cur节点,cur为tmp节点(分别向后移动了一位),然后重复以上反转操作,tmp保存cur->next节点,使cur->next指向pre节点。直至cur指针指向null,循环结束,链表反转完成。

//双指针法
var reverseList =function(head){
	if(!head ||!head.next) return head;
	let tmp=null,pre=null,cur=head;
	while(cur){ //cur为空时循环结束
		tmp=cur.next; //将cur.next节点保存起来,将要改变cur.next的指向
		cur.next=pre; //反转指针
		pre=cur; //更新pre指针
		cur=tmp;	//更新cur指针
	}
	return pre; //当cur为最后一个节点时,更新pre指针,pre指向最后一个元素,即指向反转链表的头指针
}

5.两两交换链表中的节点

leetcode
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。

不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

示例1:
输入: head=[1,2,3,4]
输出:[2,1,4,3]

示例2:
输入: head=[]
输出:[]

示例3:
输入: head=[1]
输出:[1]

虚拟头结点
使用虚拟头结点,不需要对头结点进行单独处理。
两两交换元素,主要是搞清楚交换元素的操作先后顺序。
在这里插入图片描述

var swapPairs = function (head) {
  let ret = new ListNode(0, head), temp = ret;
  while (temp.next && temp.next.next) {
    let cur = temp.next.next, pre = temp.next;
    pre.next = cur.next;
    cur.next = pre;
    temp.next = cur;
    temp = pre;
  }
  return ret.next;
};

6.删除链表的倒数第N个节点

leetcode
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

示例1:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]

示例2:
输入:head = [1], n = 1
输出:[]

双指针法(快慢指针法)
虚拟头结点

定义fast和slow指针,初始时都为虚拟头结点。
使fast比slow指针先走n+1步,然后同时移动,使fast移动到末尾null时,slow正好走到倒数第n个节点的上一个节点,可以通过使slow节点的指针指向倒数第n个节点的下一节点而移除倒数第n个节点。
在这里插入图片描述

var removeNthFromEnd = function(head,n){
	const ret=new ListNode(0,head);
	let fast=slow=ret;
	while(n--) fast=fast.next;
	while(fast){
		fast=fast.next;
		slow=slow.next;
	};
	slow.next=slow.next.next;
	return ret.next;
};

7.链表相交

leetcode

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。

示例:
输入:listA = [4,1,8,4,5], listB = [5,0,1,8,4,5]
输出:8

思路:
求两个链表交点节点的指针,交点不是数值相等,而是指针相等。
假设节点元素数值相等,则节点指针相等。
分别定义两个指针指向两个链表的头结点,求出两个链表的长度,求出两个链表的长度差。
将较长的链表的头结点指针移动到两个链表末节点对齐时较短链表头结点的位置。
移动两个链表的指针,直至指针的值相同,默认为交点。
在这里插入图片描述

function getListLen(head){
    var cur=head;
    var len=0;
    while(cur){
        len++;
        cur=cur.next;
    }
    return len;
} //求链表的长度
var getIntersectionNode = function(headA, headB) {
    var cura=headA,curb=headB,
    lena=getListLen(headA),lenb=getListLen(headB);
    if(lena<lenb){
        [cura,curb]=[curb,cura];
        [lena,lenb]=[lenb,lena];
    } //判断出哪个链表较长
    var i=lena-lenb;
    while(i-->0){
        cura=cura.next;
    }//移动较长的链表的指针到两个链表末尾对齐时较短链表的头结点的位置
    while(cura&&cura!=curb){
        cura=cura.next;
        curb=curb.next;
    }//得到交点返回值,没有交点则返回空指针
    return cura;
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值