1.题目:打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你不触动警报装置的情况下,一夜之内能够偷窃到的最高金额。
输入:[1,2,3,1] 输出:4
输入:[2,7,9,3,1] 输出:12
2.算法:
1.递归
2.迭代的动态规划
3.空间复杂度 缩小的迭代动态规划
3.算法思想:(说不清! bibi 看视频)
算法思想都是差不多的,都是从最低为开始找 最优的解(或者说是最大能取到的数值)。
4.代码:
/*************************************************
作者:She001
时间:2022/9/7
题目:打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你不触动警报装置的情况下,一夜之内能够偷窃到的最高金额。
输入:[1,2,3,1] 输出:4
输入:[2,7,9,3,1] 输出:12
算法:
1.递归
2.迭代的动态规划
3.空间复杂度 缩小的迭代动态规划
***************************************************/
#include<bits/stdc++.h>
using namespace std;
//
//算法:
//1. 递归
int max(int a,int b)//两个数,返回最大的数
{
if(a>b)
{
return a;
}
else
{
return b;
}
}
//因为我们是从 第一间房子 开始 遍历, 所以我们 需要一直从 下标数组最大的地方一直往前面递归, 这样我们就可以从开始的时候 规划最大的金额!
int fangfa_1(int num[],int index)//num 金额数组, index 到达的房子
{
if(num==NULL || index<0)//从最大的下标开始,所以我们需要在到达下标 小于 0 的时候停止, 这时候没有房子可以偷窃, 所以返回 0
{
return 0;
}
if(index==0)//当只有一个房子的时候,我们直接进入,他的上一个 递归会决定他是否需要返回数值!
{
return num[0];
}
if(index>0)
{
return max(fangfa_1(num,index-1),fangfa_1(num,index-2)+num[index]);//代表着 在不选这个点,选着下一个点,, 和选了当前的节点的时候 加上 下一个的下一个节点 index -2 这个坐标节点,因为这个是不连续的!
// 他的每个节点 都会有一个最优的解,所以他在遍历 每种情况选出最大的数值,,并且每次遍历 都会利用之前的点,来判断当前的坐标点,是否需要选着!!!!
}
}
//算法:迭代动态规划:
int fangfa_2(int num[],const int n)//num 数组, 数组的元素数量 n
{
if(num==NULL || n==0)//s数组是空的, 元素的数量是 0 返回金额为 0
{
return 0;
}
if(n==1)//当只有一个房子的时候,我们直接进入,他的上一个 递归会决定他是否需要返回数值!
{
return num[0];
}
///建立DP 空间 来存储数据
int DP[n]={0};
//DP 空间初始化
DP[0]=num[0];
DP[1]=max(num[0],num[1]);
//方法和上面的差不多,都是从小的时候开始求取 最优解
for(int i=2;i<n;i++)
{
DP[i]=max(DP[i-1],DP[i-2]+num[i]);
}
return DP[n-1];
}
//算法:3.空间复杂度 缩小的迭代动态规划
int fangfa_3(int num[],const int n)//num 数组, 数组的元素数量 n
{
if(num==NULL || n==0)//s数组是空的, 元素的数量是 0 返回金额为 0
{
return 0;
}
if(n==1)//当只有一个房子的时候,我们直接进入,他的上一个 递归会决定他是否需要返回数值!
{
return num[0];
}
///建立DP 空间 来存储数据
int DP[2];//这里减少空间复杂度
//DP 空间初始化
DP[0]=num[0];
DP[1]=max(num[0],num[1]);
int pp=0;
//方法和上面的差不多,都是从小的时候开始求取 最优解
for(int i=2;i<n;i++)
{
pp=DP[1];
DP[1]=max(DP[1],DP[0]+num[i]);
DP[0]=pp;
}
return DP[1];
}
int main()
{
int num[]={1,2,3,1};
int num1[]={2,7,9,3,1};
//递归
int a=fangfa_1(num,3);//这个后面的数字是 进入的房子, 房子从0 开始, 所以最大为 n-1 .
int a1=fangfa_1(num1,4);
cout<<"a = "<<a<<endl;
cout<<"a1= "<<a1<<endl;
///迭代的动态规划
int b=fangfa_2(num,4);//后面的数字是元素个数!
int b1=fangfa_2(num1,5);
cout<<"b = "<<b<<endl;
cout<<"b1= "<<b1<<endl;
//.空间复杂度 缩小的迭代动态规划
int c=fangfa_3(num,4);//后面的数字是元素个数!
int c1=fangfa_3(num1,5);
cout<<"c = "<<c<<endl;
cout<<"c1= "<<c1<<endl;
return 0;
}
5.DP思想:
这就是DP(动态规划,dynamic programming).
将一个问题拆成几个子问题,分别求解这些子问题,即可推断出大问题的解。
2. 几个简单的概念
【无后效性】
一旦f(n)确定,“我们如何凑出f(n)”就再也用不着了。
要求出f(15),只需要知道f(14),f(10),f(4)的值,而f(14),f(10),f(4)是如何算出来的,对之后的问题没有影响。
“未来与过去无关”,这就是无后效性。
(严格定义:如果给定某一阶段的状态,则在这一阶段以后过程的发展不受这阶段以前各段状态的影响。)
【最优子结构】
回顾我们对f(n)的定义:我们记“凑出n所需的最少钞票数量”为f(n).
f(n)的定义就已经蕴含了“最优”。利用w=14,10,4的最优解,我们即可算出w=15的最优解。
大问题的最优解可以由小问题的最优解推出,这个性质叫做“最优子结构性质”。
引入这两个概念之后,我们如何判断一个问题能否使用DP解决呢?
能将大问题拆成几个小问题,且满足无后效性、最优子结构性质。
说说我所理解的dp思想
dp一般用于解决多阶段决策问题,即每个阶段都要做一个决策,全部的决策是一个决策序列,要你求一个最好的决策序列使得这个问题有最优解。
将待求解的问题分为若干个相互联系的子问题,只在第一次遇到的时候求解,然后将这个子问题的答案保存下来,下次又遇到的时候直接拿过来用即可。
dp和分治的不同之处在于分治分解而成的子问题必须没有联系(有联系的话就包含大量重复的子问题,那么这个问题就不适宜分治,虽然分治也能解决,但是时间复杂度太大,不划算),所以用dp的问题和用分治的问题的根本区别在于分解成的子问题之间有没有联系,这些子问题有没有重叠,即有没有重复子问题。
dp和贪心的不同之处在于每一次的贪心都是做出不可撤回的决策(即每次局部最优),而在dp中还有考察每个最优决策子序列中是否包含最优决策子序列,即是否具有最优子结构性质,贪心中每一步都只顾眼前最优,并且当前的选择是不会依赖以前的选择的,而dp,在选择的时候是从以前求出的若干个与本步骤相关的子问题中选最优的那个,加上这一步的值来构成这一步那个子问题的最优解。