链表类算法题简介及思路总结(包含递归法的理解使用)

部分内容来源于 https://www.programmercarl.com/

0.引言

链表是非常基础的一种数据结构,在删除和插入方面比传统的数组结构更加的简单。

1.链表基础结构

1.1 什么是链表

链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。

链接的入口节点称为链表的头结点也就是head。

1.2 链表分类

  • 单链表。即只有一个指向后一个节点的指针。

  • 双链表。每个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。

  • 循环链表。链表首尾相连。

1.3 链表存储方式

数组是在内存中是连续分布的,但是链表在内存中可不是连续分布的。

链表是通过指针域的指针链接在内存中各个节点。

所以链表中的节点在内存中不是连续分布的 ,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。

2.链表题常用思路

2.1 双指针法

在链表题中,也可以考虑双指针法。

2.2 递归法

链表特别适合使用递归的算法,因为很多链表的算法题都需要对节点进行一个个的判断。遇到这种类型的题目就可以考虑递归的方法。

底下是一个比较通用的递归代码模板,不同于其他的递归教程,这套流程针对性很强,可以很容易套进题目之中,具体流程和代码如下。

首先要明确递归的流程,在链表递归函数运行过程中,主要分为三个部分,1.每个节点依次进入递归函数,2.遇到边界条件后进行返回,3.一直返回到递归起点。

接下来,针对以上流程,进行递归代码的分解讲解。

首先,在编写之前,先确定好,以上3个部分中的13过程中,需要在哪个部分进行逻辑的处理。

  • 在1过程需要进行逻辑处理,及从头到尾,每次进入递归函数,都会进行一个逻辑的处理,此时处理逻辑代码要写在递归函数之前。如果要将此次处理的结果传给后续节点,此时可以将变量设置为递归函数的变量进行传递,这样在每次往后递归之前都会先运行逻辑函数,并将得到的结果往后传递。
  • 在3过程进行逻辑处理,即从后往前进行逻辑处理,此时逻辑代码要写在递归函数之后,返回值之前。 如果要将每次处理的结果传递给前面的节点,此时可以将节点为返回值,这样在每次递归函数运行结束后,会运行逻辑代码,并将返回值返回。

其次,在确定好逻辑代码过程后,可以实际开始编写代码了,第一步就是要写好退出条件,在链表中即表现为当前节点为空的情况,即已经到达最尾部了。

最后,补全最开始确定的逻辑代码,就可以完成整个递归函数的编写了。

func help(head *ListNode,n int)int{
    //停止条件
    if head==nil{
        return n,false,nil
    }
    //往下递归前做的事情,n为往下传入的值
    num:=help(head.Next,n)
    //递归返回时做的事情
    if num==0{
      //做一些处理
    }
    return num //向上传递
    }

1.1 题目训练

1.1.1 反转链表

206. 反转链表 - 力扣(LeetCode)

Solution

按照以上的逻辑,可以使用两种方法,一种是在第一部分(从前往后)进行逻辑的处理,一种是在第三部分(从后往前)。

第一部分进行逻辑处理的时候,我们在首先要存好当前遍历的节点的下一个节点的地址,因为进行反转之后就找不到地址了。然后进行正常的反转即可,代码如下。

第三部分进行逻辑处理时,比较麻烦,首先反转的时候,我们需要前一个节点的地址,而从前往后传递只能作为递归函数的变量进行传递,所以我们设置递归函数其中一个变量为pre,进行传递。然后我们还需要返回最后一个节点,而从最后一个节点往前传值需要作为返回值传递,所以我们设置一个返回值即可,代码如下。

Code

//从前往后的逻辑处理
func reverseList(head *ListNode) *ListNode {
    return help(nil, head)
}

func help(pre, head *ListNode)*ListNode{
    if head == nil {
        return pre
    }
    next := head.Next
    head.Next = pre
    return help(head, next)
}

//从后往前的逻辑处理
func reverseList(head *ListNode) *ListNode {

    return help(head,nil)
}

func help(head *ListNode,pre *ListNode)*ListNode{
    if head==nil{
    return nil
    }
    result:=help(head.Next,head)
    if head.Next==nil{
        result=head
    }
    head.Next=pre
    return result
}

2.3 迭代法

利用迭代法,可以很快的对每个节点进行判断和操作。

2.4 虚拟头结点

链表的一大问题就是操作当前节点必须要找前一个节点才能操作。这就造成了,头结点的尴尬,因为头结点没有前一个节点了。每次对应头结点的情况都要单独处理,所以使用虚拟头结点的技巧,就可以解决这个问题

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

雨雨不怕雨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值