笔试算法汇总

已完成部分:背包问题、排序、堆

引用leetcode上面的一张图,说明常见的面试内容:
IT面试知识点

下面分别介绍上面的算法。

输入输出

在编程题中,经常需要程序具有接收从终端输入字符数据的功能,下面分别以c、c++、python为例,演示如何接收用户输入。要求:用户从终端分别输入一个整数和一个小数,中间用一个或多个空格分隔,要求程序能够读入数据并打印出来。

  • c语言版
// read.c
#include <stdio.h>
int main(int argc, char* argv[]){
    int a = 0;
    float b = 0.0;
    scanf("%d %f", &a, &b);
    printf("a=%d, b=%f\n", a, b);
    return 0;
}

如果想在输入的整数和小数之间用逗号隔开,可以将调用scanf函数的行改为:scanf("%d,%f", &a, &b); 允许在逗号前后存在任意多的空格、制表符、回车符。

  • c++版
// read-1.cpp
#include <iostream>
using std::cin;
using std::cout;
using std::endl;

int main(int argc, char* argv[]){
    int a = 0;
    float b = 0.0;
    cin >> a >> b;
    cout << a << ", " << b << endl;
    return 0;    
}

如果像前面一样,两个数之间用逗号隔开,可以通过读入一行,之后找到逗号所在位置并将其分割,最后分别转换成整数和小数,不过整个过程较为麻烦,建议使用c中的scanf函数。

  • python版
# read.py
line = input()
s1, s2 = line.split()
a, b = int(s1), float(s2)
print("a=%d, b=%f" %(a,b))

如果两个数之间用逗号隔开,可以将字符串分割的语句改为 s1, s2 = line.split(',') 即可。

数据结构

程序 = 数据结构+算法。其中,数据结构是底层,算法是高层,因此我们先对数据结构的相关知识进行梳理。

字符串

字典树

后缀树

正则式的使用

数组

链表

单/双向链表

跳舞链

快慢指针

队列/栈

树/图

  • 完全二叉树:若设二叉树的深度为h,除第h层所有的结点都连续集中在最左边,这就是完全二叉树。

最近公共祖先

并查集

堆(Heap)是一种重要的数据结构,是实现优先队列(priority queue)首选的数据结构,其中最常见的是二叉堆。

小/大根堆

二叉堆具有如下的性质:

  • 二叉堆是一棵完全二叉树;
  • 二叉堆中某个节点的值总是不大于(或不小于)其孩子节点的值,称为小根堆(大根堆);
  • 堆树中每个节点的子树都是堆树;

如下图所示,即为小根堆:
小根堆

二叉堆通常用数组进行存储,根节点为索引等于1的元素,索引为i的元素,其父节点为索引等于⌊i / 2⌋的元素,其左子节点索引为 2×i,其右子节点索引为2×i+1,索引为0的元素可以用于保存节点的总个数。

对于堆而言,最主要的两个函数为pushpop,下面我们用c++实现一个大根堆并使用该堆:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

class MaxHeap{
private:
    vector<int> a;
public:
    MaxHeap(){
        a.push_back(0);
    }
    MaxHeap(const vector<int>& raw):a(raw){
        a.insert(a.begin(), a.size());
        heapify(a);
    }
    void heapify(vector<int>& raw){
        for(int i = (raw.size()-1)/2; i>0; --i)
            siftdown(raw, i);
    }
    void siftup(vector<int>& raw, int index){
        while(index > 1){
            int parent = index / 2;
            if(raw[parent]<raw[index]){
                swap<int>(raw[parent], raw[index]);
                index /= 2;
            }else
                break;
        }
    }
    void push(int v){
        a[0] += 1;
        a.push_back(v);
        siftup(a, a.size()-1);
    }
    void siftdown(vector<int>& raw, int index){
        while(index*2<raw.size()){
            int selected_son=index*2, right_son=index*2+1;
            if(right_son<a.size() && a[right_son]>a[selected_son])
                selected_son = right_son;
            if(a[selected_son]<=a[index]) break;
            swap<int>(a[selected_son], a[index]);
            index = selected_son;
        }
    }
    int pop(){
        if(a.size()==1) return INT_MIN;
        a[0] -= 1;
        int ret = a[1];
        a[1] = a.back();
        a.pop_back();
        siftdown(a, 1);
        return ret;
    }
    size_t size(){
        return a.size()-1;
    }
};
int main(int argc, char* argv[]){
    MaxHeap heap;
    heap.push(2); heap.push(7); heap.push(26); heap.push(25);
    heap.push(19); heap.push(17); heap.push(1); heap.push(90);
    heap.push(3); heap.push(36);
    //vector<int> a{2, 7, 26, 25, 19, 17, 1, 90, 3, 36};
    //MaxHeap heap(a);
    while(heap.size())
        cout << heap.pop() << " ";
    cout << endl;
    return 0;
}

下面的动图来自于: https://blog.csdn.net/qq_35109096/article/details/81513577

上述的pushpop 的动图为:

push的过程

pop的过程

下面是 heapify 的过程,heapify 是指将一个数组变为符合heap要求的数组,动图来自 songwell2014

heapify.gif

注意:上面的动图是自底向上的建堆规程,其时间复杂度为 O(n),分析过程见:https://www.zhihu.com/question/264693363/answer/291397356

可并堆

可并堆是合并效率较高的堆。如果是二叉堆,那么在合并堆时最朴素的想法是将size小的堆中元素插入到size大的堆中,其时间复杂度为 O(n logn)。

可并堆(mergeable heap)是一类支持可合并的堆(即合并的时间复杂度较低,小于O(n) 的堆),包括左偏树、斜堆、二项堆、配对堆、斐波那契数列堆。

常用的可并堆为左偏树,相关介绍见:https://www.luogu.org/blog/cytus/ke-bing-dui-zhi-zuo-pian-shu

编程库中的堆

c语言中没有提供堆的结构或函数。在c++中的algorithm库中提供和堆相关的操作,包括make_heap/pop_heap/push_heap,使用的demo如下:

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

int main(int argc, char* argv[]){
    vector<int> heap{5, 8, 0};
    // auto comp = less<int>();
    auto comp = greater<int>();
    make_heap(heap.begin(), heap.end(), comp);
    heap.push_back(19); push_heap(heap.begin(), heap.end(), comp);
    heap.push_back(3); push_heap(heap.begin(), heap.end(), comp);
    heap.push_back(17); push_heap(heap.begin(), heap.end(), comp);
    heap.push_back(1); push_heap(heap.begin(), heap.end(), comp);
    while(heap.size()){
        pop_heap(heap.begin(), heap.end(), comp);
        cout << heap.back() << " ";
        heap.pop_back();
    }
    cout << endl;
}

用python实现相同功能的代码为:

import heapq
a = [5, 8, 0]
heapq.heapify(a)
heapq.heappush(a, 19)
heapq.heappush(a, 3)
heapq.heappush(a, 17)
heapq.heappush(a, 1)
while len(a):
    print(heapq.heappop(a), end=" ")
print()

哈希表

算法

排序算法

如果试题考察的重点不是排序算法,我们可以直接调用库中的排序算法,下面分别演示c、c++和python中排序算法的调用。

  • c
#include <stdio.h>
// qsort/ heapsort/ mergesort 都在stdlib库中
#include <stdlib.h>

int cmp(const void* p1, const void* p2){
    return *(int*)p1-*(int*)p2;      //按从小到大排序
    // return *(int*)p2-*(int*)p1;   //按从大到小排序 
}

int main(){
    int a[] = {2,5,1,7,8,4,2,1};
    int n = sizeof(a)/sizeof(*a);
    qsort(a, n, sizeof(*a), cmp);            //使用快速排序
    //heapsort(a, n, sizeof(*a), cmp);       //使用堆排序
    //mergesort(a, n, sizeof(*a), cmp);      //使用归并排序
    for(int i=0; i<n; ++i)
        printf("%d ", a[i]);
    printf("\n");
}
  • c++
#include <iostream>
#include <vector>
// sort/greater/less函数都在该库中
#include <algorithm>
using namespace std;

int main(int argc, char* argv[]){
    vector<int> a{2,5,1,7,8,4,2,1};
    sort(a.begin(), a.end());
    //sort(a.begin(), a.end(), greater<int>());  //递减排序
    for(int i=0; i<a.size(); ++i)
        cout << a[i] << " ";
    cout << endl;
    return 0;
}
  • python
a = [2,5,1,7,8,4,2,1]
a.sort()  # 递减排序: a.sort(reverse=True)
print(a)

分析一个排序算法时,需要考察一下几个性质:

  • 时间复杂度
  • 空间复杂度
  • 稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,则称排序算法是稳定的

在排序过程中不另外申请内存空间的排序也称为原地排序。

排序算法 best T worst T avg T Memory stability
冒泡排序 O(n) O(n^2) O(n^2) O(1) 稳定
快速排序 O(n logn) O(n^2) O(n logn) O(1) 不稳定
插入排序 O(n) O(n^2) O(n^2) O(1) 稳定
希尔排序 O(n) O(n^s) O(n logn) O(1) 不稳定
选择排序 O(n^2) O(n^2) O(n^2) O(1) 不稳定
堆排序 O(n logn) O(n logn) O(n logn) O(1) 不稳定
归并排序 O(n logn) O(n logn) O(n logn) O(n logn) 稳定
基数排序 O(kn) O(kn) O(kn) O(n) 稳定
计数排序 O(n) O(n) O(n) O(span+n) --

算法的动图主要来自于维基百科。

冒泡排序

冒泡排序
// bubbleSort.cpp
#include<vector>
#include<algorithm>
using std::vector;

void bubbleSort(vector<int>& a){
    for(int i=a.size()-1; i>0; --i){
        bool ok = true;
        for(int j=0; j<i; ++j){
            if(a[j]>a[j+1]){  // 编程时一定要注意判断条件
                swap<int>(a[j], a[j+1]);
                ok = false;
            }
        }
        if(ok) break;
    }
}

快速排序

快速排序
void quickSort(int a[], int left, int right){
    if(left>=right) return;
    int tleft=left, tright=right;
    int v = a[left];
    while(tleft<tright){
        // 注意: 第二个比较符号为<=,如果是<,会导致死循环
        while(tleft<tright && v<=a[tright]) --tright; 
        if(tleft<tright) a[tleft] = a[tright];
        while(tleft<tright && v>=a[tleft]) ++tleft; // 注意点同上
        if(tleft<tright) a[tright] = a[tleft];
    }
    a[tleft] = v;
    quickSort(a, left, tleft-1);
    quickSort(a, tleft+1, right);
}

插入排序

插入排序

如果带排序的数组较为有序,那么直接插入排序的效率会非常高。

#include<vector>
using namespace std;

void insertSort(vector<int>& a){
    for(int i=1; i<a.size(); ++i){
        int v = a[i];
        int j = i-1;
        for(; j>=0 && v<a[j]; --j){
            a[j+1] = a[j];
        }
        a[j+1] = v;
    }
}

希尔排序

希尔排序

希尔排序相当于对插入排序的升级,先以大的跨度将待排序数组变得较为有序,最后使用直接排序。在c++中随机产生一个包含100000个元素的数组,以直接插入的方式进行排序,用时约为 12000ms,用希尔排序算法用时约为 42ms。

#include<iostream>
#include<vector>
using namespace std;

// gap为1,即为一次直接插入排序
void insertSort(vector<int>&a, int gap=1){
    for(int i=gap; i<a.size(); ++i){
        int v = a[i];
        int j = i-gap;
        for(; j>=0 && a[j]>v; j-=gap){
            a[j+gap] = a[j];
        }
        a[j+gap] = v;
    }
}

void shellSort(vector<int>& a){
    for(int i=a.size()/2; i>0; i/=2)
        insertSort(a, i);
}

选择排序

选择排序
#include<vector>
using namespace std;

void selectSort(vector<int>& a){
    for(int i=a.size()-1; i>0; --i){
        int max_index = 0;
        for(int j=1; j<=i; ++j){
            if(a[max_index] <= a[j])
                max_index = j;
        }
        int t = a[max_index];
        a[max_index] = a[i];
        a[i] = t;
    }
}

虽然大多数人都说选择排序是不稳定的算法,不过觉得还是可以保证相同值相对前后顺序不变,也就是说可以为稳定的排序算法。

堆排序

堆排序是利用堆这种数据结构进行排序的算法。首先,将数组变成符合要求的堆,然后不断从堆中弹出元素,使整个数组变得有序。其中,建立堆的时间复杂度为 O(n),每次弹出一个元素的时间复杂度为 O(logn),因此,整个过程的时间复杂度为 O(n+n logn)=O(n logn)。


堆排序
#include <vector>
#include <algorithm>
#include <iostream>

using namespace std;

template <class RandomAccess, class Compare>
void heapSort(RandomAccess& a, Compare cmp){
    make_heap(a.begin(), a.end(), cmp);
    for(int i=0; i<a.size()-1; ++i)
        pop_heap(a.begin(), a.end()-i, cmp);
}
int main(int argc, char* argv[]){
    vector<int> a{3,1,6,7,8,13,1,2};
    //heapSort(a, greater<int>()); // 降序排序
    heapSort(a, less<int>());   // 升序排序
    for(auto v : a)
        cout << v << " ";
    cout << endl;
}

归并排序

归并排序
#include <iostream>
#include <vector>
using namespace std;

void mergeSort(vector<int>& a, int left, int right){
    if(left>=right) return;
    int mid = (left+right)/2;
    mergeSort(a, left, mid);
    mergeSort(a, mid+1, right);
    vector<int> ta(a.begin()+left, a.begin()+right+1);
    int p=left, p1=0, p2=mid+1-left;
    while(p1<mid+1-left && p2<ta.size()){
        if(ta[p1]<=ta[p2]) a[p++] = ta[p1++];
        else a[p++] = ta[p2++];
    }
    while(p1<mid+1-left) a[p++]=ta[p1++];
    while(p2<ta.size()) a[p++]=ta[p2++];
}
int main(){
    vector<int> a{2,1,5,3,6,7,12,2,6};
    mergeSort(a, 0, a.size()-1);
    for(auto v : a) cout << v << " ";
    cout << endl;
}

基数排序

基数排序(radix sort) 是一种非比较型整数排序算法,其原理是将整数按数位切分成不同的数字,然后按每个位数进行比较。它是这样实现的:将所有待比较数值(正整数)统一为同样的数字长度,数字较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。


基数排序

基数排序的python代码为:

import math
def radixSort(a, radix=10):# a是一个整数数组
    # k为最大的位数
    k = math.ceil(math.log(max(a)+1, radix))
    for i in range(k):
        b = [[] for j in range(radix)]
        for v in a:
            b[(v//radix**i)%(radix**i)].append(v)
        del a[:]
        for e in b:
            a.extend(e)
    return a
print(radixSort([3,12,421,22,41,52]))

计数排序

计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。


计数排序
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
void countingSort(vector<int>& a, int minv, int maxv){
    vector<int> buf(maxv-minv+1, 0);
    for(auto v: a)
        buf[v-minv] += 1;
    a.clear();
    for(int i=0; i<maxv-minv+1; ++i){
        a.insert(a.end(), buf[i], minv+i);
    }
}
int main(int argc, char* argv[]){
    vector<int> a{2,5,6,2,3,7,4,3,7,6};
    countingSort(a, *min_element(a.begin(),a.end()),
            *max_element(a.begin(),a.end()));
    for(auto v: a) cout << v << " ";
    cout << endl;
}

搜索算法

回溯

递归

剪枝

图论

最短路径

最小生成树

网络流建模

动态规划

背包问题

关于背包问题,推荐看《背包问题九讲》 这里只对其中的部分内容进行讲解。

  • 0-1 背包问题

0-1背包问题最基本的背包问题,该问题的描述为:有N件物品和一个容量为V的背包,放入第i件物品耗费的空间为C_i,得到的价值为W_i,求解将哪些物品装入背包能使价值总和最大。

如果用暴力枚举法进行问题的求解,N件物品每一件都有选中和不选中两种状态,故共有2^N种情况,求解的时间复杂度为 O(2^N),这种时间复杂度明显是不可容忍的。

假设将前 i-1 个物品放入容量为 v 的背包得到的最大价值为 pack(i-1, v),对于第 i 个物品而言,如果不选中,则最大的价值等于 pack(i-1, v); 如果选中,则最大的价值等于 pack(i-1, v-Ci)+Wi,因此将前 i 个物品放入容量为v的背包得到的最大价值为 max{ pack(i-1, v), pack(i-1, v-Ci)+Wi }。该函数即为状态转移函数。我们定义一个长度为 (N+1)×(V+1) 的二维整数数组a,其中元素 a[i][j] 存放 pack(i, j) 的值,上述0-1背包问题的伪代码为:

a[0..N+1, 0..V+1] <- 0
for i <- 1 to N
    for v <- Ci to V
        a[i, v] <- max{F[i-1, v], F[i-1, v-Ci]+Wi}
ret_value = a[N, V]

上述算法的时间复杂度和空间复杂度都为 O(NM),其实可以将空间复杂度将为 O(M),伪代码如下:

a[0..V+1] <- 0
for i <- 1 to N
    for v <- V to Ci
        a[v] <- max{F[v], F[v-Ci]+Wi}
ret_value = a[V]

下面用一个例子来实践上述问题:华为应用商城举办下应用得积分的活动,月末你还有40兆流量未使用,现在有4个应用可以下载,每个应用需要的流量分别为 12, 13, 23, 36,下载每个应用获得的积分为 10, 11, 20, 30,求用户可以获得的最大积分数为多少?

V = 40
C = [12, 13, 23, 36]
W = [10, 11, 20, 30]
a = [0]*(V+1)
for i in range(len(C)):
    for j in range(V, C[i]-1, -1):
        a[j] = max(a[j], a[j-C[i]]+W[i])
print("ret_value = %d" %(a[V]))

结果为31。

如果需要具体指出需要取出的物品号,那么只能保持空间复杂度为 O(MN):

V = 40
C = [12, 13, 23, 36]
W = [10, 11, 20, 30]
N = len(C)
a = [[0]*(V+1) for _ in range(N+1)]
for i in range(len(C)):
    for j in range(C[i], V+1):
        a[i+1][j] = max(a[i][j], a[i][j-C[i]]+W[i])
print("ret_value = %d" %(a[N][V]))
print("Select apps:", end="")
pv = V
for i in range(N, 0, -1):
    if a[i][pv]!=a[i-1][pv]:
        print(i, end=" ")
        pv -= C[i-1]

求最优解的背包问题题目中,事实上有两种不太相同的问法。有的题目要求“恰好装满背包”时的最优解,有的题目则并没有必须把背包装满,一种区别这两种问法的实现方法是在初始化的时候有所区别。第一种问法,要求恰好装满背包,那么在初始化时除了F[0]为0,其他F[1..V+1]均设为-∞;第二种问法,将 F[0..V+1]都设为0。

  • 完全背包问题

有N种物品和一个容量为V的背包,每种物品都有无限件可能,放入第 i 种物品的费用是 Ci,价值是Wi。求解:将哪些物品装入背包,可以使这些物品的费用总和不超过背包的容量,且价值总和最大。

完全背包问题的转移函数有两个:

  1. F(i, v)=max(F(i-1, v-kC_i)+kW_i | 0\le kC_i \le v)
  2. F(i, v)=max(F(i-1, v), F(i, v-C_i)+W_i)

其中转移函数2的代码更加简洁,时间复杂度也更低,为O(MN),伪代码为:

F[0..V+1] <- 0
for i <- 1..N
    for v <- Ci..V
        F[v] = max(F[v], F[v-Ci]+Wi)

与0-1背包问题唯一不同的地方是第三行v的取值顺序。

  • 多重背包问题

有N种物品和一个容量为V的背包。第i种物品最多有Mi件可用,每件耗费的空间为 Ci,价值为Wi,求解将哪些物品装入背包可以使这些物品耗费的空间总和不超过背包容量,且价值总和最大。可以将N种物品进行扩展,第i种物品的Mi件分别看成不同种物品,因此扩展后一共有 \sum_{i=1}^N M_i 种物品,其中一些物品具有相同的Ci和Wi,因此将多重背包问题变为了0-1背包问题。多重背包问题的转移函数可以写成:F[i, v] = max( F[i-1, v-kC_i]+kW_i \ |\ 0\le k\le min(M_i, \lfloor v/C_i \rfloor))

  • 二维背包问题

对于每件物品,具有两种不同的费用,选择这件物品必须同时付出这两种费用。对于每种费用都有一个可付出的最大值(背包容量)。问怎样选择物品可以得到最大的价值。

设第 i 件物品所需的两种费用分别为 Ci 和 Di。两种费用可付出的最大值(也即两种背包容量)分别为 V 和 U。物品的价值为 Wi。其转移函数可以写成: F[i, v, u] = max(F[i-1, v, u], F[i-1, v-C_i, u-D_i]+W_i),其伪代码为:

a[0..V+1, 0..U+1] <- 0
for i <- 1 to N
    for v <- V to Ci
        for u <- U to Di
            a[v, u] <- max{F[v, u], F[v-Ci, u-Di]+Wi}
ret_value = a[V, U]

二维背包问题的一个例子:https://leetcode.com/problems/ones-and-zeroes/

假定我们有m个0和n个1,另外有一组字符串,其中的每个字符串都由0和1组成,问最多能够组成多少个字符串,例如:m=5, n=3, array={"01", "0001", "111001", "1", "0"},我们可以组成4个字符串,分别为“0”、“1”、“01”,“0001”。

def findMaxForm(V, U, strs):
    N = len(strs)
    bags = [[0]*(U+1) for i in range(V+1)]
    for s in strs:
        zeros = s.count('0')
        ones = s.count('1')
        for v in range(V, zeros-1, -1):
            for u in range(U, ones-1, -1):
                bags[v][u] = max(bags[v][u], bags[v-zeros][u-ones]+1)
    return bags[V][U]

m, n = 5, 3
array = ["01", "0001", "111001", "1", "0"]
print(findMaxForm(m, n, array))
  • 总结

0-1背包问题的基本思路:

a[0..V+1] = 0 // 改为 a[0..V+1]=-∞即变为恰好装满背包的0-1背包问题
for i <- 1 to V
    // 内层循环的层数控制背包问题的维数
    for v <- V..Ci  // 改为 for v <- Ci..V 即变为完全背包问题
        a[v] = max(a[v], a[v-Ci]+Wi)
result = a[V]

最长子序列

计数问题

基础技巧

分治

贪心

二分

倍增

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

推荐阅读更多精彩内容

  • 数据结构算法大全(用 PASCAL 描述) 1.数论算法 求两数的最大公约数 function gcd(a,b:i...
    心想事成_ae7e阅读 484评论 0 0
  • 贪心算法 贪心算法总是作出在当前看来最好的选择。也就是说贪心算法并不从整体最优考虑,它所作出的选择只是在某种意义上...
    fredal阅读 9,076评论 3 52
  • 概述 排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    蚁前阅读 5,101评论 0 52
  • 回溯算法 回溯法:也称为试探法,它并不考虑问题规模的大小,而是从问题的最明显的最小规模开始逐步求解出可能的答案,并...
    fredal阅读 13,508评论 0 89
  • 最近在热播电视剧《三生三世十里桃园》。这部剧看完后,虽然夜华也是很多女子追求的对象,他也爱他心爱的女人,但他却没能...
    码字好玩儿阅读 479评论 0 3