动态规划入门2

概要

本文继续讲解geeksforgeeks上的面试题。
包括Count number of ways to cover a distance(到达指定距离的走法)、 Longest Path In Matrix(特定矩阵的最长路径)、
Subset Sum Problem(动态规划子集和问题)、
0-1 Knapsack Problem(01揹包问题)

Count number of ways to cover a distance

题目

https://leetcode.com/problems/climbing-stairs/description/
题目与geeksforgeeks上类似,就按LeetCode上说(统一按所给链接的题目讲解),因为有测试样例,方便检验。

分析

本题求解指定步长下阶梯n的走法,可以分解为走n-1阶楼梯和走n-2阶楼梯的走法的子问题的和,即为阶梯n的走法。
有状态转移方程:

climbStairs(d)=climbStairs(d1)+climbStairs(d2)10if d>0if d=0if d<0(1) (1) c l i m b S t a i r s ( d ) = { c l i m b S t a i r s ( d − 1 ) + c l i m b S t a i r s ( d − 2 ) if  d > 0 1 if  d = 0 0 if  d < 0

编程

自顶向下记忆法:

#include <iostream>
#include <cstring>

using namespace std;

class Solution {
public:
    int climbStairs(int n) {
        int dp[n + 1];
        memset(dp, 0, sizeof(dp));
        return cs(n, dp);
    }
    int cs(int n, int dp[]) {
        if(n == 0)
            return dp[n] = 1;
        if(n < 0)
            return 0;
        if(dp[n] > 0)
            return dp[n];
        return dp[n] = cs(n - 1, dp) + cs(n - 2, dp);
    }
};

int main(){
    Solution * s = new Solution();
    cout << s->climbStairs(3);
}

自底向上制表法

#include <iostream>

using namespace std;

class Solution {
public:
    int climbStairs(int n) {
        int dp[n + 1];
        dp[0] = 1; dp[1] = 1;
        for(int i = 2; i <= n; i++) {
                dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];
    }
};

int main(){
    Solution * s = new Solution();
    cout << s->climbStairs(3);
}

时间复杂度: O(n) O ( n )
空间复杂度: O(n) O ( n )

Longest Path In Matrix

题目

https://leetcode.com/problems/longest-increasing-path-in-a-matrix/description/
题目与geeksforgeeks上类似,但限定条件更少,做法略有差别。

分析

本题与lis类似,只不过是在矩阵中,加了限制条件,当前状态仅与它的上下左右元素状态有关。
设lipm(i, j)为以i, j为末尾的lis长
状态转移方程为

lipm(i,j)=1+max(lipm(i+1,j),lipm(i1,j),lipm(i,j+1),lipm(i,j1)if i+1<nseq[i][j]>seq[i+1][j]if i>0seq[i][j]>seq[i1][j]if j+1<mseq[i][j]>seq[i][j+1]if j>0seq[i][j]>seq[i][j1])(2) (2) l i p m ( i , j ) = 1 + m a x ( { l i p m ( i + 1 , j ) , if  i + 1 < n ∧ s e q [ i ] [ j ] > s e q [ i + 1 ] [ j ] l i p m ( i − 1 , j ) , if  i > 0 ∧ s e q [ i ] [ j ] > s e q [ i − 1 ] [ j ] l i p m ( i , j + 1 ) , if  j + 1 < m ∧ s e q [ i ] [ j ] > s e q [ i ] [ j + 1 ] l i p m ( i , j − 1 ) if  j > 0 ∧ s e q [ i ] [ j ] > s e q [ i ] [ j − 1 ] )

基本情况为1,即为只包含本身的lis长。

求矩阵的lis长,则需要考虑所有元素作为结尾的lis长,选择最长的lis。即


lipmG()=max0i<n,0j<m(lipm(i,j)) l i p m G ( ) = max 0 ≤ i < n , 0 ≤ j < m ( l i p m ( i , j ) )

编程

自顶向下记忆法

#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
class Solution {
public:
        int longestIncreasingPath(vector<vector<int> > & matrix) {
        if(matrix.empty())
            return 0;
        int row = matrix.size();
        int col = matrix[0].size();
        int dp[row][col];
        int globalMax = 1;
        memset(dp, -1, sizeof(int) * row * col);
        for(int i = 0; i < row; i++) {
            for(int j = 0; j < col; j++) {
                lipm(matrix, row, col, i, j, &dp[0][0], &globalMax);
            }
        }
        return globalMax;
    }
    int lipm(vector<vector<int> > & matrix, int row, int col, int i, int j, int * dp, int * globalMax) {
        if(*(dp+i*col+j) > 0)
            return *(dp+i*col+j);

        *(dp+i*col+j) = 1;
        int tmp;
        if(i < row-1 && matrix[i][j] > matrix[i+1][j]) {
            tmp = 1 + lipm(matrix, row, col, i+1, j, dp, globalMax);
            *(dp+i*col+j) = *(dp+i*col+j) < tmp ? tmp : *(dp+i*col+j);
        }
        if(i > 0  && matrix[i][j] > matrix[i-1][j]) {
            tmp = 1 + lipm(matrix, row, col, i-1, j, dp, globalMax);
            *(dp+i*col+j) = *(dp+i*col+j) < tmp ? tmp : *(dp+i*col+j);
        }
        if(j < col-1 && matrix[i][j] > matrix[i][j+1]) {
            tmp = 1 + lipm(matrix, row, col, i, j+1, dp, globalMax);
            *(dp+i*col+j) =  *(dp+i*col+j) < tmp ? tmp : *(dp+i*col+j);
        }
        if(j > 0 && matrix[i][j] > matrix[i][j-1]) {
            tmp = 1 + lipm(matrix, row, col, i, j-1, dp, globalMax);
            *(dp+i*col+j) =  *(dp+i*col+j) < tmp ? tmp : *(dp+i*col+j);
        }
        if(*(dp+i*col+j) > *globalMax)
            *globalMax = *(dp+i*col+j);
        return *(dp+i*col+j);
    }
};

int main(){
    int a[3][3] = {9,9,4,6,6,8,2,1,1};
    vector<vector<int> > arr;
    arr.resize(3);
    for(int i = 0; i < 3; i++)
        arr[i].resize(3);
    for(int i = 0; i < 3; i++)
        for(int j = 0; j < 3; j++)
            arr[i][j] = a[i][j];

    Solution * s = new Solution();
    cout << s->longestIncreasingPath(arr);
}

自底向上制表法

// undo.
// 初步设想,用grap往四周延伸,扩散。

时间复杂度: O(n2m2) O ( n 2 ∗ m 2 )
空间复杂度: O(nm) O ( n ∗ m )
(如有问题,欢迎指出)

Subset Sum Problem

题目

https://www.geeksforgeeks.org/dynamic-programming-subset-sum-problem/
没找到OJ上有这一题,如果看见的话,欢迎评论补充。

分析

判断序列是否存在子集,使子集和等于指定值。那么可以考虑将最后一个值拿走,当前状态可能会跟哪些子问题相关?

  1. 可能仍然有子集和等于指定值,即拿走的元素并非子集中元素,记为ssp(sum, i-1),问题规模缩减一,判断拿走最后一个元素,子集和是否可能等于指定sum

  2. 还有一种可能,拿走的元素是子集中的元素,拿走之后,我们要判断问题是否能达到sum-arr[i]的值,如果可以,则存在子集和等于指定值。

判断当前序列是否存在子集和等于指定值,则若有一种子问题满足条件,则说明当前序列存在子集和等于指定值,因而采用或操作取子问题的解。

设ssp(sum, i)为布尔值,表示序列的[0, i]子序列中是否包含和为sum的子集。
则状态转移式为

ssp(sum,i)=truefalsessp(sum,i1)ssp(sum,i1)ssp(sumarr[i],i1)if sum=arr[i]if i=0,sumarr[i]if sum<arr[i]others(3) (3) s s p ( s u m , i ) = { t r u e if  s u m = a r r [ i ] f a l s e if  i = 0 , s u m ≠ a r r [ i ] s s p ( s u m , i − 1 ) if  s u m < a r r [ i ] s s p ( s u m , i − 1 ) ∨ s s p ( s u m − a r r [ i ] , i − 1 ) others

编程

因其状态因素有两个,所以采用二维数组存储子问题的解。
自上而下记忆法

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

class Solution{
public:
    int ssp(int sum, vector<int> & arr) {
        int dp[sum+1][arr.size()]; 
        memset(dp, -1, sizeof(int) * (sum+1) * arr.size());
        return _ssp(sum, arr, arr.size()-1, &dp[0][0]);
    }

    //此处表示的是以i结尾
    int _ssp(int sum, vector<int> & arr, int i, int * dp) {
        if(*(dp+sum * arr.size() + i) != -1)
            return *(dp+sum * arr.size() + i); 
        if(sum == arr[i])
            return *(dp+sum * arr.size() + i) = 1; // true
        if(i == 0 && sum != arr[i])
            return *(dp+sum * arr.size() + i) = 0; // false
        if(sum < arr[i])
            return *(dp+sum * arr.size() + i) = _ssp(sum, arr, i-1, dp);
        return *(dp+sum * arr.size() + i) = _ssp(sum-arr[i], arr, i-1, dp)|_ssp(sum, arr, i-1, dp);
    }

};

int main() {
    int set[] = {3,34,4,12,5,2};
    int sum = 9;
    vector<int> a(set, set + sizeof(set)/sizeof(int));
    Solution * s = new Solution();
    cout << s->ssp(sum, a);
}

自底向上制表法

#include <iostream>
#include <vector>
using namespace std;
//don't have test cases
class Solution{
public:
    // here means ending with i.
    bool ssp(int sum, vector<int> & arr) {
        bool dp[sum+1][arr.size()];
        for(int i = 0; i <= sum; i++) {
            for(int j = 0; j < arr.size(); j++) {
                if(i==arr[j])
                    dp[i][j] = true;
                else if(j==0 && i != arr[j])
                    dp[i][j] = false;
                else if(i < arr[j])
                    dp[i][j] = dp[i][j-1];
                else
                    dp[i][j] = dp[i-arr[j]][j-1] || dp[i][j-1];
            }
        }
        return dp[sum][arr.size()-1];
    }

};

int main() {
    int set[] = {3,34,4,12,5,2};
    int sum = 9;
    vector<int> a(set, set + sizeof(set)/sizeof(int));
    Solution * s = new Solution();
    cout << s->ssp(sum, a);
}

时间复杂度: O(sumN) O ( s u m ∗ N )
空间复杂度: O(sumN) O ( s u m ∗ N )

0-1 Knapsack Problem

题目

http://acm.hdu.edu.cn/showproblem.php?pid=2602

分析

kp(i, v)为对前i个骨头进行选择后背包已用空间为v时的价值。

kp(i,v)=0kp(i1,v)max(kp(i1,vvol[i1])+value[i1],kp(i1,v))if i=0if v<vol[i1]others(4) (4) k p ( i , v ) = { 0 if  i = 0 k p ( i − 1 , v ) if  v < v o l [ i − 1 ] m a x ( k p ( i − 1 , v − v o l [ i − 1 ] ) + v a l u e [ i − 1 ] , k p ( i − 1 , v ) ) others

编程

自顶向下记忆法:

#define LOCAL
#include <stdio.h>
#include <string.h>
class Solution{
public:
    int knapsack(int size, int bvol[], int bval[], int volumn) {
        int value[1001][1001];
        memset(value, -1, sizeof(int) * 1001 * 1001);
        return _kp(size, bvol, bval, volumn, &value[0][0]);
    }

    int _kp(int i, int bvol[], int bval[], int j, int *value) {
        if(*(value+i*1001+j) >= 0)
            return *(value+i*1001+j);
        if(i == 0) { // i== 0 || j == 0 wrong! don't know why?
            return *(value+i*1001+j) = 0;
        }
        if(j < bvol[i-1])
            return *(value+i*1001+j) = _kp(i-1, bvol, bval, j, value);
        return *(value+i*1001+j) = max(_kp(i-1, bvol, bval, j - bvol[i-1], value) + bval[i-1], _kp(i-1, bvol, bval, j, value));
    }

    int max(int a, int b) {
        return a > b ? a : b;
    }
};

int main() {
    #ifdef LOCAL
    freopen("data.in", "r", stdin);
    freopen("data.out", "w", stdout);
    #endif

    int T, N, V;
    int bvol[1000], bval[1000];
    Solution * s = new Solution();
    scanf("%d", &T);
    for(int i = 0; i < T; i++) {
        scanf("%d %d", &N, &V);
        for(int j = 0; j < N; j++) {
            scanf("%d", &bval[j]);
        }
        for(int j = 0; j < N; j++) {
            scanf("%d", &bvol[j]);
        }
        printf("%d\n", s->knapsack(N, bvol, bval, V));
    }
}

注释处出错尚未弄清原因。
自底向上制表法

//#define LOCAL
#include <stdio.h>
#include <string.h>
class Solution{
public:
    int knapsack(int size, int bvol[], int bval[], int volumn) {
        int value[1001][1001];
        memset(value, 0, sizeof(value));
        for(int i = 1; i <= size; i++) {
            for(int j = 0; j <= volumn; j++) {
                if(j < bvol[i-1]) {
                    value[i][j] = value[i-1][j];
                } else {
                    value[i][j] = max(value[i-1][j-bvol[i-1]]+bval[i-1], value[i-1][j]);
                }
            }
        }
        return value[size][volumn];
    }

    int max(int a, int b) {
        return a > b ? a : b;
    }
};

int main() {
    #ifdef LOCAL
    freopen("data.in", "r", stdin);
    freopen("data.out", "w", stdout);
    #endif

    int T, N, V;
    int bvol[1000], bval[1000];
    Solution * s = new Solution();
    scanf("%d", &T);
    for(int i = 0; i < T; i++) {
        scanf("%d %d", &N, &V);
        for(int j = 0; j < N; j++) {
            scanf("%d", &bval[j]);
        }
        for(int j = 0; j < N; j++) {
            scanf("%d", &bvol[j]);
        }
        printf("%d\n", s->knapsack(N, bvol, bval, V));
    }
}

时间复杂度: O(sizevolumn) O ( s i z e ∗ v o l u m n )
空间复杂度: O(sizevolumn) O ( s i z e ∗ v o l u m n )
因为整体状态只依赖前一轮状态,可用滚动数组代替。此时,空间复杂度为 O(volumn) O ( v o l u m n )

系列文:
动态规划入门1
动态规划入门2
动态规划入门3

点赞