進階算法04

無後效性問題:

  • 不管通過什麼方式到達某個狀態,其返回值都一樣。

1. 換錢的方法數

在這裏插入圖片描述

暴力遞歸

  • 以arr = [5, 10, 25, 1],aim = 15爲例:
    在這裏插入圖片描述
    節點中的內容表示:
    • 從下標爲[i~j]的數組中取任意張,可以換到aim的方法數
class Solution{

public:
    int coins(int *arr, int len, int aim)
    {
        if (!arr || len == 0 || aim < 0)
            return 0;
            
        return help(arr, len, 0, aim);
    }

private:
    //從下標爲[index~len-1]的數組中取任意張,可以換到aim的方法數
    int help(int *arr, int len, int index, int aim)
    {
        int ret = 0;
        if (index == len)
            ret = (aim == 0) ? 1 : 0;
        else{
            for (int i = 0; arr[index] * i <= aim; i++)
            {
                ret += help(arr, len, index + 1, aim - arr[index] * i);
            }
        }

        return ret;
    }
};

可以發現 暴力遞歸 有許多重複性計算,可以使用記憶化搜索

class Solution{

public:
    int coins(int *arr, int len, int aim)
    {
        if (!arr || len == 0 || aim < 0)
            return 0;

        memo = vector<vector<int>>(len + 1, vector<int>(aim + 1, -1));

        return help(arr, len, 0, aim);
    }

private:
    vector<vector<int>> memo;       //memo[i][j]:從下標爲[i...len-1]處選取任意張,可以換到j的方法數

    //從下標爲[index~len-1]的數組中取任意張,可以換到aim的方法數
    int help(int *arr, int len, int index, int aim)
    {
        int ret = 0;
        if (index == len)
            ret = (aim == 0) ? 1 : 0;
        else{
            for (int i = 0; arr[index] * i <= aim; i++)
            {
                if (memo[index + 1][aim - arr[index] * i] != -1)
                    ret += memo[index + 1][aim - arr[index] * i];
                else
                    ret += help(arr, len, index + 1, aim - arr[index] * i);
            }
        }

        memo[index][aim] = ret;

        return ret;
    }
};

動態規劃

從暴力遞歸的遞歸函數可以看出:

  • 要計算當前(index,aim)的返回值,需要通過(index+1, aim-xxx)來獲得

以arr=[5,3,2],aim = 5爲例
在這裏插入圖片描述
最終要求得綠色位置的值。

1.首先填寫最後一排
在這裏插入圖片描述

2.依次往上填
比如想計算 index=2,aim=4 的值:
memo[2][4] = memo[3][4] + memo[3][2] + memo[3][0]
(前一個是arr[2]取0張,中間是arr[2]取1張,後一個是arr[2]取2張)

  • 最終結果:
    在這裏插入圖片描述
class Solution{

public:
    int coins(int *arr, int len, int aim)
    {
        if (!arr || len == 0 || aim < 0)
            return 0;

        memo = vector<vector<int>>(len + 1, vector<int>(aim + 1, 0));

        memo[len][0] = 1;

        for (int i = len - 1; i >= 0; i--)
        {
            for (int j = 0; j <= aim; j++)
            {
                for (int k = 0; j - k*arr[i] >= 0; k++)
                    memo[i][j] += memo[i + 1][j - k*arr[i]];
            }
        }

        return memo[0][aim];
    }

private:
    vector<vector<int>> memo;       //memo[i][j]:從下標爲[i...len-1]處選取任意張,可以換到j的方法數

};

假設現在要計算 memo[i][j] 位置上的值,按照上述方法,要通過橙色方塊求得。
在這裏插入圖片描述

  • 優化:可以通過下圖橙色方塊求得
    在這裏插入圖片描述
class Solution{

public:
    int coins(int *arr, int len, int aim)
    {
        if (!arr || len == 0 || aim < 0)
            return 0;

        memo = vector<vector<int>>(len + 1, vector<int>(aim + 1, 0));

        memo[len][0] = 1;

        for (int i = len - 1; i >= 0; i--)
        {
            for (int j = 0; j <= aim; j++)
            {
                /*for (int k = 0; j - k*arr[i] >= 0; k++)
                    memo[i][j] += memo[i + 1][j - k*arr[i]];
                */
                int n = (j - arr[i] >= 0) ? memo[i][j - arr[i]] : 0;
                memo[i][j] = memo[i + 1][j] + n;
            }
        }

        return memo[0][aim];
    }

private:
    vector<vector<int>> memo;       //memo[i][j]:從下標爲[i...len-1]處選取任意張,可以換到j的方法數

};

2. 機器人走到指定位置的方法數

在一個長度爲N的路上,下標爲[1~N]。一個機器人初始在M位置,它可以走P步,如果在1位置則只能往右走,在N位置則只能往左走,請問機器人走P步後,停在K位置上的走法有多少種

暴力遞歸

int ways(int N, int M, int P, int K)
{
    if (N < 2 || M < 1 || M > N || P < 0 || K < 1 || K > N)
        return 0;

    if (P == 0)
        return (M == K) ? 1 : 0;

    int ret = 0;
    if (M == 1)
        ret = ways(N, M + 1, P - 1, K);
    else if (M == N)
        ret = ways(N, M - 1, P - 1, K);
    else
        ret = ways(N, M + 1, P - 1, K) + ways(N, M - 1, P - 1, K);

    return ret;
}

動態規劃

在這裏插入圖片描述 memo[P][M] = memo[P-1][M-1] + memo[P-1][M+1]

int ways(int N, int M, int P, int K)
{
    if (N < 2 || M < 1 || M > N || P < 0 || K < 1 || K > N)
        return 0;

    vector<vector<int>> memo = vector<vector<int>>(P + 1, vector<int>(N + 1, 0));       //memo[i][j]:走了i步,到達j位置的方法數

    memo[0][K] = 1;

    for (int i = 1; i <= P; i++)
    {
        for (int j = 1; j <= N; j++)
        {
            memo[i][j] += (j - 1 < 1) ? 0 : memo[i - 1][j - 1];        //從左邊一個位置往右走
            memo[i][j] += (j + 1 > N) ? 0 : memo[i - 1][j + 1];        //從右邊一個位置往左走
        }
    }

    return memo[P][K];
}

更多動態規劃例題

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章