【动态规划】MATLAB和Python实现-Part05

往期系列:
【动态规划】MATLAB和Python实现-Part01

【动态规划】MATLAB和Python实现-Part02

【动态规划】MATLAB和Python实现-Part03

【动态规划】MATLAB和Python实现-Part04


零、前言

前面几部分我们通过实例体验了动态规划,本次我们进行动态规划实战,尝试解决问题。

一、爬楼梯

1.1 题目描述

假设你正在爬楼梯,需要 n n n 阶你才能到达楼顶,每次你可以爬 1 或 2 个台阶,你有多少种不同的方法可以爬到楼顶呢?
示例输入:
2
示例输出:
2
解释:
有两种方法爬到楼顶:1+12

1.2 题目分析

定义原问题和子问题:
原问题:
n n n 阶楼梯的方案数。
子问题:
k ( k ≤ n ) k(k\leq n) k(kn) 阶楼梯的方案数。

定义状态:
f ( k ) f(k) f(k) 表示走 k k k 阶楼梯的方案数。
k k k 就是此时的状态,当 k = n k=n k=n 时, f ( n ) f(n) f(n) 就是原问题的解。

寻找状态转移方程:
对该问题,容易知道:
k = 1 k=1 k=1 时, f ( k ) = 1 f(k)=1 f(k)=1
k = 2 k=2 k=2 时, f ( k ) = 2 f(k)=2 f(k)=2
而对于 k > 2 k>2 k>2 时,容易知道有:
f ( k ) = ( k − 2 ) + f ( k − 1 ) f(k)=(k-2)+f(k-1) f(k)=(k2)+f(k1)
即可以从状态 k − 2 k-2 k2 一步到达,也可以从 k − 1 k-1 k1 一步到达。

综上所述,最终的状态转移方程为:
f ( k ) = { 1 , k = 1 2 , k = 2 f ( k − 2 ) + f ( k − 1 ) , k > 2 f(k)=\begin{cases} 1, & k=1 \\ 2, & k=2 \\ f(k-2)+f(k-1), & k>2 \end{cases} f(k)= 1,2,f(k2)+f(k1),k=1k=2k>2

1.3 题目求解

MATLAB
函数实现:

function f = climb_stairs(n)
    if n == 1
        f = 1;
    elseif n == 2
        f = 2;
    else
        dp = ones(1,n);
        dp(2) = 2;
        for i = 3:n
            dp(i) = dp(i-1)+dp(i-2);
        end
        f = dp(n);
    end
end

函数调用:

clear;clc;
n = 10;        %10 阶楼梯
tic;
f = climb_stairs(n)
toc;

结果:
在这里插入图片描述

Python
函数实现:

def climb_stairs(n):
    if n==1:
        f = 1
    elif n==2:
        f = 2
    else:
        dp = [1 for i in range(n)]
        dp[1] = 2
        for i in range(2, n):
            dp[i] = dp[i-2]+dp[i-1]
        f = dp[-1]
    return f

函数调用:

import time
start = time.time()
n = 10
f = climb_stairs(n)
end = time.time()
print('爬楼梯动态规划求解结果为:')
print(f)
print('求解时间为(s):')
print('%.8f' % (start-end))

结果:
在这里插入图片描述

二、不同路径

2.1 题目描述

有一个大小为 m × n m\times n m×n 的网格,一个机器人每次只能向右或向下走一步。现在这个机器人要从左上角走到右下角。计算机器人有多少种走法。
示例输入:
m=3, n=7
示例输出:
28
解释:
机器人共有28种走法。

2.2 题目分析

定义原问题和子问题:
原问题:
若令 f ( i , j ) f(i,j) f(i,j) 表示从棋盘左上角走到第 i i i 行第 j j j 列的格子的方法数,则当 i = m , j = n i=m, j=n i=m,j=n 时为原问题的解。
子问题:
i < m i<m i<m j < n j<n j<n 时为子问题的解。

定义状态:
其中 i , j i,j i,j 的取值就表示不同的状态。

寻找状态转移方程:
对于二维问题,我们要找边界条件:
i = 1 i=1 i=1 j = 1 j=1 j=1 时,机器人只有沿直线行走的一条线路,即: f ( i , j ) = 1 f(i,j)=1 f(i,j)=1
i > 1 i>1 i>1 j > 1 j>1 j>1 时,机器人可以从上边来,或者左边来,即: f ( i , j ) = f ( i , j − 1 ) + f ( i − 1 , j ) f(i,j)=f(i,j-1)+f(i-1,j) f(i,j)=f(i,j1)+f(i1,j)

综上所述,最终的状态转移方程为:
f ( i , j ) = { 1 , i = 1 或 j = 1 f ( i , j − 1 ) + f ( i − 1 , j ) , i > 1 且 j > 1 f(i,j)=\begin{cases} 1, & i=1或j=1 \\ f(i,j-1)+f(i-1,j), & i>1 且 j>1 \end{cases} f(i,j)={1,f(i,j1)+f(i1,j),i=1j=1i>1j>1

2.3 题目求解

MATLAB
函数实现:

function f = diff_path(m,n)
    dp = ones(m,n);
    for i = 2:m
        for j = 2:n
            dp(i,j) = dp(i,j-1)+dp(i-1,j);
        end
    end
    f = dp(m,n);
end

函数调用:

clear;clc;
m = 6;
n = 7;
tic;
f = diff_path(m,n)
toc;

结果:
在这里插入图片描述

Python
函数实现:

def diff_path(m, n):
    dp = [[1 for j in range(n)] for i in range(m)]
    for i in range(1, m):
        for j in range(1, n):
            dp[i][j] = dp[i][j-1]+dp[i-1][j]
    f = dp[-1][-1]
    return f

函数调用:

import time
start = time.time()
m = 6
n = 7
f = diff_path(m, n)
end = time.time()
print('不同路径动态规划求解结果为:')
print(f)
print('求解时间为(s):')
print('%.8f' % (start-end))

结果:
在这里插入图片描述

三、不同路径Ⅱ

3.1 题目描述

有一个大小为 m × n m\times n m×n 的网格,一个机器人每次只能向右或向下走一步。另外,这个网格某些格点存在障碍物,机器人不能通行,现在这个机器人要从左上角走到右下角。计算机器人有多少种走法。
示例输入:

[0 0 0;
 0 1 0;
 0 0 0]

其中空位置为0,障碍物为1
示例输出:
2
解释:
机器人共有 2 种走法。

3.2 题目分析

定义原问题和子问题:
原问题:
若令 f ( i , j ) f(i,j) f(i,j) 表示从棋盘左上角走到第 i i i 行第 j j j 列的格子的方法数,则当 i = m , j = n i=m, j=n i=m,j=n 时为原问题的解。
子问题:
i < m i<m i<m j < n j<n j<n 时为子问题的解。

定义状态:
其中 i , j i,j i,j 的取值就表示不同的状态。

寻找状态转移方程:
我们可以将其分为两种情况讨论:

  • 当第 i i i 行第 j j j 列的格子上有障碍物,则有 f ( i , j ) = 0 f(i,j)=0 f(i,j)=0
  • 当第 i i i 行第 j j j 列的格子上没有障碍物,则有:
    • i = j = 1 i=j=1 i=j=1 时, f ( i , j ) = 1 f(i,j)=1 f(i,j)=1
    • i = 1 i=1 i=1 j > 1 j>1 j>1 时,因为只能从左边到达,则在遇到障碍物前,有 f ( i , j ) = 1 f(i,j)=1 f(i,j)=1,在遇到障碍物后 f ( i , j ) = 0 f(i,j)=0 f(i,j)=0
    • i > 1 i>1 i>1 j = 1 j=1 j=1 时,因此只能从上边到达,则在遇到障碍物前,有 f ( i , j ) = 1 f(i,j)=1 f(i,j)=1,在遇到障碍物后 f ( i , j ) = 0 f(i,j)=0 f(i,j)=0
    • i , j > 1 i,j>1 i,j>1 时,则有 f ( i , j ) = f ( i , j − 1 ) + f ( i − 1 , j ) f(i,j)=f(i,j-1)+f(i-1,j) f(i,j)=f(i,j1)+f(i1,j)

3.3 题目求解

MATLAB
函数实现:

function f = diff_path02(Mt)
    [m,n] = size(Mt);
    dp = ones(m,n);
    % 第一行
    for j = 1:n
        if Mt(1,j) == 1
            dp(1, j:end) = 0;
            break
        end
    end
    % 第一列
    for i = 1:m
        if Mt(i,1) == 1
            dp(i:end,1) = 0;
            break
        end
    end
    % 剩余部分
    for i = 2:m
        for j = 2:n
            if Mt(i,j) == 1
                dp(i,j) = 0;
            else
                dp(i,j) = dp(i,j-1)+dp(i-1,j);
            end
        end
    end
    f = dp(m,n);
end

函数调用:

clear;clc;
Mt = [0 0 0 0 0;
      0 1 0 0 1;
      0 0 0 1 0;
      0 1 0 0 0;
      0 0 0 0 0];
tic;
f = diff_path02(Mt)
toc;

结果:
在这里插入图片描述

Python
函数实现:

def diff_path02(Mt):
    m = len(Mt)
    n = len(Mt[0])
    dp = [[1 for j in range(n)] for i in range(m)]
    for j in range(n):
        if Mt[0][j] == 1:
            for k in range(j,n):
                dp[0][k] = 0
    for i in range(m):
        if Mt[i][0] == 1:
            for k in range(i, m):
                dp[k][0] = 0
    for i in range(1, m):
        for j in range(1, n):
            if Mt[i][j] == 1:
                dp[i][j] = 0
            else:
                dp[i][j] = dp[i][j-1]+dp[i-1][j]
    f = dp[-1][-1]
    return f

函数调用:

import time
start = time.time()
Mt = [[0, 0, 0, 0, 0], [0, 1, 0, 0, 1], [0, 0, 0, 1, 0], [0, 1, 0, 0, 0], [0, 0, 0, 0, 0]]
f = diff_path02(Mt)
end = time.time()
print('不同路径动态规划求解结果为:')
print(f)
print('求解时间为(s):')
print('%.8f' % (start-end))

结果:
在这里插入图片描述

四、掷骰子的N种方法

4.1 题目描述

m m m 个完全相同的骰子,每个骰子上都有 f f f 个面,分别标号为 1 , 2 , . . . , f 1,2,...,f 1,2,...,f,我们约定:掷骰子的总点数为各骰子面朝上的数字的总和,且骰子的每个面出现的概率相同。如果需要掷出的总点数为 S S S,请你计算出有多少种不同的组合情况。
示例输入:
m=1, f=6, S=3
示例输出:
1
解释:
只有一个骰子,要得到点数 3,只有掷出 3 满足情况。

4.2 题目分析

定义原问题和子问题:
原问题:
m m m 个骰子,掷出的总点数为 S S S,计算出有多少种不同的组合情况。
子问题:
i i i 个骰子,掷出的总点数为 j j j,计算出有多少种不同的组合情况。

定义状态:
f ( i , j ) f(i,j) f(i,j) 为用 i i i 个骰子,掷出的总点数为 j j j 的组合数,则 i , j i,j i,j 就是此时的状态。

寻找状态转移方程:
先考虑边界条件:
i = 1 i=1 i=1 时,说明此时只有一个骰子,则前 f f f 个元素为 1,其余为 0;
j = 1 j=1 j=1 时,则第一个元素为 1 1 1,其余为 0 0 0
容易得到状态转移方程如下:
f ( i , j ) = ∑ k = 1 f f ( i − 1 , j − k ) f(i,j)=\sum_{k=1}^ff(i-1,j-k) f(i,j)=k=1ff(i1,jk)
即前 i i i 个骰子掷出总点数等于 j j j 有以下可能:
i − 1 i-1 i1 个骰子掷出总点数等于 j − 1 j-1 j1,第 i i i 个骰子掷出 1 1 1
i − 1 i-1 i1 个骰子掷出总点数等于 j − 2 j-2 j2,第 i i i 个骰子掷出 2 2 2

i − 1 i-1 i1 个骰子掷出总点数等于 j − f j-f jf,第 i i i 个骰子掷出 f f f

4.3 题目求解

MATLAB
函数实现:

function F = throw_dice(m,f,S)
    dp = zeros(m,S);
    dp(1,1) = 1;
    for j = 1:S
        if j<= f
            dp(1,j) = 1;
        end
    end
    for i = 2:m
        for j = 2:S
            for k = 1:f
                if j > k
                    dp(i,j) = dp(i,j)+dp(i-1,j-k);
                else
                    break
                end
            end
        end
    end
    F = dp(m,S);
end

函数调用:

clear;clc;
m = 5;
f = 8;
S = 27;
tic;
F = throw_dice(m,f,S)
toc;

结果:
在这里插入图片描述

Python
函数实现:

def throw_dice(m, f, S):
    dp = [[0 for j in range(S)] for i in range(m)]
    dp[0][0] = 1
    for j in range(S):
        if j+1 <= f:
            dp[0][j] = 1
    for i in range(1, m):
        for j in range(1, S):
            for k in range(1, f+1):
                if j+1 > k:
                    dp[i][j] += dp[i-1][j-k]
                else:
                    break
    F = dp[-1][-1]
    return F

函数调用:

import time
start = time.time()
m = 5
f = 8
S = 27
F = throw_dice(m, f, S)
end = time.time()
print('掷骰子的N种方法动态规划求解结果为:')
print(F)
print('求解时间为(s):')
print('%.8f' % (start-end))

结果:
在这里插入图片描述

五、编辑距离

5.1 题目描述

给定两个小写的英文单词 w o r d 1 word1 word1 w o r d 2 word2 word2,请你计算出将 w o r d 1 word1 word1 转换为 w o r d 2 word2 word2 所使用的最少操作数(定义为 w o r d 1 word1 word1 w o r d 2 word2 word2 的编辑距离)。可以对任意单词进行如下三种操作:

  • 插入一个字符
  • 删除一个字符
  • 替换一个字符

示例输入:
word1='horse', word2='ros'
示例输出:
3
解释:

  1. 'h' 替换为 'r'horse -> rorse
  2. 删除 'r'rorse -> rose
  3. 删除 erose->ros

5.2 题目分析

定义原问题和子问题:
原问题:
记 4word1$ 长度为 m m m w o r d 2 word2 word2 长度为 n n n,记 f ( i , j ) f(i,j) f(i,j) 表示 w o r d 1 word1 word1 的前 i i i 个字母转换为 w o r d 2 word2 word2 的前 j j j 个字母所使用的最少操作数,那么 f ( m , n ) f(m,n) f(m,n) 就是我们要计算的 w o r d 1 word1 word1 w o r d 2 word2 word2 的原问题的解。
子问题:
那么 f ( i , j ) , i < m , j < n f(i,j),i<m,j<n f(i,j),i<m,j<n 就是子问题的解。

定义状态:
f ( i , j ) , i < m , j < n f(i,j),i<m,j<n f(i,j),i<m,j<n 中的 i , j i,j i,j 就表示不同的状态。

寻找状态转移方程:
先考虑边界条件:

  • i = 1 i=1 i=1 时, f ( 1 , j ) f(1,j) f(1,j) 表示 w o r d 1 word1 word1 的第 1 个字母转换成 w o r d 2 word2 word2 的前 j j j 个字母所用的最少操作数。则有:
    • w o r d 1 word1 word1 的第 1 个字母不在 w o r d 2 word2 word2 中,那么需要 j j j 次,即 f ( 1 , j ) = j f(1,j)=j f(1,j)=j
    • w o r d 1 word1 word1 的第 1 个字母在 w o r d 2 word2 word2 中,找到了其位置:如果这个位置是首位,则有 f ( 1 , j ) = j − 1 f(1,j)=j-1 f(1,j)=j1;否则,该位置之前有: f ( 1 , j ) = j f(1,j)=j f(1,j)=j,该位置之后有: f ( 1 , j ) = j − 1 f(1,j)=j-1 f(1,j)=j1
  • j = 1 j=1 j=1 时, f ( i , 1 ) f(i,1) f(i,1) 表示 w o r d 1 word1 word1 的前 i i i 个字母转换成 w o r d 2 word2 word2 的第 1 个字母所用的最少操作数。则有(与 i = 1 i=1 i=1 时的情况相似):
    • w o r d 2 word2 word2 的第 1 个字母不在 w o r d 1 word1 word1 中,那么需要 i i i 次,即 f ( i , 1 ) = i f(i,1)=i f(i,1)=i
    • w o r d 2 word2 word2 的第 1 个字母在 w o r d 1 word1 word1 中,找到了其位置:如果这个位置是首位,则有 f ( i , 1 ) = i − 1 f(i,1)=i-1 f(i,1)=i1;否则,该位置之前有: f ( i , 1 ) = i f(i,1)=i f(i,1)=i,该位置之后有: f ( i , 1 ) = i − 1 f(i,1)=i-1 f(i,1)=i1

接下来,考虑一般情况,即当 i , j > 1 i,j>1 i,j>1 时,有三种情况:

  • 当有 w o r d 1 word1 word1 的前 i − 1 i-1 i1 个变为 w o r d 2 word2 word2 的前 j − 1 j-1 j1 个,若有 w o r d 1 ( i ) = w o r d 2 ( j ) word1(i)=word2(j) word1(i)=word2(j) 则有: f ( i , j ) = f ( i − 1 , j − 1 ) + 0 f(i,j)=f(i-1,j-1)+0 f(i,j)=f(i1,j1)+0,若 w o r d 1 ( i ) ≠ w o r d 2 ( j ) word1(i)\neq word2(j) word1(i)=word2(j) 则有: f ( i , j ) = f ( i − 1 , j − 1 ) + 1 f(i,j)=f(i-1,j-1)+1 f(i,j)=f(i1,j1)+1
  • 当有 w o r d 1 word1 word1 的前 i − 1 i-1 i1 个变为 w o r d 2 word2 word2 的前 j j j 个,那么只需要进行一步删除操作,即 f ( i , j ) = f ( i − 1 , j ) + 1 f(i,j)=f(i-1,j)+1 f(i,j)=f(i1,j)+1
  • 当有 w o r d 1 word1 word1 的前 i i i 个变为 w o r d 2 word2 word2 的前 j − 1 j-1 j1 个,那么只需要进行一步插入操作,即 f ( i , j ) = f ( i , j − 1 ) + 1 f(i,j)=f(i,j-1)+1 f(i,j)=f(i,j1)+1.

综上所述:
{ t e m p 1 = f ( i − 1 , j − 1 ) + { 0 , w o r d 1 ( i ) = w o r d 2 ( j ) 1 , w o r d 1 ( i ) ≠ w o r d 2 ( j ) t e m p 2 = f ( i − 1 , j ) + 1 t e m p 3 = f ( i , j − 1 ) + 1 f ( i , j ) = m i n { t e m p 1 , t e m p 2 , t e m p 3 } \begin{cases} temp1 = f(i-1,j-1)+\begin{cases} 0, & word1(i)=word2(j) \\ 1, & word1(i)\neq word2(j) \\ \end{cases} \\ temp2 = f(i-1,j)+1 \\ temp3 = f(i,j-1)+1 \\ f(i,j)=min\{temp1,temp2,temp3\} \end{cases} temp1=f(i1,j1)+{0,1,word1(i)=word2(j)word1(i)=word2(j)temp2=f(i1,j)+1temp3=f(i,j1)+1f(i,j)=min{temp1,temp2,temp3}

5.3 题目求解

MATLAB
函数实现:

function f = edi_dis(word1,word2)
    m = length(word1);
    n = length(word2);
    dp = ones(m,n);
    ind = strfind(word2,word1(1));
    for j = 1:n
        if isempty(ind)
            dp(1,j) = j;
        elseif ind(1) == 1
            dp(1,j) = j-1;
        else
            if j<ind(1)
                dp(1,j) = j;
            else
                dp(1,j) = j-1;
            end
        end
    end
    ind = strfind(word1, word2(1));
    for i = 1:m
        if isempty(ind)
            dp(i,1) = i;
        elseif ind(1) == 1
            dp(i,1) = i-1;
        else
            if i < ind(1)
                dp(i,1) = i;
            else
                dp(i,1) = i-1;
            end
        end
    end
    for i = 2:m
        for j = 2:n
            temp1 = dp(i-1,j-1)+(word1(i)~=word2(j))*1;
            temp2 = dp(i-1,j)+1;
            temp3 = dp(i,j-1)+1;
            dp(i,j) = min([temp1, temp2, temp3]);
        end
    end
    f = dp(m,n);
end

函数调用:

%% edi_dis
clear;clc;
word1 = 'horse';
word2 = 'ros';
tic;
f = edi_dis(word1,word2)
toc;

结果:
在这里插入图片描述

Python
函数实现:

def edi_dis(word1, word2):
    m = len(word1)
    n = len(word2)
    dp = [[1 for j in range(n)] for i in range(m)]
    ind = word2.find(word1[0])
    for j in range(n):
        if ind == -1:
            dp[0][j] = j+1
        elif ind == 0:
            dp[0][j] = j
        else:
            if j < ind:
                dp[0][j] = j+1
            else:
                dp[0][j] = j
    ind = word1.find(word2[0])
    for i in range(m):
        if ind == -1:
            dp[i][0] = i+1
        elif ind == 0:
            dp[i][0] = i
        else:
            if i < ind:
                dp[i][0] = i+1
            else:
                dp[i][0] = i
    for i in range(1, m):
        for j in range(1, n):
            temp1 = dp[i-1][j-1]+(1 if word1[i]!=word2[j] else 0)*1
            temp2 = dp[i-1][j]+1
            temp3 = dp[i][j-1]+1
            dp[i][j] = min(temp1, temp2, temp3)
    f =dp[-1][-1]
    return f

函数调用:

import time
start = time.time()
word1 = 'horse'
word2 = 'ros'
f = edi_dis(word1, word2)
end = time.time()
print('动态规划求解结果为:')
print(f)
print('求解时间为(s):')
print('%.8f' % (start-end))

结果:
在这里插入图片描述

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

四口鲸鱼爱吃盐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值