代码随想录 day03 第二章 链表part01

day03 第二章 链表part01


今日任务

●  链表理论基础

●  203.移除链表元素

●  707.设计链表

●  206.反转链表

1. 链表理论基础

  1. 基础结构

    • 通过指针串联的结构
      • 两部分组成
        1. 数据域:存储该链表数据类型的具体数据
        2. 指针域:存储下一个节点的指针【长度固定】
    • 除了最后一个节点以外,每个节点都会存储下一个节点的位置信息,最后一个节点指向null
    • 一定能定位到头节点【否则就是无法追踪的链表,要么被GC,要么导致Mem leak】
  2. 常见类型

    1. 单链表

      1. 图片来自代码随想录官网

    2. 双链表

      1. 相较普通单链表多了一个指向上一个节点的指针

      2. 查询效率优于单链表

      3. 不用从头节点开始遍历查找

      4. 图片来自代码随想录官网

    3. 循环链表

      1. 将链表的首位相连成一个环

      2. 图片来自代码随想录官网

  3. 在内存中的存储方式

    1. 不是连续分布的【区别于数组】
    2. 散乱分布在内存在,因为有下一个节点的地址,可以通过读取内存位置来精准找到下一个节点
  4. 定义实现

    1. C/CPP

      // 单链表
      struct ListNode {
          int val;  // 节点上存储的元素
          ListNode *next;  // 指向下一个节点的指针
          ListNode(int x) : val(x), next(NULL) {}  // 节点的构造函数
      };
      
    2. Golang

      // 单链表
      type ListNode struct {
          Val int
          Next *ListNode
      }
      
  5. 常见操作

    1. 删除 O(1)

      1. 将指针从 被删除节点的前一个节点 直接连接 被删除节点的后一个节点 即可

      2. 图片来自代码随想录官网

    2. 添加 O(1)

      1. 将指针从 待添加节点的前一个节点 直接连接 待添加节点待添加节点 指向 后一个节点

      2. 图片来自代码随想录官网

    3. 注意

      1. 删除、添加操作本身是O(1), 但是查询的过程是 O(n)

2. 移除链表元素

关联 leetcode 203.移除链表元素

  • 思路

    • 普通单链表始终保持找到 线头【头节点即可】
    • 使用虚拟节点保证 原链表头节点和其他节点的操作一致,降低复杂度
  • 使用虚拟头节点后,比较操作都是一样的了

    • 都是比较 虚拟头节点的Next节点值与 target 是否匹配 ===》一定是一个循环操作
  • 代码实现

    func removeElements(head *ListNode, val int) *ListNode {
    	
    	// 虚拟头指针, 线头的作用,不可变的固定头节点位置
    	vHead := ListNode{
    		Next: head,
    	}
    
    	// 操作指针
    	cHead := &vHead
    
    	 //链表不为空
    	for cHead.Next != nil { 
    		if cHead.Next.Val == val {
    			cHead.Next = cHead.Next.Next
    			continue 
    		}
    
    		// 下一位不是目标值,则操作指针向后顺移一位
    		cHead = cHead.Next
    		 
    
    	}
    
    	return vHead.Next
    }
    

3. 设计链表

关联 leetcode 707.设计链表

  • 注意避坑

    • 链表节点在操作前一定要注意
      • 单链表节点插入时,一定是把新节点先指向链表内节点,否则会导致游离节点产生,插入新节点后的所有节点都会丢失,因为后续的节点无法定位
      • 新节点插入时,先把新节点的后续节点关联上,再断开原有链表链接
  • 代码实现:

    type MyLinkedList struct {
    	vHead *LinkNode
    	Size  int
    }
    
    // 单链表
    type LinkNode struct {
    	Val  int
    	Next *LinkNode
    }
    
    // 初始化链表,分配虚拟头节点,链表长度
    func Constructor() MyLinkedList {
    	return MyLinkedList{
    		vHead: &LinkNode{
    			Val:  0,
    			Next: nil,
    		},
    		Size: 0,
    	}
    }
    
    // 注意 index 从0开始,链表的index,因此要注意 排除 虚拟头节点
    // 考虑极端情况,只有一个 真实头节点
    func (this *MyLinkedList) Get(index int) int {
    	if index < 0 || index >= this.Size {
    		return -1
    	}
    	cHead := this.vHead.Next
    	for index > 0 {
    		cHead = cHead.Next
    		index--
    	}
    	return cHead.Val
    }
    
    func (this *MyLinkedList) AddAtHead(val int) {
    	oHead := this.vHead.Next // 原始头节点
    
    	insertNode := LinkNode{
    		Val:  val,
    		Next: oHead,
    	}
    	this.vHead.Next = &insertNode
    	this.Size++
    }
    
    func (this *MyLinkedList) AddAtTail(val int) {
    	cHead := this.vHead
    	for cHead.Next != nil {
    		cHead = cHead.Next
    	}
    	cHead.Next = &LinkNode{Val: val}
    	this.Size++
    }
    
    func (this *MyLinkedList) AddAtIndex(index int, val int) {
    	if index > this.Size {
    		return
    	}
    	if index == this.Size {
    		this.AddAtTail(val)
    		return
    	}
    	if index == 0 {
    		this.AddAtHead(val)
    		return
    	}
    
    	cHead := this.vHead
    	for index > 0 {
    		cHead = cHead.Next
    		index--
    	}
    	insertNode := LinkNode{Val: val}
    	insertNode.Next = cHead.Next
    	cHead.Next = &insertNode
    	this.Size++
    }
    
    func (this *MyLinkedList) DeleteAtIndex(index int) {
    	if this.Get(index) == -1 {
    		return
    	}
    
    	cHead := this.vHead
    	for index > 0 {
    		cHead = cHead.Next
    		index--
    	}
    	cHead.Next = cHead.Next.Next
    	this.Size--
    }
    

4. 反转链表

关联 leetcode 206.反转链表

  • 双指针解法

    • 注意循环终止条件
      • 终止条件用 cur 不用 cur->next 考虑极端,原链表是一个空链表,则cur->next 会报空指针异常
    • 注意节点丢失问题
      • 如果在保存 cur节点地址前, 先改动了 cur节点的前一个节点的Next指向,则 cur节点地址将会丢失,导致内存泄漏
    // 单链表节点定义
    type ListNode struct {
    	Val  int
    	Next *ListNode
    }
    
    func reverseList(head *ListNode) *ListNode {
    		// 双指针解法
    	var pre *ListNode // 空节点
    
    	cur := head
    
    	//循环终止条件
    	for cur != nil {
    		next := cur.Next
    		cur.Next = pre // 反转
    		// 移动指针
    		pre = cur
    		cur = next
    
    	}
    
    	return pre
    }
    
  • 递归解法【由双指针可推导】

    • 使用递归替代循环
      • 无论递归还是循环都是 重复一样的劳动
      • 递归的内存占用会高于循环,计算资源使用优于循环
    // 单链表节点定义
    type ListNode struct {
    	Val  int
    	Next *ListNode
    }
    
    // 反转目的, 将当前节点的Next 指向前一个节点
    // cur: 当前节点
    // pre: 当前节点的前置节点
    func reverse(cur, pre *ListNode) *ListNode {
    	if cur == nil {
    		return pre
    	}
    	// 当前节点的下一个节点
    	next := cur.Next
    	// 反转指向
    	cur.Next = pre
    	// 移动节点
    	pre = cur
    	cur = next
    	//	进入下一次递归
    	return reverse(cur, pre)
    }
    
    func reverseList(head *ListNode) *ListNode {
    	//	 递归解法
    	return reverse(head, nil)
    }
    

参考引用网站: 代码随想录 (programmercarl.com)

  • 14
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值