打家劫舍1

打家劫舍1

在这里插入图片描述

1.递归算法

一种最容易想到的,用不断递归的方法来解决的低效算法:

#include<stdio.h>
#include<stdlib.h>
//深搜解决打家劫舍问题 
int dp(int start,int n,int nums[])
{
	if(start>=n)
	return 0;
	return dp(start+1,n,nums)>=nums[start]+dp(start+2,n,nums)?dp(start+1,n,nums):nums[start]+dp(start+2,n,nums);
}
int rob(int n,int nums[])
{
	return dp(0,n,nums);
}
main()
{
	int nums[]={2,7,9,3,1};
	int n=sizeof(nums)/sizeof(int);
	int max=rob(n,nums);
	printf("the max money we can rob is %d",max);
}

即对每一种问题都进行考虑:
在走每一步之前我们都面临两个选择:
1.抢劫当前这一家,然后对下下一家进行考虑
2.不抢劫当前这一家,对下一家进行考虑
“考虑”是对算法的递归调用,我们每次考虑都在两种选择中选择抢劫钱数最多的一个选择。而对每一种选择我们都需要进一步展开对他们进行考虑,直到出现一个截至条件,就像是深搜递归的终止条件一样。
在上述算法中,我们默认是从第0家开始考虑的,所以当我们对考虑的展开考虑,也就是对子问题的求解再次循环进行到第0家时,就应该对这样的考虑问题进行终止了。并且这样的考虑是没有意义的,我们将撤销这样的步骤,所以应当返回0,从而对结果不产生任何影响。
这样的算法是可行的,但是面临重复计算的低效问题。
即对于每一种考虑的结果,我们可能在无意识的情况下对其进行了大量重复计算,比如说对最优解的考虑我们可以初步展开成:
max(dp(1),n[0]+dp(2)) 而dp(1)又可以展开成max(dp(2),n[1]+dp(3))
到这里就足以说明重复计算的情况了,即在对最优解的考虑中我们需要对dp(2)的情况进行一次计算,而对dp(1)的考虑中,我们也要对dp(2)进行一次计算,这里的计算显然是重复的,并且由于对dp(2)的计算可能可以进一步进行大量的展开,其中又可能包含了大浪的重复计算,所以这就造成了算法效率的低下。

2.记忆化递归算法

我们应该使用记忆化的思想对这样的算法进行优化,即通过先将最优解的问题逐步拆分成子问题的解,然后不断求解子问题,并对子问题的结果进行记录,对其记录是很重要的,这样的记录可以避免对相同子问题的重复计算,从而显著提高散发效率,而我们只牺牲了很小一部分的空间复杂度。

#include<stdio.h>
#include<stdlib.h>
//动态规划处理打家劫舍 
int dp(int start,int n,int nums[],int memory[])
{
	if(start>=n)
	return 0;
	if(memory[start]!=0) return memory[start];
	else memory[start]=dp(start+1,n,nums,memory)>=nums[start]+dp(start+2,n,nums,memory)?
	dp(start+1,n,nums,memory):nums[start]+dp(start+2,n,nums,memory);
	return memory[start];
}
int rob(int n,int nums[],int memory)
{
	return dp(0,n,nums,memory);
}
main()
{
	int nums[]={2,7,9,3,1};
	int n=sizeof(nums)/sizeof(int);
	
	//定义记录子问题的数组
    int memory[n];
    
    memset(memory,0,sizeof(memory));
	int max=rob(n,nums,memory);
	printf("the max money we can rob is %d",max);
}

3.深搜求解

深搜求解本问题的实质和第一种递归差不多,旨在求出每一种可能的情况且每一次搜索都将递归进行到尽头。
但是以下算法更贯彻了深搜的思想,每次一搜索的下次一搜索都直接对从下一个可搜索的点到最后一个可搜索的点在内的全部点展开了搜索,可以看作是自顶向下的搜索。并且对最优解的获取直接是在每次一搜索的结尾进行判断的,而没有将最优解分割为子最优解。

#include<stdio.h>
#include<stdlib.h>
int max=0;
int sum=0;
//深搜处理打家劫舍 
void dfs(int index,int n,int nums[])
{
	sum+=nums[index]; 
	
	int i,j,k;
	for(i=index+2;i<n;i++)
	{
		dfs(i,n,nums);
	}
	if(index+2>=n)
	{
	  max = max<sum ? sum : max;
	}
	sum-=nums[index];
}
void rob(int n,int nums[])
{
	 dfs(0,n,nums);//偷第0家
	 dfs(1,n,nums);//不偷第0家
}
main()
{
	int nums[]={2,7,9,3,1};
	int n=sizeof(nums)/sizeof(int);
	rob(n,nums);
	printf("the max money we can rob is %d",max);
}
打家劫舍1问题可以使用动态规划算法来解决。动态规划的核心思想是将大问题分解为小问题,并保存小问题的解以避免重复计算。 ### 算法思路 设`dp[i]`表示考虑前`i`个房屋能够偷窃到的最高金额。对于第`i`个房屋,有两种选择: 1. 不偷窃第`i`个房屋,那么`dp[i] = dp[i - 1]`。 2. 偷窃第`i`个房屋,那么`dp[i] = dp[i - 2] + nums[i - 1]`(因为不能偷窃相邻的房屋)。 状态转移方程为:`dp[i] = max(dp[i - 1], dp[i - 2] + nums[i - 1])`。 ### 代码实现(Java) ```java public class HouseRobber { public int rob(int[] nums) { int n = nums.length; // 处理边界情况 if (n == 0) { return 0; } if (n == 1) { return nums[0]; } // 创建dp数组,dp[i]表示考虑前i个房屋能够偷窃到的最高金额 int[] dp = new int[n + 1]; // 设置初始条件 dp[0] = 0; // 没有房屋,收益为0 dp[1] = nums[0]; // 只有个房屋,最大收益就是偷这个房屋 // 按照状态转移方程填充dp数组 for (int i = 2; i <= n; i++) { // 状态转移方程:dp[i] = max(dp[i-1], dp[i-2] + nums[i-1]) dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i - 1]); } // 返回考虑所有房屋的最大收益 return dp[n]; } } ``` ### 复杂度分析 - **时间复杂度**:$O(n)$,其中`n`是房屋的数量。需要遍历次数组来填充`dp`数组。 - **空间复杂度**:$O(n)$,主要用于存储`dp`数组。 ### 优化空间复杂度 可以发现,在计算`dp[i]`时,只需要用到`dp[i - 1]`和`dp[i - 2]`,因此可以使用两个变量来代替`dp`数组,将空间复杂度优化到$O(1)$。 ```java public class HouseRobberOptimized { public int rob(int[] nums) { int n = nums.length; if (n == 0) { return 0; } if (n == 1) { return nums[0]; } int prev2 = 0; int prev1 = nums[0]; for (int i = 2; i <= n; i++) { int curr = Math.max(prev1, prev2 + nums[i - 1]); prev2 = prev1; prev1 = curr; } return prev1; } } ``` ### 复杂度分析 - **时间复杂度**:$O(n)$,其中`n`是房屋的数量。需要遍历次数组。 - **空间复杂度**:$O(1)$,只使用了常数级的额外空间。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值