代码随想录训练营第三天

链表是一种非连续线性数据结构,由节点通过指针域连接。文章介绍了单链表、双链表和循环链表的概念,以及链表的存储方式。在Python和Java等语言中,链表的节点删除和添加是O(1)操作,但查找可能有O(n)时间复杂度。文章还讨论了如何删除和添加节点,以及链表的反转操作。
摘要由CSDN通过智能技术生成

链表

链表理论基础

链表:

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

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

链表的类型:
单链表

上述描述。

注意:单链表中的 指针域 只能 指向节点的下一个节点

双链表

每一个节点有两个指针域一个指向下一个节点一个指向上一个节点

双链表 既可以向前查询也可以向后查询。

循环链表

顾名思义,就是链表首尾相连

循环链表可以用来解决约瑟夫环问题

链表的存储方式

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

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

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

这个链表起始节点为2, 终止节点为7, 各个节点 分布在 内存的不同地址空间上,通过指针串联在一起。

链表的定义

其中,ListNode是链表节点的类名,__init__是类的构造函数,self.val表示节点的值,self.next表示下一个节点的指针。

class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None
        
# 创建链表节点
node1 = ListNode(1)
node2 = ListNode(2)
node3 = ListNode(3)
# 设置链表指针
node1.next = node2
node2.next = node3
# 打印链表
curr = node1
while curr:
    print(curr.val)
    curr = curr.next
# 输出    
1
2
3
链表的操作

例:删除D节点

只要将C节点的next指针 指向E节点就可以了。

那有同学说了,D节点不是依然存留在内存里么?只不过是没有在这个链表里而已。

是这样的,所以在C++里最好是再手动释放这个D节点释放这块内存

其他语言例如Java、Python,就有自己的内存回收机制,就不用自己手动释放了。

添加节点

链表的增添和删除都是O(1)操作,也不会影响到其他节点。

但是要注意,要是删除第五个节点,需要从头节点查找第四个节点通过next指针进行删除操作,查找的时间复杂度是O(n)。

性能分析

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

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

1、移除链表元素(leetcode203)

思路:

以链表 1 4 2 4 来举例,移除元素4。

这种情况下的移除操作,就是让节点next指针直接指向下下一个节点就可以了

那么因为单链表的特殊性,只能指向下一个节点,刚刚删除的是链表的中第二个,和第四个节点,那么如果删除的是头结点又该怎么办呢?

这里就涉及如下链表操作的两种方式:

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

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

第一种操作:直接使用原来的链表来进行移除。

移除头结点和移除其他节点的操作是不一样的,因为链表的其他节点都是通过前一个节点来移除当前节点,而头结点没有前一个节点。

所以头结点如何移除呢,其实只要将头结点向后移动一位就可以,这样就从链表中移除了一个头结点。

依然别忘将原头结点从内存中删掉。{python、java可以省略这步}

这样移除了一个头结点,是不是发现,在单链表中移除头结点 和 移除其他节点的操作方式是不一样,其实在写代码的时候也会发现,需要单独写一段逻辑来处理移除头结点的情况。

可以设置一个虚拟头结点,这样原链表的所有节点就都可以按照统一的方式进行移除了。

来看看如何设置一个虚拟头。依然还是在这个链表中,移除元素1。

这里来给链表添加一个虚拟头结点为新的头结点,此时要移除这个旧头结点元素1。

这样是不是就可以使用和移除链表其他节点的方式统一了呢?

来看一下,如何移除元素1 呢,还是熟悉的方式,然后从内存中删除元素1。

最后呢在题目中,return 头结点的时候,别忘了 return dummyNode->next;, 这才是新的头结点。

1、设置 虚拟节点 dummy_head_next节点 指向 head节点

2、定义cur 临时指针 用来遍历整个链表 cur = dummy_head 因为要删除 head节点,而删除这个节点,只能通过这个节点的前一个节点直接指向 head的next节点,删除head节点。

3、设置了cur临时节点=head节点,要删除cur_next(head)节点,首先要判断cur_next是否为空,如果不是空就一直遍历列表

4、如果cur_next_val的数值等于tarket,就让cur_next = cur_next_next

5、如果没有找到目标值,继续遍历,cur = cur_next

6、遍历结束后,return dummy_head_next,不返回head,是因为head可能被删了。

代码实现
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next

class Solution:
    class Solution:
    def removeElements(self, head: ListNode, val: int) -> ListNode:
        dummy_head = ListNode(next=head) #添加一个虚拟节点 并指向head节点
        cur = dummy_head    # 临时指针指向 dummy_head
        while(cur.next!=None): # 判断 head的节点是否为空
            if(cur.next.val == val):
                cur.next = cur.next.next # 删除cur.next节点
            else:
                cur = cur.next
        return dummy_head.next   # 不返回  head :可能head 节点 被删除
            

2、设计链表(leetcode707)

  1. 反转链表(leetcode206)

思路

      定义一个新的链表,实现链表元素的反转,其实这是对内存空间的浪费。

       其实只需要改变链表的next指针的指向直接将链表反转 ,而不用重新定义一个新的链表。

1、初始化:cur = head pre = null

2、当cur = Null时,遍历结束。注意 当cur 指向pre时, 1 -> 2间没有链接,所以2 -> 1 不能指向,所以需要在没赋值前,创建临时指针temp 指向cur的下一个节点 temp = cur.next

3、赋值:cur.next = pre

4、改变完方向后,pre与cur 指针整体向后移动以为 :pre = cur, cur = temp

5、返回新链表的头节点,pre 指向head节点

代码实现

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    class Solution:
    def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        cur, pre = head, None
        while cur:
            temp = cur.next  # 创建临时指针 = cur的下一个节点,保存节点
            cur.next = pre   # 第一次翻转
            pre = cur       
            cur = temp
        return pre
第二十二天的算法训练营主要涵盖了Leetcode题目中的三道题目,分别是Leetcode 28 "Find the Index of the First Occurrence in a String",Leetcode 977 "有序数组的平方",和Leetcode 209 "长度最小的子数组"。 首先是Leetcode 28题,题目要求在给定的字符串中找到第一个出现的字符的索引。思路是使用双指针来遍历字符串,一个指向字符串的开头,另一个指向字符串的结尾。通过比较两个指针所指向的字符是否相等来判断是否找到了第一个出现的字符。具体实现的代码如下: ```python def findIndex(self, s: str) -> int: left = 0 right = len(s) - 1 while left <= right: if s[left == s[right]: return left left += 1 right -= 1 return -1 ``` 接下来是Leetcode 977题,题目要求对给定的有序数组中的元素进行平方,并按照非递减的顺序返回结果。这里由于数组已经是有序的,所以可以使用双指针的方法来解决问题。一个指针指向数组的开头,另一个指针指向数组的末尾。通过比较两个指针所指向的元素的绝对值的大小来确定哪个元素的平方应该放在结果数组的末尾。具体实现的代码如下: ```python def sortedSquares(self, nums: List[int]) -> List[int]: left = 0 right = len(nums) - 1 ans = [] while left <= right: if abs(nums[left]) >= abs(nums[right]): ans.append(nums[left ** 2) left += 1 else: ans.append(nums[right ** 2) right -= 1 return ans[::-1] ``` 最后是Leetcode 209题,题目要求在给定的数组中找到长度最小的子数组,使得子数组的和大于等于给定的目标值。这里可以使用滑动窗口的方法来解决问题。使用两个指针来表示滑动窗口的左边界和右边界,通过移动指针来调整滑动窗口的大小,使得滑动窗口中的元素的和满足题目要求。具体实现的代码如下: ```python def minSubArrayLen(self, target: int, nums: List[int]) -> int: left = 0 right = 0 ans = float('inf') total = 0 while right < len(nums): total += nums[right] while total >= target: ans = min(ans, right - left + 1) total -= nums[left] left += 1 right += 1 return ans if ans != float('inf') else 0 ``` 以上就是第二十二天的算法训练营的内容。通过这些题目的练习,可以提升对双指针和滑动窗口等算法的理解和应用能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值