zoukankan      html  css  js  c++  java
  • Idea 02.暴力递归与动态规划(1)

    1,关键词解释

    1.1 暴力递归:

    1, 把问题转化为规模缩小了的同类问题的子问题

    2, 有明确的不需要继续进行递归的条件(base case)

    3, 有当得到了子问题的结果之后的决策过程

    4, 不记录每一个子问题的解

     

    1.2 动态规划:

    1, 从暴力递归中来

    2, 将每一个子问题的解记录下来,避免重复计算

    3, 把暴力递归的过程,抽象成了状态表达

    4, 并且存在化简状态表达,使其更加简洁的可能

    2,学会尝试才能掌握

    2.1 P类问题和NP问题

    P类问题:时间复杂度为多项式; 知道怎么算,让计算机帮我算。

    NP问题:时间复杂度很复杂,指数级或位置; 不知道怎么算,但是知道怎么尝试。

    2.2 尝试的重要性

    学会了尝试,在不断的实践中积累经验才能真正掌握这些算法思想的精髓。许多本科毕业生甚至研究生,都缺乏这种能力。

    3,例题实践

    3.1 求n!的值

    #include <iostream>
    
    using namespace std;
    
    class Factorial {
    public:
        //方法一:递归版
        int factor(int n) {
            if (n<0) return -1;
    
            if (n == 1 || n == 0) return 1;
            else {
                return  n*factor(n - 1);
            }
    
        }
        //方法二:直接法
        int factor2(int n) {
            int res=1;
            for (int i = 1; i <= n; ++i) {
                res *= i;//1×2×3×……×n
            }
            return res;
        }
    };
    
    int main(){
        Factorial test;
        //cout << test.factor(3) << endl;
        cout << test.factor2(4)<<endl;
        return 0;
    }

    3.2 汉诺塔问题

    问题描述:

     三根柱子:"left","mid","right"
    要求:
    1.要把放在“left”杆子上的n个从大到小叠加放置的圆盘移动到“right”杆子上;
    2.移动过程中,一个只能移动一个圆盘,且大的圆盘不能放置在小的圆盘上。
    

    递归分解:

      1. n为1时,"left"杆子上只有一块圆盘,可以直接将它移动至"right"杆子上;(base case:递归出口)
      2. n大于1时,要想使得第1步成立,要先把"left"上面的n-1块圆盘移动至辅助的"mid"杆子上;
      3. 最后,将"mid"杆子上的n-1块圆盘移动至"right"杆子上。
    //题目地址:https://www.nowcoder.com/questionTerminal/7d6cab7d435048c4b05251bf44e9f185 
    
    class Hanoi {
    public:
        vector<string> getSolution(int n) {
            //判断n是否合法输入
            if(n<=0) return res;
            func(n,"left","mid","right");
            return res;
        }
        
        void func(int n, string from, string mid,string to){
            if(n==1)
                res.push_back("move from "+from+" to "+to);
            else{
                func(n-1,from,to,mid);
                func(1,from,mid,to);
                func(n-1,mid,from,to);
            }
        }
        
    private:
        vector<string> res;
    };

     

     

    3.3 打印一个字符串(“abc”)的所有的子序列(注:不是子串)

    a:选或不选   b:选或不选 c:选或不选  2×2×2=8种可能,包含空子序列。
    
    #include <iostream>
    #include <string>
    
    using namespace std;
    
    //void printAllSbu(char str[],int i,string res)
    void printAllSub(char *str,int i, string res) {
        //str[]字符串数组的实际大小为sizeof(str),有效大小为sizeof(str)-1
        if (i == sizeof(str)-1) {
            std::cout << res;
            return;
        }
        else {
            printAllSub(str, i + 1, res);//不选str[i]
            printAllSub(str, i + 1, res + str[i]);//选str[i]
        }
    
    }
    
    void main() {
        string test = "abc";
        char  v[4];
        //strncpy_s 优化后更安全的函数
        strncpy_s(v, test.c_str(), test.length() + 1);//必须加1,还占一个位置
        printAllSub(v, 0, " ");
    }

    3.4 打印一个含n个字母的字符串(如:“abc”)的所有的全排列

    #case1:假设不含重复字母

    递归思路:

      1. (bese case:) n=1时,如果字符串中只有一个元素,直接生成全排列;
      2. 当n>1时,如果能生成n-1个元素的全排列,就能生成n个元素的全排列,以三个字符"abc"为例:
      • 首先我们固定第一个字符a,排列后面的两个字符bc;
      • 当两个字符bc排列求好后,我们把字符b和第一字符a交换,使得b固定在第一个字符,排列后面的两个字符ac;
      • 当两个字符ac排列求好后,我们把字符c和第一字符a交换,使得c固定在第一个字符,排列后面的两个字符ab;这里特别需要注意一点,我们上一步交换了a和b的位置,要想保证我们正确的交换a和c的位置,需要恢复原字符串,先换回a和b的位置因为,我们确定第一个字符串和后面每一个字符串交换,固定第一个位置的元素时,是基于初始字符串abc的次序考虑的。(参考资料的第一篇博文中存在代码错误就是这里没有先恢复原串)

      既然我们已经知道怎么求三个字符的排列,那么固定第一个字符之后求后面两个字符的排列,就是典型的递归思路了

    /*打印一个字符串中所有字母的全排列(假设不含重复字母))*/
    
    #include <iostream>
    #include <string>
    
    using namespace std;
    
    void printAllArr(char str[],int i) {
        int n = sizeof(str)/sizeof(str[0])-1;//获取数组长度 sizeof(str)/sizeof(str[0])
        //cout << n << endl;
    
        if (i == n-1) {
            for(int w  = 0; w < n;++w){
                cout << str[w];
            }
            cout << endl;
            return;
        }
        else {
            for (int j = i; j <n; ++j) {
                swap(str[i], str[j]);
                printAllArr(str, i + 1);
                swap(str[i], str[j]);//没有这一行,结果会出错。
    } } }
    void main() { string test = "abc"; char v[4]; strncpy_s(v, test.c_str(), test.length() + 1);//必须加1,还占一个位置 printAllArr(v, 0); }

    #case2:含有重复字母,且要求输出结果无重复

            关键思路:如果str[i]和str[j]相同,则忽略交换。(如:"abca",遇到第一个a和第四个a则不交换。)

           

    #include <iostream>
    #include <string>
    
    using namespace std;
    
    //判断是否需要交换
    int is_swap(char *str, int begin, int k) {
        int i, flag;
    
        for (i = begin, flag = 1; i < k; i++) {
            if (str[i] == str[k]) {
                flag = 0;
                break;
            }
        }
        return flag;
    }
    
    //打印所有的全排列
    void printAllArr(char str[], int i, int n) {
        /*--------不能在这里获取正确的数组长度----------*/
        //int n = sizeof(str) / sizeof(str[0]);//遗留问题:这里为什么固定是4呢?
        //cout << n << endl;                   //原因:这里参数传递只是数组的首元素指针(32位的内存地址),并不是整个数组,所有固定是4
        //cout << sizeof(str) << endl;           //4
        //cout << sizeof(str[0]) << endl;      //1
    
        if (i == n - 1) {
            for (int w = 0; w < n; ++w) {
                cout << str[w];
            }
            cout << endl;
            return;
        }
        else {
            for (int j = i; j < n; ++j) {
                if (is_swap(str, i, j)) {//判断是否需要交换,相同则不交换
                    swap(str[i], str[j]);
                    printAllArr(str, i + 1,n);
                    swap(str[i], str[j]);
                }
            }
        }
    }
    
    void main() {
        char str[4] = { 'a','b','c','a'};
        int length = sizeof(str) / sizeof(str[0]);//获取数组长度
        //cout <<"length = " <<length << endl;
        printAllArr(str, 0, length);
    }

    参考资料:

    1.输出一个字符串的全排列 (注:该文提供的代码结果有误,少了上面标红的一行代码)

    2.字符串 全排列生成问题 

    3. https://www.nowcoder.com/courses/semester/senior 《牛客高级项目课——(牛客网)》--大牛·左程云

  • 相关阅读:
    广域网(ppp协议、HDLC协议)
    0120. Triangle (M)
    0589. N-ary Tree Preorder Traversal (E)
    0377. Combination Sum IV (M)
    1074. Number of Submatrices That Sum to Target (H)
    1209. Remove All Adjacent Duplicates in String II (M)
    0509. Fibonacci Number (E)
    0086. Partition List (M)
    0667. Beautiful Arrangement II (M)
    1302. Deepest Leaves Sum (M)
  • 原文地址:https://www.cnblogs.com/paulprayer/p/10000214.html
Copyright © 2011-2022 走看看