打卡leetcode第8天(动态规划入门)

1.最大子数组和

【题目】

【分析】

①动态规划法:

关键 1:理解题意

题目要我们找出和最大的连续子数组的值是多少,「连续」是关键字,连续很重要,不是子序列。

题目只要求返回结果,不要求得到最大的连续子数组是哪一个。这样的问题通常可以使用「动态规划」解决。

关键 2:如何定义子问题(如何定义状态)

设计状态思路:把不确定的因素确定下来,进而把子问题定义清楚,把子问题定义得简单。动态规划的思想通过解决了一个一个简单的问题,进而把简单的问题的解组成了复杂的问题的解。

我们 不知道和最大的连续子数组一定会选哪一个数,那么我们可以求出 所有 经过输入数组的某一个数的连续子数组的最大和。

例如,示例 1 输入数组是 [-2,1,-3,4,-1,2,1,-5,4] ,我们可以求出以下子问题:

    子问题 1:经过 −2-2−2 的连续子数组的最大和是多少;
    子问题 2:经过 111 的连续子数组的最大和是多少;
    子问题 3:经过 −3-3−3 的连续子数组的最大和是多少;
    子问题 4:经过 444 的连续子数组的最大和是多少;
    子问题 5:经过 −1-1−1 的连续子数组的最大和是多少;
    子问题 6:经过 222 的连续子数组的最大和是多少;
    子问题 7:经过 111 的连续子数组的最大和是多少;
    子问题 8:经过 −5-5−5 的连续子数组的最大和是多少;
    子问题 9:经过 444 的连续子数组的最大和是多少。

一共 9 个子问题,这些子问题之间的联系并没有那么好看出来,这是因为 子问题的描述还有不确定的地方。

例如「子问题 3」:经过 −3-3−3 的连续子数组的最大和是多少。「经过 −3-3−3 的连续子数组」我们任意举出几个:

    [-2,1,-3,4] ,−3-3−3 是这个连续子数组的第 3 个元素;
    [1,-3,4,-1] ,−3-3−3 是这个连续子数组的第 2 个元素; ……

我们不确定的是:−3-3−3 是连续子数组的第几个元素。那么我们就把 −3-3−3 定义成连续子数组的最后一个元素。在新的定义下,我们列出子问题如下:

    子问题 1:以 −2-2−2 结尾的连续子数组的最大和是多少;
    子问题 2:以 111 结尾的连续子数组的最大和是多少;
    子问题 3:以 −3-3−3 结尾的连续子数组的最大和是多少;
    子问题 4:以 444 结尾的连续子数组的最大和是多少;
    子问题 5:以 −1-1−1 结尾的连续子数组的最大和是多少;
    子问题 6:以 222 结尾的连续子数组的最大和是多少;
    子问题 7:以 111 结尾的连续子数组的最大和是多少;
    子问题 8:以 −5-5−5 结尾的连续子数组的最大和是多少;
    子问题 9:以 444 结尾的连续子数组的最大和是多少。

我们加上了「结尾的」,这些子问题之间就有了联系。我们单独看子问题 1 和子问题 2:

    子问题 1:以 −2-2−2 结尾的连续子数组的最大和是多少;

以 −2-2−2 结尾的连续子数组是 [-2],因此最大和就是 −2-2−2。

    子问题 2:以 111 结尾的连续子数组的最大和是多少;

以 111 结尾的连续子数组有 [-2,1] 和 [1] ,其中 [-2,1] 就是在「子问题 1」的后面加上 1 得到。−2+1=−1<1-2 + 1 = -1 < 1−2+1=−1<1 ,因此「子问题 2」 的答案是 111。

大家发现了吗,如果编号为 i 的子问题的结果是负数或者 000 ,那么编号为 i + 1 的子问题就可以把编号为 i 的子问题的结果舍弃掉(这里 i 为整数,最小值为 1 ,最大值为 8),这是因为:

    一个数 a 加上负数的结果比 a 更小;一个数 a 加上 000 的结果不会比 a 更大;
    而子问题的定义必须以一个数结尾,因此如果子问题 i 的结果是负数或者 000,那么子问题 i + 1 的答案就是以 nums[i] 结尾的那个数。

    因为我们把子问题定义的更清楚,子问题之间的联系就容易观察到。这是我们定义子问题、定义状态的经验。

接下来我们按照编写动态规划题解的步骤,把「状态定义」「状态转移方程」「初始化」「输出」「是否可以空间优化」全都写出来。
定义状态(定义子问题)

dp[i]:表示以 nums[i] 结尾 的 连续 子数组的最大和。

说明:「结尾」和「连续」是关键字。
状态转移方程(描述子问题之间的联系)

根据状态的定义,由于 nums[i] 一定会被选取,并且以 nums[i] 结尾的连续子数组与以 nums[i - 1] 结尾的连续子数组只相差一个元素 nums[i] 。

假设数组 nums 的值全都严格大于 000,那么一定有 dp[i] = dp[i - 1] + nums[i]。

可是 dp[i - 1] 有可能是负数,于是分类讨论:

    如果 dp[i - 1] > 0,那么可以把 nums[i] 直接接在 dp[i - 1] 表示的那个数组的后面,得到和更大的连续子数组;
    如果 dp[i - 1] <= 0,那么 nums[i] 加上前面的数 dp[i - 1] 以后值不会变大。于是 dp[i] 「另起炉灶」,此时单独的一个 nums[i] 的值,就是 dp[i]。

以上两种情况的最大值就是 dp[i] 的值,写出如下状态转移方程:

记为「状态转移方程 1」。

状态转移方程还可以这样写,反正求的是最大值,也不用分类讨论了,就这两种情况,取最大即可,因此还可以写出状态转移方程如下:

记为「状态转移方程 2」。

求解动态规划的问题经常要分类讨论,这是因为动态规划的问题本来就有「最优子结构」的特点,即大问题的最优解通常由小问题的最优解得到。因此我们在设计子问题的时候,就需要把求解出所有子问题的结果,进而选出原问题的最优解。

思考初始值

dp[0] 根据定义,只有 1 个数,一定以 nums[0] 结尾,因此 dp[0] = nums[0]。
思考输出

注意:这里状态的定义不是题目中的问题的定义,不能直接将最后一个状态返回回去;

这个问题的输出是把所有的 dp[0]、dp[1]、……、dp[n - 1] 都看一遍,取最大值。

--参考分析--:力扣

②贪心法:

关键点:
1:遍历整个数组
2:将数组的值进行统计,如果值大于零则一直加,
3:如果值小于零,则丢弃值
4:遍历过程中保存最大值
时间复杂度O(n)
空间复杂度O(n)

【代码】

--动态规划--
int maxSubArray(int* nums, int numsSize){
    int pre=0,maxAns=nums[0];
    for(int i=0;i<numsSize;i++){
        pre=fmax(pre+nums[i],nums[i]);
        maxAns=fmax(maxAns,pre);
    }
    return maxAns;
}
--贪心法--
int maxSubArray(int* nums, int numsSize)
{
    int sum = 0;
    int max = INT_MIN;
    for(int i = 0;i<numsSize;i++)
    {
        sum += nums[i];
        if(sum>max)
        {
            max = sum;
        }
        if(sum < 0)
        {
            sum = 0;
        }
    }
    return max;

}

贪心法针对这题快一些

2.分割等和子集

【题目】

【分析】

 

【代码】

bool canPartition(int* nums, int numsSize)
{
	int sum = 0;
	int i, j;
	int tar;
	for (i = 0; i< numsSize; i++) {
		sum += nums[i];
	}

	if (sum % 2 == 1) {
		return false;
	}
	tar = sum / 2;
    bool dp[numsSize][tar + 1];
    memset(dp,false,sizeof(dp));
	for (j = 0; j <= tar; j++) {
		if (nums[0] == j) {
			dp[0][j] = true;
		}
	}

	for (i = 1; i < numsSize; i++) {
		for (j = 0; j <= tar; j++) {
			if (j < nums[i]) {
				dp[i][j] = dp[i - 1][j];
			} else {
				dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i]];
			}
		}
	}

	return dp[numsSize - 1][tar];
}

3.零钱兑换

 【解析】

目中说每种硬币的数量是无限的,可以看出是典型的完全背包问题。

动规五部曲分析如下:

1.确定dp数组以及下标的含义

dp[j]:凑足总额为j所需钱币的最少个数为dp[j]

2.确定递推公式

得到dp[j](考虑coins[i]),只有一个来源,dp[j - coins[i]](没有考虑coins[i])。

凑足总额为j - coins[i]的最少个数为dp[j - coins[i]],那么只需要加上一个钱币coins[i]即dp[j - coins[i]] + 1就是dp[j](考虑coins[i])

所以dp[j] 要取所有 dp[j - coins[i]] + 1 中最小的。

递推公式:dp[j] = min(dp[j - coins[i]] + 1, dp[j]);

3.dp数组如何初始化

首先凑足总金额为0所需钱币的个数一定是0,那么dp[0] = 0;

其他下标对应的数值呢?

考虑到递推公式的特性,dp[j]必须初始化为一个最大的数,否则就会在min(dp[j - coins[i]] + 1, dp[j])比较的过程中被初始值覆盖。

所以下标非0的元素都是应该是最大值。

参考:leetcode-master/0322.零钱兑换.md at master · youngyangyang04/leetcode-master · GitHub

【代码】:C

int coinChange(int* coins, int coinsSize, int amount) {
    int* dp = (int*)malloc(sizeof(int) * (amount + 1));
    for (int j = 0; j < amount + 1; j++) {
        dp[j] = INT_MAX;
    }
    dp[0] = 0;
    for (int j = 0; j <= amount; j++) {
        for (int i = 0; i < coinsSize; i++) {
            if (j >= coins[i] && dp[j - coins[i]] != INT_MAX) {
                dp[j] = (int)fmin(dp[j], dp[j - coins[i]] + 1);
            }
        }
    }
    if (dp[amount] == INT_MAX) {
        return -1;
    }
    return dp[amount];
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值