【DW1月-力扣刷题】Task01 链表

参考链接:https://github.com/itcharge/LeetCode-Py

一、链表基础知识

1.1 链表简介

链表(Linked List):是一种线性表数据结构。它使用一组任意的存储单元(可以是连续的,也可以是不连续的),来存储一组具有相同类型的数据。
简单来说,链表 是实现线性表的链式存储结构的基础。

链表结构优缺点:

  • 优点:存储空间不必事先分配,在需要存储空间的时候可以临时申请,不会造成空间的浪费;一些操作的时间效率远比数组高(插入、移动、删除元素等)。
  • 缺点:不仅数据元素本身的数据信息要占用存储空间,指针也需要占用存储空间,链表结构比数组结构的空间开销大。

链表种类:

  • 单链表。单链表通过将一组任意的存储单元串联在一起。其中,每个数据元素占用若干存储单元的组合称为一个「链节点」。
  • 双链表。它的每个链节点中有两个指针,分别指向直接后继和直接前驱。
  • 循环链表。它的最后一个链节点指向头节点,形成一个环。

1.2 链表的基本操作

1.2.1 链表的结构定义

  • 链表是由链节点通过 next 链接而构成的,所以先来定义一个简单的链节点类,即ListNode 类。ListNode 类使用成员变量 val 表示数据元素的值,使用指针变量 next 表示后继指针。
  • 然后再定义链表类,即 LinkedList 类。ListkedList 类中只有一个链节点变量 head 用来表示链表的头节点。

代码如下:

# 链节点类
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

# 链表类
class LinkedList:
    def __init__(self):
        self.head = None

1.2.1 建立一个线性链表

建立一个线性链表就是根据线性表的数据元素动态生成链节点,并依次将其连接到链表中。其做法如下:

  • 从所给线性表的第 1 个数据元素开始依次获取表中的数据元素。
  • 每获取一个数据元素,就为该数据元素生成一个新节点,将新节点插入到链表的尾部。
  • 插入完毕之后返回第 1 个链节点的地址。

建立一个线性链表的时间复杂度为 O(n),n 为线性表长度。
代码如下:

# 根据 data 初始化一个新链表
def create(self, data):
    self.head = ListNode(0)
    cur = self.head
    for i in range(len(data)):
        node = ListNode(data[i])
        cur.next = node
        cur = cur.next

1.2.3 求线性链表的长度

线性链表的长度被定义为链表中包含的链节点的个数。求线性链表的长度操作只需要使用一个可以顺着链表指针移动的指针变量 cur 和一个计数器 count。具体做法如下:

  • 让指针变量 cur 指向链表的第 1 个链节点。
  • 然后顺着链节点的 next 指针遍历链表,指针变量 cur 每指向一个链节点,计数器就做一次计数。
  • 等 cur 指向为空时结束遍历,此时计数器的数值就是链表的长度,将其返回即可。

求线性链表的长度操作的问题规模是链表的链节点数 n,基本操作是 cur 指针的移动,操作的次数为 n,因此算法的时间复杂度为 O(n)
代码如下:

# 获取链表长度
def length(self):
    count = 0
    cur = self.head
    while cur:
        count += 1
        cur = cur.next 
    return count

1.2.4 查找元素

在链表中查找值为 val 的位置:链表不能像数组那样进行随机访问,只能从头节点 head 开始,沿着链表一个一个节点逐一进行查找。如果查找成功,返回被查找节点的地址。否则返回 None。

查找元素操作的问题规模是链表的长度 n,而基本操作是指针 cur 的移动操作,所以查找元素算法的时间复杂度为 O(n)

# 查找元素
def find(self, val):
    cur = self.head
    while cur:
        if val == cur.val:
            return cur
        cur = cur.next

    return None

1.2.5 插入元素

链表中插入元素操作分为三种:

  • 链表头部插入元素:在链表第 1 个链节点之前插入值为 val 的链节点。
  • 链表尾部插入元素:在链表最后 1 个链节点之后插入值为 val 的链节点。
  • 链表中间插入元素:在链表第 i 个链节点之前插入值为 val 的链节点。

链表头部插入元素
算法实现的步骤如下:

  • 先创建一个值为 val 的链节点 node。
  • 然后将 node 的 next 指针指向链表的头节点 head。
  • 再将链表的头节点 head 指向 node。


    image.png

因为在链表头部插入链节点与链表的长度无关,所以该算法的时间复杂度为 O(1)

# 头部插入元素
def insertFront(self, val):
    node = ListNode(val)
    node.next = self.head
    self.head = node

尾部插入元素
算法实现的步骤如下:

  • 先创建一个值为 val 的链节点 node。
  • 使用指针 cur 指向链表的头节点 head。
  • 通过链节点的 next 指针移动 cur 指针,从而遍历链表,直到 cur.next == None。
  • 令 cur.next 指向将新的链节点 node。
    image.png

    因为将 cur 从链表头部移动到尾部的操作次数是 n 次,所以该算法的时间复杂度是 O(n)
# 尾部插入元素
def insertRear(self, val):
    node = ListNode(val)
    cur = self.head
    while cur.next:
        cur = cur.next
    cur.next = node

中间插入元素
算法的实现步骤如下:

  • 使用指针变量 cur 和一个计数器 count。令 cur 指向链表的头节点,count 初始值赋值为 0。
  • 沿着链节点的 next 指针遍历链表,指针变量 cur 每指向一个链节点,计数器就做一次计数。
  • 当 count == index - 1 时,说明遍历到了第 index - 1 个链节点,此时停止遍历。
  • 创建一个值为 val 的链节点 node。
  • 将 node.next 指向 cur.next。
  • 然后令 cur.next 指向 node。
    image.png

    因为将 cur 从链表头部移动到第 i 个链节点之前的操作平均时间复杂度是 O(n),所以该算法的时间复杂度是 O(n)
# 中间插入元素
def insertInside(self, index, val):
    count = 0
    cur = self.head
    while cur and count < index - 1:
        count += 1
        cur = cur.next
        
    if not cur:
        return 'Error'
    
    node = ListNode(val)
    node.next = cur.next
    cur.next = node

1.2.6 改变元素

将链表中第 i 个元素值改为 val:首先要先遍历到第 i 个链节点,然后直接更改第 i 个链节点的元素值。具体做法如下:

  • 使用指针变量 cur 和一个计数器 count。令 cur 指向链表的头节点,count 初始值赋值为 0。
  • 沿着链节点的 next 指针遍历链表,指针变量 cur 每指向一个链节点,计数器就做一次计数。
  • 当 count == index 时,说明遍历到了第 index 个链节点,此时停止遍历。
  • 直接更改 cur 的值 val。

因为将 cur 从链表头部移动到第 i 个链节点的操作平均时间复杂度是 O(n),所以该算法的时间复杂度是 O(n)

# 改变元素
def change(self, index, val):
    count = 0
    cur = self.head
    while cur and count < index:
        count += 1
        cur = cur.next
        
    if not cur:
        return 'Error'
    
    cur.val = val

1.2.7 删除元素

链表的删除元素操作同样分为三种情况:

  • 链表头部删除元素:删除链表的第 1 个链节点。
  • 链表尾部删除元素:删除链表末尾最后 1 个链节点。
  • 链表中间删除元素:删除链表第 i 个链节点。

二、链表排序

2.1 链表冒泡排序

算法描述:
使用三个指针 node_i、node_j 和 tail。其中 node_i 用于控制外循环次数,循环次数为链节点个数(链表长度)。node_j 和 tail 用于控制内循环次数和循环结束位置。
算法步骤:

  • 排序开始前,将 node_i 、node_j 置于头节点位置。tail 指向链表末尾,即 None。
  • 比较链表中相邻两个元素 node_j.val 与 node_j.next.val 的值大小,如果 node_j.val > node_j.next.val,则值相互交换。否则不发生交换。然后向右移动 node_j 指针,直到 node_j.next == tail 时停止。
  • 一次循环之后,将 tail 移动到 node_j 所在位置。相当于 tail 向左移动了一位。此时 tail 节点右侧为链表中最大的链节点。
  • 然后移动 node_i 节点,并将 node_j 置于头节点位置。然后重复第 3、4 步操作。
  • 直到 node_i 节点移动到链表末尾停止,排序结束。
  • 返回链表的头节点 head。
def bubbleSort(self, head: ListNode):
    node_i = head
    tail = None
    # 外层循环次数为 链表节点个数
    while node_i:
        node_j = head
        while node_j and node_j.next != tail:
            if node_j.val > node_j.next.val:
                # 交换两个节点的值
                node_j.val, node_j.next.val = node_j.next.val, node_j.val
            node_j = node_j.next
        # 尾指针向前移动 1 位,此时尾指针右侧为排好序的链表
        tail = node_j
        node_i = node_i.next
        
    return head

2.2 链表选择排序

算法描述:

  • 使用两个指针 node_i、node_j。node_i 既可以用于控制外循环次数,又可以作为当前未排序链表的第一个链节点位置。
  • 使用 min_node 记录当前未排序链表中值最小的链节点。
  • 每一趟排序开始时,先令 min_node = node_i(即暂时假设链表中 node_i 节点为值最小的节点,经过比较后再确定最小值节点位置)。
  • 然后依次比较未排序链表中 node_j.val 与 min_node.val 的值大小。如果 node_j.val > min_node.val,则更新 min_node 为 node_j。
  • 这一趟排序结束时,未排序链表中最小值节点为 min_node,如果 node_i != min_node,则将 node_i 与 min_node 值进行交换。如果 node_i == min_node,则不用交换。
  • 排序结束后,继续向右移动 node_i,重复上述步骤,在剩余未排序链表中寻找最小的链节点,并与 node_i 进行比较和交换,直到 node_i == None 或者 node_i.next == None 时,停止排序。
  • 返回链表的头节点 head。
def sectionSort(self, head: ListNode):
    node_i = head
    # node_i 为当前未排序链表的第一个链节点
    while node_i and node_i.next:
        # min_node 为未排序链表中的值最小节点
        min_node = node_i
        node_j = node_i.next
        while node_j:
            if node_j.val < min_node.val:
                min_node = node_j
            node_j = node_j.next
        # 交换值最小节点与未排序链表中第一个节点的值
        if node_i != min_node:
            node_i.val, min_node.val = min_node.val, node_i.val
        node_i = node_i.next
    
    return head

2.3 链表插入排序

算法描述:

  • 1、先使用哑节点 dummy_head 构造一个指向 head 的指针,使得可以从 head 开始遍历。
  • 2、维护 sorted_list 为链表的已排序部分的最后一个节点,初始时,sorted_list = head。
  • 3、维护 prev 为插入元素位置的前一个节点,维护 cur 为待插入元素。初始时,prev = head,cur = head.next。
  • 4、比较 sorted_list 和 cur 的节点值。
    • 如果 sorted_list.val <= cur.val,说明 cur 应该插入到 sorted_list 之后,则将 sorted_list 后移一位。
    • 如果 sorted_list.val > cur.val,说明 cur 应该插入到 head 与 sorted_list 之间。则使用 prev 从 head 开始遍历,直到找到插入 cur 的位置的前一个节点位置。然后将 cur 插入。
  • 5、令 cur = sorted_list.next,此时 cur 为下一个待插入元素。
  • 重复 4、5 步骤,直到 cur 遍历结束为空。返回 dummy_head 的下一个节点。
def insertionSort(self, head: ListNode):
    if not head and not head.next:
        return head
    
    dummy_head = ListNode(-1)
    dummy_head.next = head
    sorted_list = head
    cur = head.next 
    
    while cur:
        if sorted_list.val <= cur.val:
            # 将 cur 插入到 sorted_list 之后
            sorted_list = sorted_list.next 
        else:
            prev = dummy_head
            while prev.next.val <= cur.val:
                prev = prev.next
            # 将 cur 到链表中间
            sorted_list.next = cur.next
            cur.next = prev.next
            prev.next = cur
        cur = sorted_list.next 
    
    return dummy_head.next

2.4 链表归并排序

算法描述:
分割环节:找到链表中心链节点,从中心节点将链表断开,并递归进行分割。

  • 使用快慢指针 fast = head.next、slow = head,让 fast 每次移动 2 步,slow 移动 1 步,移动到链表末尾,从而找到链表中心链节点,即 slow。
  • 从中心位置将链表从中心位置分为左右两个链表 left_head 和 right_head,并从中心位置将其断开,即 slow.next = None。
  • 对左右两个链表分别进行递归分割,直到每个链表中只包含一个链节点。

归并环节:将递归后的链表进行两两归并,完成一遍后每个子链表长度加倍。重复进行归并操作,直到得到完整的链表。

  • 使用哑节点 dummy_head 构造一个头节点,并使用 cur 指向 dummy_head 用于遍历。
  • 比较两个链表头节点 left 和 right 的值大小。将较小的头节点加入到合并后的链表中。并向后移动该链表的头节点指针。
  • 然后重复上一步操作,直到两个链表中出现链表为空的情况。
  • 将剩余链表插入到合并中的链表中。
  • 将哑节点 dummy_dead 的下一个链节点 dummy_head.next 作为合并后的头节点返回。
def merge(self, left, right):
    # 归并环节
    dummy_head = ListNode(-1)
    cur = dummy_head
    while left and right:
        if left.val <= right.val:
            cur.next = left
            left = left.next
        else:
            cur.next = right
            right = right.next
        cur = cur.next
    
    if left:
        cur.next = left
    elif right:
        cur.next = right
        
    return dummy_head.next
    
def mergeSort(self, head: ListNode):
    # 分割环节
    if not head or not head.next:
        return head
    
    # 快慢指针找到中心链节点
    slow, fast = head, head.next
    while fast and fast.next:
        slow = slow.next 
        fast = fast.next.next 
    
    # 断开左右链节点
    left_head, right_head = head, slow.next 
    slow.next = None
    
    # 归并操作
    return self.merge(self.mergeSort(left_head), self.mergeSort(right_head))

2.5 链表快速排序

算法描述:

  • 从链表中找到一个基准值 pivot,这里以头节点为基准值。
  • 然后通过快慢指针 node_i、node_j 在链表中移动,使得 node_i 之前的节点值都小于基准值,node_i 之后的节点值都大于基准值。从而把数组拆分为左右两个部分。
  • 再对左右两个部分分别重复第二步,直到各个部分只有一个节点,则排序结束。
def partition(self, left: ListNode, right: ListNode):
    # 左闭右开,区间没有元素或者只有一个元素,直接返回第一个节点
    if left == right or left.next == right:
        return left
    # 选择头节点为基准节点
    pivot = left.val
    # 使用 node_i, node_j 双指针,保证 node_i 之前的节点值都小于基准节点值,node_i 与 node_j 之间的节点值都大于等于基准节点值
    node_i, node_j = left, left.next
    
    while node_j != right:
        # 当 node_j 的值小于基准节点值时,交换 node_i 与 node_j 的值
        if node_j.val < pivot:
            node_i = node_i.next
            node_i.val, node_j.val = node_j.val, node_i.val
        node_j = node_j.next
    # 将基准节点放到正确位置上
    node_i.val, left.val = left.val, node_i.val
    return node_i
    
def quickSort(self, left: ListNode, right: ListNode):
    if left == right or left.next == right:
        return left
    pi = self.partition(left, right)
    self.quickSort(left, pi)
    self.quickSort(pi.next, right)
    return left

2.7 链表计数排序

算法描述:

  • 使用 cur 指针遍历一遍链表。找出链表中最大值 list_max 和最小值 list_min。
  • 使用数组 counts 存储节点出现次数。
  • 再次使用 cur 指针遍历一遍链表。将链表中每个值为 cur.val 的节点出现次数,存入数组对应第 cur.val - list_min 项中。
  • 反向填充目标链表:
    • 建立一个哑节点 dummy_head,作为链表的头节点。使用 cur 指针指向 dummy_head。
  • 从小到大遍历一遍数组 counts。对于每个 counts[i] != 0 的元素建立一个链节点,值为 i + list_min,将其插入到 cur.next 上。并向右移动 cur。同时 counts[i] -= 1。直到 counts[i] == 0 后继续向后遍历数组 counts。
  • 将哑节点 dummy_dead 的下一个链节点 dummy_head.next 作为新链表的头节点返回。
def countingSort(self, head: ListNode):
    if not head:
        return head
    
    # 找出链表中最大值 list_max 和最小值 list_min
    list_min, list_max = float('inf'), float('-inf')
    cur = head
    while cur:
        if cur.val < list_min:
            list_min = cur.val
        if cur.val > list_max:
            list_max = cur.val
        cur = cur.next
        
    size = list_max - list_min + 1
    counts = [0 for _ in range(size)]
    
    cur = head
    while cur:
        counts[cur.val - list_min] += 1
        cur = cur.next
        
    dummy_head = ListNode(-1)
    cur = dummy_head
    for i in range(size):
        while counts[i]:
            cur.next = ListNode(i + list_min)
            counts[i] -= 1
            cur = cur.next
    return dummy_head.nextv

2.7 链表桶排序

算法描述:

  • 使用 cur 指针遍历一遍链表。找出链表中最大值 list_max 和最小值 list_min。
  • 通过 (最大值 - 最小值) / 每个桶的大小 计算出桶的个数,即 bucket_count = (list_max - list_min) // bucket_size + 1 个桶。
  • 定义二维数组 buckets 为桶,外层数组大小为 bucket_count 个。
  • 使用 cur 指针再次遍历一遍链表,将每个元素装入对应的桶中。
  • 对每个桶内的元素单独排序(使用插入、归并、快排等算法)。
  • 最后按照顺序将桶内的元素拼成新的链表,并返回。
def insertionSort(self, arr):
    for i in range(1, len(arr)):
        temp = arr[i]
        j = i
        while j > 0 and arr[j - 1] > temp:
            arr[j] = arr[j - 1]
            j -= 1
        arr[j] = temp
        
    return arr

def bucketSort(self, head: ListNode, bucket_size=5):
    if not head:
        return head
    
    # 找出链表中最大值 list_max 和最小值 list_min
    list_min, list_max = float('inf'), float('-inf')
    cur = head
    while cur:
        if cur.val < list_min:
            list_min = cur.val
        if cur.val > list_max:
            list_max = cur.val
        cur = cur.next
        
    # 计算桶的个数,并定义桶
    bucket_count = (list_max - list_min) // bucket_size + 1
    buckets = [[] for _ in range(bucket_count)]
    
    # 将链表节点值依次添加到对应桶中
    cur = head
    while cur:
        buckets[(cur.val - list_min) // bucket_size].append(cur.val)
        cur = cur.next
    
    dummy_head = ListNode(-1)
    cur = dummy_head
    for bucket in buckets:
        # 对桶中元素单独排序
        self.sortLinkedList(bucket)
        for num in bucket:
            cur.next = ListNode(num)
            cur = cur.next
    
    return dummy_head.next

2.8 链表基数排序

算法描述:

  • 使用 cur 指针遍历链表,获取节点值位数最长的位数 size。
  • 从个位到高位遍历位数。因为 0 ~ 9 共有 10 位数字,所以建立 10 个桶。
  • 以每个节点对应位数上的数字为索引,将节点值放入到对应桶中。
    建立一个哑节点 dummy_head,作为链表的头节点。使用 cur 指针指向 dummy_head。
  • 将桶中元素依次取出,并根据元素值建立链表节点,并插入到新的链表后面。从而生成新的链表。
  • 之后依次以十位,百位,…,直到最大值元素的最高位处值为索引,放入到对应桶中,并生成新的链表,最终完成排序。
  • 将哑节点 dummy_dead 的下一个链节点 dummy_head.next 作为新链表的头节点返回。
def radixSort(self, head: ListNode):       
    # 计算位数最长的位数
    size = 0
    cur = head
    while cur:
        val_len = len(str(cur.val))
        if val_len > size:
            size = val_len
        cur = cur.next
    
    # 从个位到高位遍历位数
    for i in range(size):
        buckets = [[] for _ in range(10)]
        cur = head
        while cur:
            # 以每个节点对应位数上的数字为索引,将节点值放入到对应桶中
            buckets[cur.val // (10 ** i) % 10].append(cur.val)
            cur = cur.next
        
        # 生成新的链表
        dummy_head = ListNode(-1)
        cur = dummy_head
        for bucket in buckets:
            for num in bucket:
                cur.next = ListNode(num)
                cur = cur.next
        head = dummy_head.next
        
    return head

三、链表双指针

双指针(Two Pointers):指的是在遍历元素的过程中,使用两个指针进行访问,从而达到相应的目的。如果两个指针方向相反,则称为「对撞时针」。如果两个指针方向相同,则称为「快慢指针」。如果两个指针分别属于不同的数组 / 链表,则称为「分离双指针」。

3.1 起点不一致的快慢指针

起点不一致的快慢指针:指的是两个指针从同一侧开始遍历链表,但是两个指针的起点不一样。 快指针 fast 比慢指针 slow 先走 n 步,直到快指针移动到链表尾端时为止。
求解步骤:

  • 使用两个指针 slow、fast。slow、fast 都指向链表的头节点,即:slow = head,fast = head。
  • 先将快指针向右移动 n 步。然后再同时向右移动快、慢指针。
  • 等到快指针移动到链表尾部(即 fast == Node)时跳出循环体。
slow = head
fast = head

while n:
    fast = fast.next
    n -= 1
while fast:
    fast = fast.next
    slow = slow.next

3.2 步长不一致的慢指针

步长不一致的慢指针:指的是两个指针从同一侧开始遍历链表,两个指针的起点一样,但是步长不一致。例如,慢指针 slow 每次走 1 步,快指针 fast 每次走两步。直到快指针移动到链表尾端时为止。
求解步骤:

  • 使用两个指针 slow、fast。slow、fast 都指向链表的头节点。
  • 在循环体中将快、慢指针同时向右移动,但是快、慢指针的移动步长不一致。比如将慢指针每次移动 1 步,即 slow = slow.next。快指针每次移动 2 步,即 fast = fast.next.next。
  • 等到快指针移动到链表尾部(即 fast == Node)时跳出循环体。
fast = head
slow = head

while fast and fast.next:
    slow = slow.next
    fast = fast.next.next

3.3 分离双指针

分离双指针:两个指针分别属于不同的链表,两个指针分别在两个链表中移动。
求解步骤:

  • 使用两个指针 left_1、left_2。left_1 指向第一个链表头节点,即:left_1 = list1,left_2 指向第二个链表头节点,即:left_2 = list2。
  • 当满足一定条件时,两个指针同时右移,即 left_1 = left_1.next、left_2 = left_2.next。
  • 当满足另外一定条件时,将 left_1 指针右移,即 left_1 = left_1.next。
  • 当满足其他一定条件时,将 left_2 指针右移,即 left_2 = left_2.next。
  • 当其中一个链表遍历完时或者满足其他特殊条件时跳出循环体。
left_1 = list1
left_2 = list2

while left_1 and left_2:
    if 一定条件 1:
        left_1 = left_1.next
        left_2 = left_2.next
    elif 一定条件 2:
        left_1 = left_1.next
    elif 一定条件 3:
        left_2 = left_2.next

四、总结

本章节主要学习了链表、排序和双指针的基础知识,以及他们的求解方法,代码实现等等。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,193评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,306评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,130评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,110评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,118评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,085评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,007评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,844评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,283评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,508评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,395评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,985评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,630评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,797评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,653评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,553评论 2 352

推荐阅读更多精彩内容