分别用线性规划和动态规划求解打家劫舍问题(力扣198)

写在前面:

1. 本人是只挣扎在数模海洋的小可怜,最近同时学线性规划和动态规划,于是就有了这篇博客

2. 编程使用matlab

3. 动态规划解法参考 数学建模清风动态规划课程https://www.bilibili.com/video/BV1tp4y167c5

打家劫舍问题描述:

你是一个小偷,现在有一排相邻的房屋等着你去偷窃。这些房子装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。给定一个代表每个房屋存放金额的正整数数组,计算你不触动警报装置的情况下,一夜之内能够偷窃到的最高金额。(不考虑偷窃时间)

示例1:

输入:[1,2,3,1,3]

输出:7

解释:偷窃1号、3号、5号房屋,偷窃到的最高金额=1+3+3=7。

示例2:

输入:[2,7,2,3,8]

输出:15

解释:偷窃2号、5号房屋,偷窃到的最高金额=7+8=15。

解法一:线性规划

设有m间房屋,每间房屋存放的金额记为v_i,i=1,2,\cdots ,m,引入一套0-1变量y_i,i=1,2,\cdots ,my_i=1表示偷第i间房屋,y_i=0表示不偷第i间房屋。

则目标函数写为

max \sum_{i=1}^{m}y_iv_i

根据题目“如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警”,则小偷不能偷相邻两间房屋,即相邻两个0-1变量不能同时取1,故约束条件写为:

y_i+y_{i+1}\leqslant 1,i=1,2,\cdots ,m-1

综上,线性规划的数学模型为:

max \sum_{i=1}^{m}y_iv_i

s.t.\left\{ \begin{aligned} &y_i+y_{i+1} \leqslant 1,\, i=1,2,\cdots,m-1\\ \\ &y_i=0\ \ or\ \ 1,\, i=1,2,\cdots,m \end{aligned} \right.

 代码如下:

(如果不懂可以在命令行键入doc optimproblem查看帮助文档)

function [fval,ind]=djjs(v)
m=length(v);  % 共m间房屋
prob=optimproblem("ObjectiveSense","max");  % 创建一个求最大值的优化问题
y=optimvar('y',1,m,'Type',"integer","LowerBound",0,"UpperBound",1);  % 决策变量,规模1*m,0-1变量
prob.Objective=sum(y.*v);  % 目标函数
con=optimconstr(m-1);  % 创建空约数集合
for i=1:m-1
    con(i)=y(i)+y(i+1)<=1;
end  % 填写约束
prob.Constraints.con=con;  % 将约束集合纳入问题
[sol,f]=solve(prob);  % 求解
fval=f;
ind=sol.y;
end

运行时在命令行键入:

clear
v=[1,2,3,1,3];
[fval,ind]=djjs(v)

运行结果:

 解法二:动态规划

ps:动态规划我是跟着清风老师的视频学的,在这里我不讲这一算法的内容。下面的思考步骤(注意,我说的是思考步骤)是我根据清风的视频加上自己的学习体验总结的,看着比较啰嗦但是我自己这样思考的话有助于求解问题。

(1)定义原问题和子问题

原问题:从全部m间房屋偷窃得到的最高金额

子问题:从前x\, (x\leqslant m)间房屋偷窃得到的最高金额

(2)定义状态和最值函数

状态:前x间房屋

最值函数:从前x间房屋偷得的最高金额dp(x)

(3)描述dp数组

本问题中dp数组是一维数组,设数组下标为xx从1取到m,对应数组值dp(x)表示 从前x间房屋中偷得的最高金额,即各个子问题的解。数组最后一个值,即x=m时,dp(m)即原问题的解。

(4)状态转移方程

思考状态转移方程时,可以问自己这么几个问题:哪些状态可以转移到当前状态?从哪个状态转移会达到最优?前后状态最值函数间的关系式怎么构建?

易知,从状态x-1和状态x-2可以转移到当前状态x。如果从状态x-1转移,为不触发报警器则第x间房屋不能偷,故有dp(x)=dp(x-1);如果从状态x-2转移,为使偷得金额最大则需偷第x间房屋,故有dp(x)=dp(x-2)+v_x。但是我们并不清楚从哪个状态转移会使dp(x)达到最大,故状态转移方程写为:

dp(x)=max\left \{ dp(x-1),dp(x-2)+v_x \right \}\, ,x=3,4,\cdots ,m

(可能有人会有疑问,如果从状态x-1转移但是第x-1间房屋并没有偷,是不是可以有dp(x)=dp(x-1)+v_x?其实这种情况等同于从状态x-2转移:dp(x)=dp(x-1)+v_x=dp(x-2)+v_x,所以不应重复计算)

关于状态转移方程,也可以这样构造一个场景思考:

小偷到第x间房屋时,如果第x-1间房屋已经偷过, 为不触发报警器则第x间房屋不能偷,故有dp(x)=dp(x-1);如果第x-2间房屋没有偷过,为使偷得金额最大则需偷第x间房屋,故有dp(x)=dp(x-2)+v_x。故也可得出状态转移方程:

dp(x)=max\left \{ dp(x-1),dp(x-2)+v_x \right \}\, ,x=3,4,\cdots ,m

(5)边界条件

本题的边界条件有两个:

dp(1)=v_1\, ,\, dp(2)=max\left \{ v_1,v_2 \right \}

(6)编程求解dp数组

基于已得到的状态转移方程、边界条件,跑循环逐个求解dp数组的值。

代码如下:

function fval=djjs(v)
m=length(v);  % 共有m间房屋
if m==1  % 出口1:如果只有1间房,直接返回v(1)
    fval=v(1);
elseif m==2  % 出口2:如果只有2间房,直接返回v(1)和v(2)中的较大值
    fval=max(v(1),v(2));
else  % 出口3
    dp=v(1)*ones(1,m);  % 定义dp数组
    dp(2)=max(v(1),v(2));  % 边界条件
    for x=3:m  % 顺序、循环求解
        dp(x)=max(dp(x-1),dp(x-2)+v(x));  % 状态转移方程
    end
    fval=dp(m);
end
end

运行时在命令行键入以下代码即可:

clear
v=[1,2,3,1,3];
fval=djjs(v)

你可能感兴趣的:(动态规划,leetcode,算法,线性规划)