zoukankan      html  css  js  c++  java
  • 《算法导论》摘记/经典算法题集锦

    分治策略

    Diogenes教授有n个被认为是完全相同的VLSI芯片,原则上它们是可以互相测试的。教授的测试装置一次可测二片,当该装置中放有两片芯片时,每一片就对另一片作测试并报告其好坏。一个好的芯片总是能够报告另一片的好坏,但一个坏的芯片的结果是不可靠的。这样,每次测试的四种可能结果如下:

        A芯片报告         B芯片报告     结论

        B是好的          A是好的      都是好的,或都是坏的

        B是好的          A是坏的      至少一片是坏的

        B是坏的          A是好的      至少一片是坏的

        B是坏的          A是坏的      至少一片是坏的

    Q:

    1.证明如果超过n/2个芯片是坏的,使用任何基于这种逐对检测操作的策略,教授都不能确定哪些芯片是好的。

    2.如果超过n/2个芯片是好的,如何O(n)找出所有好的芯片?

    解:对于问题2,只需找出1个好的芯片,就能确定所有的芯片的好坏。
    
    粗暴的方法是拿一个芯片与其他所有芯片配对,因为好的比坏的多,就可以根据多的确定该芯片的好坏,直到找到一个好芯片为止。复杂度是O(n2)的。同时也说明了Q1(否则无法确定好坏)
    
    O(n)怎么做?递归缩小问题规模。
    
    假设有偶数个芯片。两两配对,有a对检测结果为都是好的,b对检测结果为一好一坏,c对检测结果为都是坏的。
    
    取a对中每对的任意一个芯片,组成a个芯片的子问题。易证明该子问题依然满足好的芯片数量多于坏的芯片。(因为b+c对中坏的 >= 好的)
    
     
    
    如果有奇数个芯片。两两配对,有a对检测结果为都是好的,b对检测结果为一好一坏,c对检测结果为都是坏的。还有一个零头。
    
    1.如果零头是坏的,则a对中好的芯片 > 坏的芯片,用a对去检测零头,发现有一半以上的检测结果是坏的,反言之 小于一半的检测结果是好的。
    
    2.如果零头是好的,则a对中好的芯片 >= 坏的芯片,用a对去检测零头,发现大于等于一半的检测结果是好的。
    
    根据结果判断零头是好是坏。如果是1,把零头弃掉;如果是2,把零头加进来,这样该子问题依然满足好的芯片数量多于坏的芯片。
    
     
    如此递归缩小问题规模,每次规模至少减半。
    
    找出一个好零件的时间T1(n) = T1(n/2)+O(n) = O(n),
    找出其他好零件的时间T2(n) = n−1 = O(n),
    所以总的时间T(n)=O(n)。
    View Code
     
     
    概率分析和随机算法
     
    1.生日悖论
    关于如何卡字符串hash
    概率论是一个很神奇的东西。。。
    假设一年有n天,有k个人。
    生日相同的两人对的期望是k(k-1)/(2n), 也就是说O(sqrt(n))的人,生日相同的两人对的期望数是1; O(sqrt(n))的人至少有一对生日相同的概率大于1/2。(参见算导)
    意思就是n个桶,随机丢sqrt(n)个数据,hash冲突的概率极大!
    故一般的hash易被卡。
    如果mod 2^64,可人工构造数据卡hash;mod 1e9左右的,可随机构造数据卡hash。
    而双hash不易被卡。
    以下是一个随机生成字符串的程序。当有两个长度为l的不同字符串hash值相同时输出并退出。
    #define NDEBUG
    #include <bits/stdc++.h>
    #define ll unsigned long long
    #define st first
    #define nd second
    #define pii pair<int, int>
    #define pil pair<int, ll>
    #define pli pair<ll, int>
    #define pll pair<ll, ll>
    #define pw(x) ((1LL)<<(x))
    #define lson l, m, rt<<1
    #define rson m+1, r, rt<<1|1
    
    using namespace std;
    /***********/
    template <class T>
    bool scan (T &ret) {
        char c;
        int sgn;
        if (c = getchar(), c == EOF) return 0; //EOF
        while (c != '-' && (c < '0' || c > '9') ) c = getchar();
        sgn = (c == '-') ? -1 : 1;
        ret = (c == '-') ? 0 : (c - '0');
        while (c = getchar(), c >= '0' && c <= '9') ret = ret * 10 + (c - '0');
        ret *= sgn;
        return 1;
    }
    template<typename T>
    inline int sgn(T a) {return a>0?1:(a<0?-1:0);}
    template <class T1, class T2>
    bool gmax(T1 &a, const T2 &b) { return a < b? a = b, 1:0;}
    template <class T1, class T2>
    bool gmin(T1 &a, const T2 &b) { return a > b? a = b, 1:0;}
    const int inf = 0x3f3f3f3f;
    const ll INF = 1e18;
    /***********/
    
    
    const ll Mod = 1e9+7, base = 997, l = 20, N = 500000;
    char s[N+10];
    map<ll, string> ma;
    void init(){
        srand(time(0));
        int len = 0;
        while(len < N)
            s[len++] = rand()%26+'a';
    }
    int main(){
        init();
    
        ll hash_pow_l = 1;
        for(int i = 1; i <= l; i++)
            hash_pow_l = (hash_pow_l * base) % Mod;
    
        ll val = 0, val2 = 0;
        for(int i = 0; i < l; i++)
            val = (val * base + s[i] - 'a' + 1) % Mod;
        ma.insert( {val, string(s, s+l)} );
    
        string t1, t2;
        for(int i = l; i < N; i++){
            val = (val*base+s[i]-'a'+1)%Mod;
            val = (val+Mod - (s[i-l]-'a'+1)*hash_pow_l%Mod) % Mod;
            if(ma.find(val) == ma.end())
                ma.insert( {val, string(s+i+1-l, s+i+1)} );
            else{
                t1 = ma[val], t2 = string(s+i+1-l, s+i+1);
                break ;
            }
        }
        cout << t1 << endl; cout << t2 << endl;
        val = val2 = 0;
        for(int i = 0; i < t1.size(); i++){
            val = (val*base+t1[i]-'a'+1)%Mod;
            val2 = (val2*base+t2[i]-'a'+1)%Mod;
            cout <<i << ':' << val <<' '<<val2 << endl;
        }
        return 0;
    }
    View Code

    2.特征序列

    假设抛一枚标准的硬币n次,最长连续正面的序列的期望长度有多长?

    答案与lgn等阶, O(log)。

    建堆的复杂度是O(n)

    计数排序:先统计一遍前缀和,然后逆序插入。

    基数排序最好从低有效位开始,从高有效位开始需要分段递归。

    ================================================

    快排期望复杂度的证明:http://www.cnblogs.com/fengty90/p/3768827.html

    ================================================

    经典算法题集锦

    1. 数组a是一个不下降数组。将数组a循环移位后,求某个数出现的最左位置。O(logn)时间复杂度, O(1)空间复杂度。提示:差分

    2. 一个数组,其中某个数出现的次数超过了一半。求该数。O(n)时间复杂度, O(1)空间复杂度。扩展:在一个数组中找出出现次数超过n/3的数字。

    3. 给定一个未排序数组。求该数组在排完序后,相邻两数差的最大值。 O(nlogn)时间复杂度,O(n+k)时间复杂度, O(n)时间复杂度。提示:桶排序

    4 给定一个未排序数组。求该数组在排完序后,连续数字的最大长度。O(nlogn)复杂度,O(n)复杂度。(hashset后,对每个数组中的数字,找其所在段的最大长度,找的同时删去找到的数).

    5. 完美洗牌算法。将一个原始数组进行置换排序。

    6. 二叉排序树:在一棵排序二叉树上找与给定值的差值第k小的值。O(nlogn)? O(klogn)? O(k+logn)!

    7. 给定一个长度为n的有序数组A,找到离目标数m最近的一个长度为k的区间。 O(k+logn)? O(lognlogA)?O(logn+logk)!

    8.给定一个长度为n的无序数组,任意两数不同。请找到任意一个局部最小值。O(logn)  

       拓展:给定一个n*n的无序二维数组,任意两数不同,请找到一个四联通的局部最小值。O(n) 提示:十字架划分,找到最小值,然后再选择更小的子块接着找,同时保留之前找的最小值。

  • 相关阅读:
    上传和下载附件功能
    C#小常识,持续更新..
    动态添加HTML表单控件,无(runat="server")
    Excel技巧 持续更新..
    JS函数集锦 持续更新..
    JS 函数 检验输入是否为数字类型,正整数
    存储过程 游标 事例
    Sql 查询语句中的类型转换
    shell 计数脚本
    centos 获取文件的创建时间
  • 原文地址:https://www.cnblogs.com/dirge/p/6242745.html
Copyright © 2011-2022 走看看