二叉搜索树

二叉搜索树中的搜索

给定二叉搜索树(BST)的根节点 root 和一个整数值 val
你需要在 BST 中找到节点值等于 val 的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 null

示例 1:

输入:root = [4,2,7,1,3], val = 2
输出:[2,1,3]

递归

public TreeNode searchBST(TreeNode root, int val) {
    if (root == null) {
        return null;
    }
    if (root.val == val) {
        return root;
    }
    return searchBST(root.val < val ? root.right : root.left, val);
}

迭代

public TreeNode searchBST(TreeNode root, int val) {
    while (root != null) {
        if (root.val == val) {
            return root;
        } 
        root = root.val < val ? root.right : root.left;
    }
    return null;
}

二叉搜索树的第k大节点

给定一棵二叉搜索树,请找出其中第k大的节点。

示例 1:

输入: root = [3,1,4,null,2], k = 1
   3
  / \
 1   4
  \
   2
输出: 4

示例 2:

输入: root = [5,3,6,2,4,null,null,1], k = 3
       5
      / \
     3   6
    / \
   2   4
  /
 1
输出: 4

限制:

1 ≤ k ≤ 二叉搜索树元素个数

右 根 左

private TreeNode node;

private int i;

public int kthLargest(TreeNode root, int k) {
    postorder(root, k);
    return node.val;
}

public void postorder(TreeNode root, int k) {
    if (root != null) {
        postorder(root.right, k);
        i++;
        if (i == k) {
            node = root;
        }
        postorder(root.left, k);
    }
}

验证二叉搜索树

给定一个二叉树,判断其是否是一个有效的二叉搜索树。
假设一个二叉搜索树具有如下特征:

  • 节点的左子树只包含小于当前节点的数。
  • 节点的右子树只包含大于当前节点的数。
  • 所有左子树和右子树自身必须也是二叉搜索树。

示例 1:

输入:
    2
   / \
  1   3
输出: true

示例 2:

输入:
    5
   / \
  1   4
     / \
    3   6
输出: false
解释: 输入为: [5,1,4,null,null,3,6]。
     根节点的值为 5 ,但是其右子节点值为 4 

错误的思路:
根大于左子树,小于右子树,再递归判断左右子树
原因:不仅跟要大于右子树,而且要大于右子树的所有节点

方法一:递归(上下界)

判断左子树时将根作为上界,判断右子树时将根作为下界

public boolean isValidBST(TreeNode root) {
    return recurse(root, null, null);
}

public boolean recurse(TreeNode root, Integer lower, Integer supper) {
    if (root == null) {
        return true;
    }
    if (lower != null && root.val <= lower) {
        return false;
    }
    if (supper != null && root.val >= supper) {
        return false;
    }
    return recurse(root.left, lower, root.val) && recurse(root.right, root.val, supper);
}

迭代实现:

private Stack<TreeNode> stack = new Stack<>();
private Stack<Integer> lowers = new Stack<>();
private Stack<Integer> suppers = new Stack<>();

public boolean isValidBST(TreeNode root) {
    if (root == null) {
        return true;
    }
    Integer lower = null;
    Integer supper = null;
    update(root, lower, supper);
    while (!stack.isEmpty()) {
        root = stack.pop();
        lower = lowers.pop();
        supper = suppers.pop();
        if (lower != null && root.val <= lower) {
            return false;
        }
        if (supper != null && root.val >= supper) {
            return false;
        }
        if (root.left != null) {
            update(root.left, lower, root.val);
        }
        if (root.right != null) {
            update(root.right, root.val, supper);
        }
    }
    return true;
}

public void update(TreeNode root, Integer lower, Integer supper) {
    stack.push(root);
    lowers.add(lower);
    suppers.add(supper);
}

方法二:中序遍历

private Integer pre = null;

public boolean isValidBST(TreeNode root) {
    if (root == null) {
        return true;
    }
    if (!isValidBST(root.left)) {
        return false;
    }
    if (pre !=null && root.val <= pre) {
        return false;
    }
    pre = root.val;
    return isValidBST(root.right);
}

迭代法:

public boolean isValidBST(TreeNode root) {
    if (root == null) {
        return true;
    }
    Integer pre = null;
    Stack<TreeNode> stack = new Stack<>();
    while (!stack.isEmpty() || root != null) {
        while (root != null) {
            stack.push(root);
            root = root.left;
        }
        root = stack.pop();
        if (pre != null && root.val <= pre) {
            return false;
        }
        pre = root.val;
        root = root.right;
    }
    return true;
}

二叉搜索树的最小绝对差

给你一个二叉搜索树的根节点 root ,返回 树中任意两不同节点值之间的最小差值
差值是一个正数,其数值等于两值之差的绝对值。

示例 1:

输入:root = [4,2,6,1,3]
输出:1

递归
中序遍历

int min = Integer.MAX_VALUE;
int pre = -1;

public int getMinimumDifference(TreeNode root) {
    dfs(root);
    return min;
}

private void dfs(TreeNode root) {
    if (root == null) {
        return;
    }
    dfs(root.left);
    if (pre != -1) {
        min = Math.min(min, root.val - pre);
    }
    pre = root.val;
    dfs(root.right);
}

迭代

public int getMinimumDifference(TreeNode root) {
    Stack<TreeNode> stack = new Stack<>();
    TreeNode pre = null;
    int min = Integer.MAX_VALUE;
    while (!stack.isEmpty() || root != null) {
        while (root != null) {
            stack.push(root);
            root = root.left;
        }
        root = stack.pop();
        if (pre != null) {
            min = Math.min(min, root.val - pre.val);
        }
        pre = root;
        root = root.right;
    }
    return min;
}

二叉搜索树中的众数

给你一个含重复值的二叉搜索树(BST)的根节点 root ,找出并返回 BST 中的所有 众数(即,出现频率最高的元素)。
如果树中有不止一个众数,可以按 任意顺序 返回。

假定 BST 满足如下定义:

  • 结点左子树中所含节点的值 小于等于 当前节点的值
  • 结点右子树中所含节点的值 大于等于 当前节点的值
  • 左子树和右子树都是二叉搜索树

示例 1:

输入:root = [1,null,2,2]
输出:[2]

如果不是二叉搜索树,遍历树,统计每一数字的出现次数,最后对出现次数做排序,取最大的。
由于二叉搜索树,中序遍历是非递减的序列,所以在遍历过程中不断更新每一个数的出现次数并取最大的

递归

private TreeNode pre;
private int count, maxCount;
private List<Integer> res = new ArrayList<>();

public int[] findMode(TreeNode root) {
    dfs(root);
    return res.stream().mapToInt(num -> num).toArray();
}

private void dfs(TreeNode root) {
    if (root == null) {
        return;
    }
    dfs((root.left));
    if (pre == null || pre.val == root.val) {
        count++;
    } else {
        count = 1;
    }
    if (count > maxCount) {
        maxCount = count;
        res.clear();
        res.add(root.val);
    } else if (count == maxCount) {
        res.add(root.val);
    }
    pre = root;
    dfs(root.right);
}

迭代

public int[] findMode(TreeNode root) {
    TreeNode pre = null;
    int count = 0, maxCount = 0;
    List<Integer> res = new ArrayList<>();

    Stack<TreeNode> stack = new Stack<>();
    while (!stack.isEmpty() || root != null) {
        while (root != null) {
            stack.push(root);
            root = root.left;
        }
        root = stack.pop();
        if (pre == null || pre.val == root.val) {
            count++;
        } else {
            count = 1;
        }
        if (count > maxCount) {
            maxCount = count;
            res.clear();
            res.add(root.val);
        } else if (count == maxCount) {
            res.add(root.val);
        }
        pre = root;
        root = root.right;
    }
    return res.stream().mapToInt(num -> num).toArray();
}

二叉搜索树中的插入操作

给定二叉搜索树(BST)的根节点和要插入树中的值,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 保证原始二叉搜索树中不存在新值。
注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回任意有效的结果。

例如,

给定二叉搜索树:
        4
       / \
      2   7
     / \
    1   3
和 插入的值: 5

你可以返回这个二叉搜索树:

         4
       /   \
      2     7
     / \   /
    1   3 5

或者这个树也是有效的:
         5
       /   \
      2     7
     / \   
    1   3
         \
          4

方法一:递归

public TreeNode insertIntoBST(TreeNode root, int val) {
    if (root == null) {
        return new TreeNode(val);
    }
    if (val < root.val) {
        root.left = insertIntoBST(root.left, val);
    } else {
        root.right = insertIntoBST(root.right, val);
    }
    return root;
}

方法二:迭代

public TreeNode insertIntoBST(TreeNode root, int val) {
    TreeNode node = root;
    while (node != null) {
        if (val < node.val) {
            if (node.left != null) {
                node = node.left;
            } else {
                node.left = new TreeNode(val);
                return root;
            }
        }  else {
            if (node.right != null) {
                node = node.right;
            } else {
                node.right = new TreeNode(val);
                return root;
            }
        }
    }
    return new TreeNode(val);
}

删除二叉搜索树中的节点

给定一个二叉搜索树的根节点 **root **和一个值 key,删除二叉搜索树中的 **key **对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。

一般来说,删除节点可分为两个步骤:

  1. 首先找到需要删除的节点;
  2. 如果找到了,删除它。

示例 1:

输入:root = [5,3,6,2,4,null,7], key = 3
输出:[5,4,6,2,null,null,7]
解释:给定需要删除的节点值是 3,所以我们首先找到 3 这个节点,然后删除它。
一个正确的答案是 [5,4,6,2,null,null,7], 如下图所示。
另一个正确答案是 [5,2,6,null,4,null,7]。

递归
这里有三种可能的情况:

  • 要删除的节点为叶子节点,可以直接删除。


  • 要删除的节点不是叶子节点且拥有右节点,则该节点可以由该节点的后继节点进行替代,该后继节点位于右子树中较低的位置。然后可以从后继节点的位置递归向下操作以删除后继节点。


  • 要删除的节点不是叶子节点,且没有右节点但是有左节点。这意味着它的后继节点在它的上面,但是我们并不想返回。我们可以使用它的前驱节点进行替代,然后再递归的向下删除前驱节点。


算法:

  • 如果 key > root.val,说明要删除的节点在右子树,root.right = deleteNode(root.right, key)。
  • 如果 key < root.val,说明要删除的节点在左子树,root.left = deleteNode(root.left, key)。
  • 如果 key == root.val,则该节点就是我们要删除的节点,则:
    • 如果该节点是叶子节点,则直接删除它:root = null。
    • 如果该节点不是叶子节点且有右节点,则用它的后继节点的值替代 root.val = successor.val,然后删除后继节点。
    • 如果该节点不是叶子节点且只有左节点,则用它的前驱节点的值替代 root.val = predecessor.val,然后删除前驱节点。
public TreeNode deleteNode(TreeNode root, int key) {
    if (root == null) {
        return null;
    }
    if (key < root.val) {
        root.left = deleteNode(root.left, key);
    } else if (key > root.val) {
        root.right = deleteNode(root.right, key);
    } else {
        if (root.left == null && root.right == null) {
            root = null;
        } else if (root.right != null) {
            root.val = successor(root);
            root.right = deleteNode(root.right, root.val);
        } else {
            root.val = predecessor(root);
            root.left = deleteNode(root.left, root.val);
        }
    }
    return root;
}

// 后继结点为中序遍历后一个结点,即先切到右子树,再遍历左子树
private int successor(TreeNode root) {
    root = root.right;
    while (root.left != null) {
        root = root.left;
    }
    return root.val;
}

// 前继结点为中序遍历前一个结点,即先切到左子树,再遍历右子树
private int predecessor(TreeNode root) {
    root = root.left;
    while (root.right != null) {
        root = root.right;
    }
    return root.val;
}

另外的思路:
找到key结点后

  • 有一棵子树为空,返回非空子树
  • 否则将前继结点或后继结点替换
public TreeNode deleteNode(TreeNode root, int key) {
    if (root == null) {
        return null;
    }
    if (root.val == key) {
        if (root.right == null) {
            return root.left;
        } else {
            // 这里是将后继结点替换,也可以改成用前继结点替换
            TreeNode cur = root.right;
            while (cur.left != null) {
                cur = cur.left;
            }
            int val = root.val;
            root.val = cur.val;
            cur.val = val;
            root.right = deleteNode(root.right, key);
        }
    } else if (root.val > key) {
        root.left = deleteNode(root.left, key);
    } else {
        root.right = deleteNode(root.right, key);
    }
    return root;
}

修剪二叉搜索树

给你二叉搜索树的根节点 root ,同时给定最小边界low 和最大边界 high。通过修剪二叉搜索树,使得所有节点的值在[low, high]中。修剪树 不应该 改变保留在树中的元素的相对结构 (即,如果没有被移除,原有的父代子代关系都应当保留)。 可以证明,存在 唯一的答案
所以结果应当返回修剪好的二叉搜索树的新的根节点。注意,根节点可能会根据给定的边界发生改变。

示例 1:

输入:root = [1,0,2], low = 1, high = 2
输出:[1,null,2]

方法一:递归

public TreeNode trimBST(TreeNode root, int low, int high) {
    if (root == null) {
        return null;
    }
    // 根及左子树所有都小,全部抛弃,取右子树
    if (root.val < low) {
        return trimBST(root.right, low, high);
    }
    // 根及右子树所有都大,全部抛弃,取左子树
    if (root.val > high) {
        return trimBST(root.left, low, high);
    }
    // 根满足条件,裁剪它们的子树
    root.left = trimBST(root.left, low, high);
    root.right = trimBST(root.right, low, high);
    return root;
}

迭代

public TreeNode trimBST(TreeNode root, int low, int high) {
    if (root == null) {
        return null;
    }
    // 从根开始二分找到一个满足条件的结点
    while (root != null) {
        if (root.val < low) {
            root = root.right;
        } else if (root.val > high) {
            root = root.left;
        } else {
            break;
        }
    }
    // 处理左子树小于的结点
    TreeNode cur = root;
    while (cur != null) {
        // 如果左子树的根结点都小于,直接将 右子树挂过来
        while (cur.left != null && cur.left.val < low) {
            cur.left = cur.left.right;
        }
        cur = cur.left;
    }
    cur = root;
    while (cur != null) {
        // 如果左子树的根结点都小于,直接将 右子树挂过来
        while (cur.right != null && cur.right.val > high) {
            cur.right = cur.right.left;
        }
        cur = cur.right;
    }  
    return root;
}

将有序数组转换为二叉搜索树

给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。
**高度平衡 **二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。

示例 1:

输入:nums = [-10,-3,0,5,9]
输出:[0,-3,9,-10,null,5]
解释:[0,-10,5,null,-3,null,9] 也将被视为正确答案:

递归
中序遍历,取一个做根结点,两边做左右子树

public TreeNode sortedArrayToBST(int[] nums) {
    return build(nums, 0, nums.length - 1);
}

public TreeNode build(int[] nums, int low, int high) {
    if (low > high) {
        return null;
    }
    // 选取中间位置的左边做根结点
    // 也可以选取中间位置的右边做根结点:int mid = (low + high + 1) / 2;
    // 也可以随机选择一个做根结点:int mid = (low + high + rand.nextInt(2)) / 2;
    int mid = (low + high) / 2;
    TreeNode root = new TreeNode(nums[mid]);
    root.left = build(nums, low, mid - 1);
    root.right = build(nums, mid + 1, high);
    return root;
}

迭代

public TreeNode sortedArrayToBST(int[] nums) {
    if (nums == null || nums.length == 0) {
        return null;
    }
    Queue<TreeNode> queue = new LinkedList<>();
    Queue<int[]> posQueue = new LinkedList<>();
    TreeNode root = new TreeNode(0);
    queue.add(root);
    posQueue.add(new int[]{0, nums.length - 1});
    while (!queue.isEmpty()) {
        TreeNode node = queue.poll();
        int[] pair = posQueue.poll();
        int mid = (pair[0] + pair[1]) / 2;
        node.val = nums[mid];
        if (mid - 1 >= pair[0]) {
            node.left = new TreeNode(0);
            queue.add(node.left);
            posQueue.add(new int[]{pair[0], mid - 1});
        }
        if (mid + 1 <= pair[1]) {
            node.right = new TreeNode(0);
            queue.add(node.right);
            posQueue.add(new int[]{mid + 1, pair[1]});
        }
    }
    return root;
}

将二叉搜索树变平衡

给你一棵二叉搜索树,请你返回一棵 平衡后 的二叉搜索树,新生成的树应该与原来的树有着相同的节点值。
如果一棵二叉搜索树中,每个节点的两棵子树高度差不超过 1 ,我们就称这棵二叉搜索树是 平衡的

如果有多种构造方法,请你返回任意一种。

示例:


输入:root = [1,null,2,null,3,null,4,null,null]
输出:[2,1,3,null,null,null,4]
解释:这不是唯一的正确答案,[3,1,4,null,2,null,null] 也是一个可行的构造方案。

贪心构造
「平衡」要求它是一棵空树或它的左右两个子树的高度差的绝对值不超过 1,这很容易让我们产生这样的想法——左右子树的大小越「平均」,这棵树会不会越平衡?于是一种贪心策略的雏形就形成了:我们可以通过中序遍历将原来的二叉搜索树转化为一个有序序列,然后对这个有序序列递归建树,对于区间 [L, R][:

  • 取mid即中心位置作为当前节点的值;
  • [L,mid−1] 作为当前节点的左子树;
  • [mid+1,R] 作为当前节点的右子树。
private List<TreeNode> list = new ArrayList<>();

public TreeNode balanceBST(TreeNode root) {
    inorder(root);
    return build(0, list.size() - 1);
}

private void inorder(TreeNode root) {
    if (root == null) {
        return;
    }
    inorder(root.left);
    list.add(root);
    inorder(root.right);
}

private TreeNode build(int low, int high) {
    if (low > high) {
        return null;
    }
    int mid = (low + high) / 2;
    TreeNode root = list.get(mid);
    root.left = build(low, mid - 1);
    root.right = build(mid + 1, high);
    return root;
}

把二叉搜索树转换为累加树

给定一个二叉搜索树(Binary Search Tree),把它转换成为累加树(Greater Tree),使得每个节点的值是原来的节点值加上所有大于它的节点值之和。

例如:

输入: 原始二叉搜索树:
              5
            /   \
           2     13

输出: 转换为累加树:
             18
            /   \
          20     13

递归

private TreeNode pre;

public TreeNode bstToGst(TreeNode root) {
    if (root == null) {
        return null;
    }
    bstToGst(root.right);
    if (pre != null) {
        root.val += pre.val;
    }
    pre = root;
    bstToGst(root.left);
    return root;
}

迭代

public TreeNode convertBST(TreeNode root) {
    Stack<TreeNode> stack = new Stack<>();
    TreeNode pre = null;
    TreeNode node = root;
    while (!stack.isEmpty() || node != null) {
        while (node != null) {
            stack.add(node);
            node = node.right;
        }
        node = stack.pop();
        if (pre != null) {
            node.val += pre.val;
        }
        pre = node;
        node = node.left;
    }
    return root;
}

二叉搜索树的后序遍历序列

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。

参考以下这颗二叉搜索树:

     5
    / \
   2   6
  / \
 1   3

示例 1:

输入: [1,6,3,2,5]
输出: false

示例 2:

输入: [1,3,2,6,5]
输出: true

递归
划分左右子树:数组最右边的元素为根,向前找到第一个大于根的位置,即为右子树部分,剩下部分为左子树

public boolean verifyPostorder(int[] postorder) {
    return dfs(postorder, 0, postorder.length - 1);
}

private boolean dfs(int[] postorder, int low, int high) {
    // 只有一个元素时肯定满足
    if (low >= high) {
        return true;
    }
    // 从最后的前一个位置开始向前遍历,找到第一个小于根的元素
    // 如果合法,左边部分就是左子树,右边部分就是右子树
    int index = high - 1;
    while (index >= low && postorder[index] > postorder[high]) {
        index--;
    }
    // 判断是否合法:即判断左子树是否全部小于根
    for (int i = index; i >= low; i--) {
        if (postorder[i] >= postorder[high]) {
            return false;
        }
    }
    return dfs(postorder, low, index) && dfs(postorder, index + 1, high - 1);
}

单调栈
维护一个升序的栈


后续遍历结果是
[3,6,5,9,8,11,13,12,10]

  • 如果两个挨着的是,arr[i] < arr[i - 1],则arr[i - 1]一定是arr[i]的右孩子
  • 如果arr[i] > arr[i - 1],可能arr[i]作为右子树或者根节点更大,所以arr[i - 1]一定是arr[i]...arr[n]某个结点的左孩子,又由于右结点比根大,所以根结点是arr[i]...arr[n]中最小的结点

根据上面分析的过程,很容易想到使用栈来解决。遍历数组的所有元素,如果栈为空,就把当前元素压栈。如果栈不为空,并且当前元素大于栈顶元素,说明是升序的,那么就说明当前元素是栈顶元素的右子节点,就把当前元素压栈,如果一直升序,就一直压栈。当前元素小于栈顶元素,说明是倒序的,说明当前元素是某个节点的左子节点,我们目的是要找到这个左子节点的父节点,就让栈顶元素出栈,直到栈为空或者栈顶元素小于当前值为止,其中最后一个出栈的就是当前元素的父节点

public boolean verifyPostorder(int[] postorder) {
    Stack<Integer> stack = new Stack<>();
    int root = Integer.MAX_VALUE;
    for (int i = postorder.length - 1; i >= 0; i--) {
        // 如果当前元素比之前的小,说明当前元素是之前出现最小元素的左子树
        while (!stack.isEmpty() && postorder[i] < stack.peek()) {
            root = stack.pop();
        }
        // 如果是二叉搜索树,不可能比root大:
        // root为上一次有左子树的根,数组前面的为根的左子树,所以不会大于根
        if (postorder[i] > root) {
            return false;
        }
        stack.push(postorder[i]);
    }
    return true;
}

不同的二叉搜索树

给定一个整数 n,求以 1 ... n 为节点组成的二叉搜索树有多少种?

示例:

输入: 3
输出: 5
解释: 给定 n = 3, 一共有 5 种不同结构的二叉搜索树:

   1         3     3      2      1
    \       /     /      / \      \
     3     2     1      1   3      2
    /     /       \                 \
   2     1         2                 3

动态规划

假设n个节点存在二叉排序树的个数是G(n),令f(i)为以i为根的二叉搜索树的个数
G(n) = f(1) + f(2) + ... + f(n)

当i为根节点时,其左子树节点个数为i-1个,右子树节点为n-i,则
f(i) = G(i-1)*G(n-i)

综合两个公式可以得到 卡特兰数 公式
G(n) = G(0)×G(n-1)+G(1)×(n-2)+...+G(n-1)×G(0)

public int numTrees(int n) {
    int[] dp = new int[n + 1];
    dp[0] = 1;
    dp[1] = 1;
    for (int i = 2; i <= n; i++) {
        for (int j = 1; j <= i; j++) {
            dp[i] += dp[j - 1] * dp[i - j];
        }
    }
    return dp[n];
}

G(n)函数的值在数学上被称为卡塔兰数
C0=1
Cn+1=Cn×2×(2n+1)/(n+2)

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