zoukankan      html  css  js  c++  java
  • [算法] 动态规划 (2)

    之前写过一篇关于 DP 文章,总结了几种常见的动态规划问题。(传送门

    今天去了华为面试(3轮),每一轮都让手写一道算法题(都是动态规划的) ,不得不说现在的招聘要求真的是“水涨船高”(保研也是)。上半年去面实习生的时候,只有两轮面试,都没要求写算法题。可能是打了一波贸易战,坑位少了,自然要求也高。也不知道程序员的“热钱”还能恰多久,希望能赶在寒冬来临之前恰个饱饭。

    现在写下这几道题的总结,希望对后人有帮助。

    Rod Cutting

    棍棒切割问题。

    给定一段长度为 (n) 的的棍棒,和一个价格表 (p_i (i=1,...,n)) , (p_i) 表示长度为 (i) 的棍棒的价格。 求如何切割长度为 (n) 的棍棒,使得价格最大,求最大价格。

    例如,给出价格表如下:

    长度 (i) 1 2 3 4 5 6 7 8 9 10
    价格 (p_i) 1 5 8 9 10 17 17 20 24 30

    现在给定 (n=3) 的棍棒,切割位置有 2 处,那么切割方案有 $2^{2-1} = 2 $ 种:

    • 3 = 0 +3:价格为 8
    • 3 = 1 + 2:价格为 6

    显然,对于任意的 (n) ,可切割位置为 (n-1) 个,所以切割方案有 $frac {2^{n-1}} {2} $ 种,因此如果是穷举,复杂度达到 (O(2^n))

    现在考虑使用动态规划的解法。

    dp[i] 表示 长度为 (i) 的棍棒的最大价格

    状态转移方程:

    [dp[i] = egin{cases} 0 quad if quad i = 0 \ 1 quad if quad i = 1 \ max(dp[i-j]+price[j]) quad 0≤j≤i end{cases} ]

    需要注意的 2 个地方:

    • price[0] = 0
    • dp 数组的大小是 n+1

    Python实现:

    import sys
    
    '''
    dp[i]表示:长度 i 的 rod 能够获得的最大利润
    dp[0] = price[0] = 0
    dp[1] = price[1]
    dp[i] = max(dp[i-j]+price[j]),1<=j<=i
    '''
    prices = [0, 1, 5, 8, 9, 10, 17, 17, 20, 24, 30]
    
    
    def rodCutting(size: int, prices: list) -> int:
        dp = [0 for i in range(0, size+1)]
        for i in range(0, size+1):
            dp[i] = prices[i]
            for j in range(1, i):
                dp[i] = max(dp[i], dp[i - j] + prices[j])
        print(dp)
        return dp[size]
    
    
    print(rodCutting(4, prices))
    

    GetNextLarge

    后来发现好像是 Leetcode 上面的一道题

    给定一个链表, 输出每个节点,在它后面的第一个不小于它的节点,不存在则是-1。

    Sample:
    Input: [1,5,6,0,1,5,8]
    Output:[5,6,8,1,5,8,-1]
    

    暴力解法:(O(N^2)) ,需要给出 (O(N)) 的解法(当时写了个暴力法,面试官提示有 (O(N)) 的解法,但是没答出来,还好还是过了这一轮面试)。

    一种解法是利用栈:从前往后扫描这个链表,每扫描一个节点,节点元素 val 压栈,进栈的条件是 val < s.top,否则(也就是 s.top <= val)该节点的 val 就是 s.topnext large value。(也就是说,整个过程中,从 bottomtop ,栈是呈降序排列的)

    C++代码 :

    vector<int> getNextLarge(int a[], int len)
    {
        stack<int> s;
        vector<int> v(len, -1);
        for (int i = 0; i < len - 1; i++)
        {
            if (s.empty() || a[i] < a[s.top()])
                s.push(i);
            else
            {
                while (!s.empty() && a[s.top()] < a[i])
                {
                    int x = s.top();
                    s.pop();
                    v[x] = a[i];
                }
                s.push(i);
            }
        }
        return v;
    }
    int main()
    {
        int a[] = {1, 3, 1, 8, 9, 3, 1, 8, 1, 7, 1};
        auto v = getNextLarge(a, 11);
        for (int i = 0; i < 11; i++)
            cout << setw(4) << a[i];
        cout << endl;
        for (auto x : v)
            cout << setw(4) << x;
    }
    

    最小路径和

    给定一个 (n×n) 的二维数组,求出从 (0,0)(n-1,n-1) 的路径中,有一个和最小的路径,给出最小和。

    例如:

    [1, 2, 3]
    [4, 9, 2]
    [1, 2, 1]
    

    最小的路径是:

    • 1->2->3->2->1
    • 1->4->1->2->1

    树塔问题的变种,求出具体的路径也不难(开一个二维数组记录路径即可),在这里给出求最小和的DP解法。

    dp[i, j] 表示从 (i,j)(n-1,n-1) 的最小路径和

    边界条件:

    [dp[n-1][n-1] = a[n-1][n-1] \ dp[i][n-1] = a[i][n-1] + dp[i+1][n-1] \ dp[n-1][j] = a[n-1][j] + dp[n-1][j+1] ]

    状态转移方程:

    [dp[i,j] = a[i,j] + min(dp[i+1,j], dp[i,j+1]) ]

    Python代码实现:

    n = 3
    plat = [
        [1, 2, 3],
        [4, 9, 2],
        [1, 2, 1]
    ]
    
    dp = [[0 for i in range(3)] for i in range(3)]
    
    dp[n - 1][n - 1] = plat[n - 1][n - 1]
    
    for i in reversed(range(0, n - 1)):
        dp[i][n - 1] = plat[i][n - 1] + dp[i + 1][n - 1]
    
    for j in reversed(range(0, n - 1)):
        dp[n - 1][j] = plat[n - 1][j] + dp[n - 1][j + 1]
    
    for i in reversed(range(0, n - 1)):
        for j in reversed(range(0, n - 1)):
            dp[i][j] = plat[i][j] + min(dp[i + 1][j], dp[i][j + 1])
    
    print(dp[0][0])
    

    最长公共子串

    这是 LCS 的变种。

    最长公共子串(Longest Common Substring)与最长公共子序列(Longest Common Subsequence)的区别: 子串要求在原字符串中是连续的,而子序列则只需保持相对顺序一致,并不要求连续。

    最长公共子序列的解法,请看这里

    例如给出:

    string s1 = "abcde";
    string s2 = "abde";
    

    要求出最大公共子串的长度,显然这里答案是 2"ab" 或者 "de")。

    当时我就无脑写了个 dp 给了面试官,结果那个面试官好像⑧太懂算法,我现场手动算了一个例子给他看,他好像还是⑧懂,也不知道记录我做没做对。(可能这题想考基本功,面试官想听暴力穷举法的思路)

    (dp[i,j]) 表示以 (s1[i]) 结尾的,且以 (s2[j]) 结尾的最大公共子串的长度。

    因为“子串”要求是连续的,所以在状态定义时大多数都是定义为“以XX结尾”,这样才能保证连续。

    边界条件:

    [dp[0,j] = 0, 0≤j≤len(s2) \ dp[i,0] = 0, 0≤i≤len(s1) ]

    状态转移方程:

    [dp[i,j] = egin{cases} dp[i-1,j-1]+1 quad if quad s1[i] == s2[j] \ 0 quad quad quad quad quad quad quad quad if quad s1[i]!=s2[j] end{cases} ]

    Python实现:

    s1, s2 = "abcde", "abde"
    # s1, s2 = "aaaaaaaaaa", "aaaa"
    # s1, s2 = "helloworld", "hellohelloworldhello"
    
    
    def solve(s1: str, s2: str) -> int:
        len1 = len(s1)
        len2 = len(s2)
        dp = [[0 for i in range(0, len2 + 1)] for i in range(0, len1 + 1)]
        maxlen = 0
        for i in range(1, len1+1):
            for j in range(1, len2+1):
                if s1[i - 1] == s2[j - 1]:
                    dp[i][j] = dp[i - 1][j - 1] + 1
                else:
                    dp[i][j] = 0
                    # dp[i][j] = max(dp[i-1][j], dp[i][j-1]) #最长公共子序列
                maxlen = max(maxlen, dp[i][j])
        return maxlen
    
    
    print(solve(s1, s2))
    
  • 相关阅读:
    HTML表单
    CSS等高布局的6种方式
    HTML用户反馈表单
    HTML美化修饰<A>
    sql查询语句 --------一个表中的字段对应另外一个表中的两个字段的查询语句
    jq 表格添加删除行
    js 静止f1到f12 和屏蔽鼠标右键
    手机自适应页面的meta标签
    tp3.2 的验证码的使用
    php多线程抓取网页
  • 原文地址:https://www.cnblogs.com/sinkinben/p/11573595.html
Copyright © 2011-2022 走看看