剑指offer学习笔记:4.4 分解让复杂问题简单化

分治法,分而治之。

面试题26:复杂链表的复制

请实现函数ComplexListNode* Clone(ComplexListNode*pHead)复制一个复杂链表。在复杂链表中,每个节点除了一个m_pNext指针指向下一节点,还有一个m_pSibling指向链表中的任意节点或者NULL。结点的c++定义如下:

struct ComplexListNode{
 int m_nValue;   
 ComplexListNode* m_pNext; 
 ComplexListNode* m_pSibling;
}

leetcode链接 https://leetcode-cn.com/problems/copy-list-with-random-pointer/

/*
// Definition for a Node.
class Node {
public:
   int val;
   Node* next;
   Node* random;
   
   Node(int _val) {
       val = _val;
       next = NULL;
       random = NULL;
   }
};
*/
class Solution {
public:
   Node* copyRandomList(Node* head) {
       if (head == NULL)
       {
           return NULL;
       }
       Node* tmp = head;   // 用于遍历的临时指针
       // 第一步,先复制链表,同时将新节点依次插到其复制节点后面
       while(tmp != NULL)
       {
           // cout << tmp->val << " ";
           Node* node = new Node(tmp->val);
           node->next = tmp->next;    // 新建节点next转化为原节点next,实现插入
           tmp->next = node;   // 原节点next转化为当前新建节点
           tmp = node->next;
       }
       // tmp = head;
       /* 验证下复制完的对不对
       while(tmp != NULL)
       {
           cout << tmp->val << " ";
           tmp = tmp->next;
       }
       */
       // 第二步:开始复制随机指针
       tmp = head;
       while(tmp != NULL)
       {
           // cout << tmp->val << " ";
           if (tmp->random == NULL)
           {
               tmp = tmp->next->next;
               continue;
           }
           tmp->next->random = tmp->random->next;
           tmp = tmp->next->next;
       }
       // 第三步,进行拆分
       Node* newHead = head->next;
       tmp = head;
       Node* tmp2 = newHead;
       while(tmp != NULL && tmp2 != NULL)
       {
           tmp->next = tmp2->next;
           tmp = tmp2->next;
           if (tmp == NULL)
           {
               break;
           }
           tmp2->next = tmp->next;
           tmp2 = tmp2->next;
       }
       tmp2->next = NULL;
       return newHead;
   }
};

解题思路:简单想法是将复制过程分为两步:第一步,从前往后创建节点,完成next指针复制。第二步,完成sibling指针复制,因为sibling指向可能在该节点前面也可能在改节点后面,因此每个节点复制时都需要遍历链表,找到其指向节点,时间复杂度为n^2。这个思路是很简单,写起来发现,还需要用辅助hash表存上原始节点和当前节点对应关系,不然在找随机指向节点时无法判断相等。已经建立hash表,就不用循环判断了,取随机指向节点直接从表中拿就行了。

/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* next;
    Node* random;
    
    Node(int _val) {
        val = _val;
        next = NULL;
        random = NULL;
    }
};
*/
class Solution {
public:
    Node* copyRandomList(Node* head) {
        if (head == NULL)
        {
            return NULL;
        }
        map<Node*, Node*> oldToNew;
        Node* tmp = head;   // 用于遍历的临时指针
        Node* cur = new Node(head->val);
        oldToNew[tmp] = cur;
        Node* newHead = cur;   // 保存新的头结点,用于返回
        // 第一步,先复制链表next
        tmp = tmp->next;
        while(tmp != NULL)
        {
            Node* node = new Node(tmp->val);
            cur->next = node;
            oldToNew[tmp] = cur;
            cur = cur->next;
            oldToNew[tmp] = cur;
            tmp = tmp->next;
        }
        cur->next = NULL;
        // 第二步:复制随机指针
        cur = newHead;
        tmp = head;
        Node* search = newHead;
        while(cur != NULL)
        {
            if (tmp->random == NULL)
            {
                cur->random = NULL;
                tmp = tmp->next;
                cur = cur->next;
                continue;
            }
            // 循环找判断不了,直接用hash表
            cur->random = oldToNew[tmp->random];
           /*
            search = newHead;
            while(search != NULL)
            {
                if (search == oldToNew[tmp->random] )
                {
                    break;
                }
                search = search->next;
            }
            cur->random = search;
            */
            tmp = tmp->next;
            cur = cur->next;
        }
        return newHead;
    }
};

接下来,我们换一种思路,在不用辅助空间情况下实现o(n)时间效率。现在主要问题是不用辅助空间,就不能判断原始节点和当前节点对应关系,因此无法找到随机指针指向节点对应的复制节点。因此思路为将新建复制节点插入原始链表中,并与原始其复制节点保持一定相对关系,这样就可以通过原始节点找到当前新复制的节点了。

依然分为2步,第一步:复制节点,并将复制节点插入到原始节点后面,构建入下图。图中,虚线代表随机指针指向
第一步复制链表节点并插入原链表中

第二步:随机指针指向原始指向位置的next,即为原始指向节点的复制节点
第二步新节点随机指针指向原指向位置next节点

第三步:进行链表拆分,偶数位置拆分出来构建的链表就是新链表
新链表
/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* next;
    Node* random;
    
    Node(int _val) {
        val = _val;
        next = NULL;
        random = NULL;
    }
};
*/
class Solution {
public:
    Node* copyRandomList(Node* head) {
        if (head == NULL)
        {
            return NULL;
        }
        Node* tmp = head;   // 用于遍历的临时指针
        // 第一步,先复制链表,同时将新节点依次插到其复制节点后面
        while(tmp != NULL)
        {
            // cout << tmp->val << " ";
            Node* node = new Node(tmp->val);
            node->next = tmp->next;    // 新建节点next转化为原节点next,实现插入
            tmp->next = node;   // 原节点next转化为当前新建节点
            tmp = node->next;
        }
        // tmp = head;
        /* 验证下复制完的对不对
        while(tmp != NULL)
        {
            cout << tmp->val << " ";
            tmp = tmp->next;
        }
        */
        // 第二步:开始复制随机指针
        tmp = head;
        while(tmp != NULL)
        {
            // cout << tmp->val << " ";
            if (tmp->random == NULL)
            {
                tmp = tmp->next->next;
                continue;
            }
            tmp->next->random = tmp->random->next;
            tmp = tmp->next->next;
        }
        // 第三步,进行拆分
        Node* newHead = head->next;
        tmp = head;
        Node* tmp2 = newHead;
        while(tmp != NULL && tmp2 != NULL)
        {
            tmp->next = tmp2->next;
            tmp = tmp2->next;
            if (tmp == NULL)
            {
                break;
            }
            tmp2->next = tmp->next;
            tmp2 = tmp2->next;
        }
        tmp2->next = NULL;
        return newHead;
    }
};

面试题27:二叉搜索树和双向链表

输入一棵二叉搜索树,将该二叉搜索树转化为有序的双向链表。要求不能创建任何新节点,只能调整树中结点指针的指向。
leetcode链接 https://leetcode-cn.com/problems/er-cha-sou-suo-shu-yu-shuang-xiang-lian-biao-lcof/

/*
// Definition for a Node.
class Node {
public:
   int val;
   Node* left;
   Node* right;>
   Node() {}>
   Node(int _val) {
       val = _val;
       left = NULL;
       right = NULL;
   }>
   Node(int _val, Node* _left, Node* _right) {
       val = _val;
       left = _left;
       right = _right;
   }
};
*/
class Solution {
public:
   void treeToDoublyList(Node* root, Node** last)
   {
       if (root == NULL)
       {
           return;
       }
       Node* cur = root;
       if (cur->left != NULL)
       {
           treeToDoublyList(cur->left, last);
       }      
       if (*last != NULL)
       {
           // cout << (*last)->val << " ";
           cur->left = *last;
           (*last)->right = cur;
       }
       else
       {
           cur->left = NULL;
       }
       *last = cur;
       if (cur->right != NULL)
       {
           treeToDoublyList(cur->right, last);
       }
       return;
   }
   Node* treeToDoublyList(Node* root) {
       if (root == NULL)
       {
           return NULL;
       }
       Node* last = NULL;
       treeToDoublyList(root, &last);
       Node* begin = last;
       while(begin != NULL && begin->left != NULL)
       {
           begin = begin->left;
       }
       begin->left = last;
       last->right = begin;   // 注意连接头尾指针,不然leetcode报莫名其妙的错误,报有调用空指针,找了半天,无语
       return begin;
   }
};

解题思路:中序遍历转化成双向链表
按照中序遍历的顺序,当我们遍历到根节点时,其左子树已经遍历完并转换为有序链表,且当前链表最后一个节点就是左子树中最大节点。将根节点链接到当前返回有序链表即完成与左子树合并。然后遍历右子树,右子树遍历完链表头节点为其最小节点,将根节点与链表头节点相连,即完成其与右子树合并。通过递归,最终完成整棵树的合并。

面试题28:字符串的排列

输入一个字符串,打印出该字符串中字符的所有排列。例如输入字符串是abc,则打印出其所有排列是abc,acb,bac,bca,cab,cba,注意这个题是可能有重复字符的
leetcode链接 https://leetcode-cn.com/problems/zi-fu-chuan-de-pai-lie-lcof/ https://leetcode-cn.com/problems/permutation-ii-lcci/

class Solution {
public:
   void permutation(string s, int index, vector<string>& result)
   {
       if (index >= s.length() - 1)
       {
           result.push_back(s);
           return;
       }
       vector<char> his;  // 记录替换过的char,遇到重复的,直接continue
       for(int i = index + 1; i < s.length(); i++)
       {
           string tmp = s;
           if (find(his.begin(), his.end(), s[i]) == his.end())
           {
               his.push_back(s[i]);
           }
           else
           {
               continue;
           }
           char ctmp = tmp[index + 1];
           tmp[index + 1] = tmp[i];
           tmp[i] = ctmp;
           permutation(tmp, index + 1, result);
       }
       return;
   }
   vector<string> permutation(string s) {
       vector<string> result;
       if (s.length() == 0)
       {
           return result;
       }
       vector<char> his;  // 记录替换过的char,遇到重复的,直接continue
       for(int i = 0; i < s.length(); i++)
       {
           // 交换,得到新字符串snew
           string sNew = s;
           if (find(his.begin(), his.end(), s[i]) == his.end())
           {
               his.push_back(s[i]);
           }
           else
           {
               continue;
           }
           char tmp = sNew[0];
           sNew[0] = s[i];
           sNew[i] = tmp;
           permutation(sNew, 0, result);
       }
       return result;
   }
};

解题思路:无重复字符串排列思路:
先求第一位可能出现的情况,将第一位固定后,求后面各位可能出现的情况,一位一位固定,递归调用,直到达到预期长度,则为一种情况。跟“面试题12:打印1到最大n位的所有数”中的全排列法,思路有点相似。
参考链接 https://blog.csdn.net/volcano1995/article/details/89705689
第一步求所有可能出现在第一个位置的字符(即把第一个字符和后面的所有字符交换[相同字符不交换]);
第二步固定第一个字符,求后面所有字符的排列。这时候又可以把后面的所有字符拆成两部分(第一个字符以及剩下的所有字符),依此类推。这样,我们就可以用递归的方法来解决。

递归过程

无重复字符串的排列组合。https://leetcode-cn.com/problems/permutation-i-lcci/

class Solution {
public:
    void permutation(string s, int index, vector<string>& result)
    {
        if (index >= s.length() - 1)
        {
            result.push_back(s);
            return;
        }
        for(int i = index + 1; i < s.length(); i++)
        {
            string tmp = s;
            char ctmp = tmp[index + 1];
            tmp[index + 1] = tmp[i];
            tmp[i] = ctmp;
            permutation(tmp, index + 1, result);
        }
        return;
    }
    vector<string> permutation(string s) {
        vector<string> result;
        if (s.length() == 0)
        {
            return result;
        }
        for(int i = 0; i < s.length(); i++)
        {
            // 交换,得到新字符串snew
            string sNew = s;
            char tmp = sNew[0];
            sNew[0] = s[i];
            sNew[i] = tmp;
            // cout << sNew << endl;
            // permutation(s, 0, s[i], result);
            permutation(sNew, 0, result);
        }
        return result;
    }
};

有重复字符串的排列组合。 https://leetcode-cn.com/problems/permutation-ii-lcci/
解题思路:当有重复字符,需要保证相同字符只重复一次。

class Solution {
public:
    void permutation(string s, int index, vector<string>& result)
    {
        if (index >= s.length() - 1)
        {
            result.push_back(s);
            return;
        }
        vector<char> his;  // 记录替换过的char,遇到重复的,直接continue
        for(int i = index + 1; i < s.length(); i++)
        {
            string tmp = s;
            if (find(his.begin(), his.end(), s[i]) == his.end())
            {
                his.push_back(s[i]);
            }
            else
            {
                continue;   // 保证相同元素不会再重新交换一次
            }
            char ctmp = tmp[index + 1];
            tmp[index + 1] = tmp[i];
            tmp[i] = ctmp;
            permutation(tmp, index + 1, result);
        }
        return;
    }
    vector<string> permutation(string s) {
        vector<string> result;
        if (s.length() == 0)
        {
            return result;
        }
        vector<char> his;  // 记录替换过的char,遇到重复的,直接continue
        for(int i = 0; i < s.length(); i++)
        {
            // 交换,得到新字符串snew
            string sNew = s;
            if (find(his.begin(), his.end(), s[i]) == his.end())
            {
                his.push_back(s[i]);
            }
            else
            {
                continue;   // 保证相同元素不会再重新交换一次
            }
            char tmp = sNew[0];
            sNew[0] = s[i];
            sNew[i] = tmp;
            permutation(sNew, 0, result);
        }
        return result;
    }
};

本题扩展,求字符所有组合。例如输入还是abc,组合有a,b,c,ab,ac,bc,abc,注意交换位置的算一个,譬如ac和ca只记录为一个

//
// Created by Xue,Lin on 2020/6/28.
//
#ifndef UNTITLED_COMBINE_NUM_H
#define UNTITLED_COMBINE_NUM_H
# include<iostream>
# include<vector>
# include "string"
using namespace std;
void combination(string alpa, int length, vector<char>& tmp, vector<string>& result)
{
   if (length <= 0)
   {
       string a = "";
       for(int i = 0; i < tmp.size(); i++)
       {
           a += tmp[i];
       }
       // cout << a << endl;
       result.push_back(a);
       return;
   }
   if (alpa == "")
   {
       return;
   }
   tmp.push_back(alpa[0]);  // 当选中当前第一个元素
   // cout << "begin: " << tmp.size() << endl;
   // cout << length -1 << endl;
   // 从剩余元素中选取m-1个
   combination(alpa.substr(1, alpa.length()-1), length-1, tmp, result);
   // 将第一个元素弹出vector
   tmp.pop_back();
   // cout << "end: " << tmp.size() << endl;
   // 从剩余元素中选取m个
   combination(alpa.substr(1, alpa.length()-1), length, tmp, result);
   return;
}
vector<string> combination(string alpa)
{
   vector<string> result;
   if (alpa == "")
   {
       return result;
   }
   vector<char> tmp;
   for(int i = 1; i < alpa.length(); i++)
   {
       combination(alpa, i, tmp, result);
   }
   return result;
}
#endif //UNTITLED_COMBINE_NUM_H

解题思路:
输入是n个字符,则这n个字符能构成长度为1,长度为2,长度为n的组合。在求n个字符的长度为m的组合是,我们把n个字符分为两部分,第一个字符和其余的所有字符。如果组合中包含第一个字符,则下一步在剩余的字符中选取m-1个字符。如果组合中不包含第一个字符,则下一步在剩余n-1个字符中选择m个字符。也就是说,我们可以把求n个字符组成长度为m的组合问题分解成两个子问题,分别求n-1个字符中长度为m-1的组合,以及求n-1个字符长度为m的组合。这两个问题都用递归来实现。

相关题目:输入一个含有8个数字的数组,判断有没有可能把这8个数字分别放在正方体的8个顶点上,使得正方体上三组相对的面上4个顶点和都相等。

立方体示意图

//
// Created by Xue,Lin on 2020/6/28.
//
#ifndef UNTITLED_CHECK_CUBE_H
#define UNTITLED_CHECK_CUBE_H
bool check(int* data, int n)
{
   return (data[0]+data[1]+data[2]+data[3]) == (data[4]+data[5]+data[6]+data[7]) && \
   (data[0] + data[2] + data[4] + data[6]) == (data[1] + data[3] + data[5] + data[7]) && \
   (data[0] + data[1] + data[4] + data[5]) == (data[2] + data[3] + data[4] + data[7]);
}
bool Permutation(int data[], int* change, int begin, int n)
{
   if (begin >= n)
   {
       return check(change, n);
   }
   bool result = false;
   for(int i = begin; i < n; i++)
   {
       int* arr = change;
       int tmp = change[begin];
       change[begin] = change[i];
       change[i] = tmp;
       if (Permutation(data, change, begin+1, n))
       {
           return true;
       }
   }
   return result;
}
bool Permutation(int data[], int n)
{
   for(int i = 0; i < n; i++)
   {
       int* change = data;
       int tmp = change[0];
       change[0] = change[i];
       change[i] = tmp;
       if (Permutation(data, change, 0, n))
       {
           return true;
       }
   }
   return false;
}
#endif //UNTITLED_CHECK_CUBE_H

解题思路:相当于a1,a2,a3,a4,a5,a6,a7,a8这8个数字的排列组合,然后判断这个组合是不是符合题意,即a1+a2+a3+a4=a5+a6+a7+a8,a1+a3+a5+a7=a2+a4+a6+a8,a1+a2+a5+a6=a3+a4+a7+a8。全排列思路与面试题28相同。

相关题目:在8*8的国际象棋上摆8个皇后,使其不能相互攻击,即任意两个皇后不能在同一行,同一列或同一对角线上。请问一共有多少种摆法。 leetcode链接 https://leetcode-cn.com/problems/eight-queens-lcci/ 别人写的回溯法,自己写的超时,有时间再回来研究

class Solution {
public:
   vector<vector<string>> res;
/* 输入棋盘边长 n,返回所有合法的放置 */
vector<vector<string>> solveNQueens(int n) {
   // '.' 表示空,'Q' 表示皇后,初始化空棋盘。
   vector<string> board(n, string(n, '.'));
   backtrack(board, 0);
   return res;
}
// 路径:board 中小于 row 的那些行都已经成功放置了皇后
// 选择列表:第 row 行的所有列都是放置皇后的选择
// 结束条件:row 超过 board 的最后一行
void backtrack(vector<string>& board, int row) {
   // 触发结束条件
   if (row == board.size()) {
       res.push_back(board);
       return;
   }
   
   int n = board[row].size();
   for (int col = 0; col < n; col++) {
       // 排除不合法选择
       if (!isValid(board, row, col)) 
           continue;
       // 做选择
       board[row][col] = 'Q';
       // 进入下一行决策
       backtrack(board, row + 1);
       // 撤销选择
       board[row][col] = '.';
   }
}
bool isValid(vector<string>& board, int row, int col) {
   int n = board.size();
   // 检查列是否有皇后互相冲突
   for (int i = 0; i < n; i++) {
       if (board[i][col] == 'Q')
           return false;
   }
   // 检查右上方是否有皇后互相冲突
   for (int i = row - 1, j = col + 1; 
           i >= 0 && j < n; i--, j++) {
       if (board[i][j] == 'Q')
           return false;
   }
   // 检查左上方是否有皇后互相冲突
   for (int i = row - 1, j = col - 1;
           i >= 0 && j >= 0; i--, j--) {
       if (board[i][j] == 'Q')
           return false;
   }
   return true;
}>

};

解题思路:按上面思路全排列之后,再判断。测试用例能过,但是会超时。。参考链接,讲了回溯法。

class Solution {
public:
    bool checkok(vector<int> change, int n)
    {
        if (n == 0 || n == 1)
        {
            return true;
        }
        if (n == 2)
        {
            return false;
        }
        vector<int> sub;
        vector<int> add;
        for(int i = 0; i < n; i++)
        {
            int tmp = change[i] - i;
            if (find(sub.begin(), sub.end(), tmp) == sub.end())
            {
                sub.push_back(tmp);
            } else
            {
                return false;
            }
            tmp = change[i] + i;
            if (find(add.begin(), add.end(), tmp) == add.end())
            {
                add.push_back(tmp);
            } else
            {
                return false;
            }
        }
        return true;
    }
    void solveNQueens(vector<int> arr, vector<int>& change, int index, int length, vector<vector<int>>& result1)
    {
        // cout << index << endl;
        if (index >= length - 1)
        {
            if (checkok(change, length))
            {
                result1.push_back(change);
            }
            /*
            for(int j = 0; j < change.size(); j++)
            {
                cout << change[j] << " ";
            }
            cout << endl;
            */
            return;
        }
        for(int i = index + 1; i < length; i++)
        {
            vector<int> tmpArr = change;
            int tmp = tmpArr[index+1];
            tmpArr[index+1] = tmpArr[i];
            tmpArr[i] = tmp;
            solveNQueens(arr, tmpArr, index+1, length, result1);
        }
        return;
    }
    vector<vector<string>> solveNQueens(int n) {
        vector<vector<string>> result;
        vector<vector<int>> result1;
        if (n == 0)
        {
            return result;
        }
        vector<int> arr;
        for (int i = 0; i < n; i++)
        {
            arr.push_back(i);
        }
        for(int i = 0; i < n; i++)
        {
            vector<int> change = arr;
            int tmp = change[0];
            change[0] = change[i];
            change[i] = tmp;
            // cout << "1" << endl;
            solveNQueens(arr, change, 0, n, result1);
        }
        for(int i = 0; i < result1.size(); i++)
        {
            vector<string> sinVec;
            for(int j = 0; j < result1[i].size(); j++)
            {
                // cout << result1[i][j] << " ";
                string tmp = "";
                for(int z = 0; z < n; z++)
                {
                    if (z == result1[i][j])
                    {
                        tmp += 'Q';
                        continue;
                    }
                    tmp += '.';
                }
                sinVec.push_back(tmp);
            }
            // cout << endl;
            result.push_back(sinVec);
        }
        return result;
    }
};

c++【拾遗】 回溯算法
参考链接 https://leetcode-cn.com/problems/n-queens/solution/hui-su-suan-fa-xiang-jie-by-labuladong/
回溯法的核心就是递归调用的过程,在递归调用之前「做选择」,在递归调用之后「撤销选择」

def backtrack(路径, 选择列表):
   if 满足结束条件:
       result.add(路径)
       return
   
   for 选择 in 选择列表:
       做选择
     backtrack(路径, 选择列表)
      撤销选择

例如字符全排列实现,比上面那种交换的要好理解,但是增加了遍历情况,性能会差些

/* 主函数,输入一组不重复的数字,返回它们的全排列 */
List<List<Integer>> permute(int[] nums) {
   // 记录「路径」
   LinkedList<Integer> track = new LinkedList<>();
   backtrack(nums, track);
   return res;
}>
// 路径:记录在 track 中
// 选择列表:nums 中不存在于 track 的那些元素
// 结束条件:nums 中的元素全都在 track 中出现
void backtrack(int[] nums, LinkedList<Integer> track) {
   // 触发结束条件
   if (track.size() == nums.length) {
       res.add(new LinkedList(track));
       return;
   }
   
   for (int i = 0; i < nums.length; i++) {
       // 排除不合法的选择
       if (track.contains(nums[i]))
           continue;
       // 做选择
       track.add(nums[i]);
       // 进入下一层决策树
       backtrack(nums, track);
       // 取消选择
       track.removeLast();
   }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容