赞
踩
目录
dp[i]位置的值,表示以i为结尾的子数组的最大和。注意,是以i为结尾,并没有规定从那个位置开始。
i位置有两种状态:长度为1,长度大于1。
长度为1时,以i结尾,就是i位置的值,即nums[i]
长度大于1时,以i结尾,就是要包括前面i-1位置的和,即dp[i-1] + nums[i]
初始化解决的是填表越界的问题
根据状态转移方程,我们需要初始化dp[0]的位置
第一个,因为必须包含一个元素,所以只能是其本身值
即dp[0] = n[0]
从左往右
我们不知道最大值到底是以那个位置为结尾的连续子数组,因此要遍历拿出最大值。
- class Solution {
- public:
- int maxSubArray(vector<int>& nums) {
- //1、创建dp表
- int n = nums.size();
- if(n==1) return nums[0];
- vector<int> dp(n);
-
- //2、初始化
- dp[0] = nums[0];
-
- //3、填表
- for(int i = 1; i<n; ++i)
- {
- dp[i] = max(nums[i], dp[i-1] + nums[i]);
- }
-
- //4、返回值
- int ret = -0x3f3f3f3f;
- for(int i = 0; i<n; ++i)
- {
- if(dp[i] >= ret)
- {
- ret = dp[i];
- }
- }
- return ret;
-
- }
- };
这一题比简单的线性最大数组和多了一个环形。
我们怎么做呢?
可以不可以把环形,变成线性的数组?
如果转化为线性的数组,那就是最大连续子数组和
我们仔细研究一下这个环形,会发现:答案就只有两种可能
一个是最大连续字数组就在中间位置:
一个是最大连续子数组在两边连接处:
这第二种情况,我们会发现中间的连续子数组是最小的。
那么数组的最大值就是整个数组的和-最小连续子数组。
那么,我们就来比较这两种情况那个大即可。
我们需要两个表,一个f求最小值,一个g求最大值:
f[i]位置的值,表示以i为结尾的最小连续子数组和。注意,是以i为结尾,并没有规定从那个位置开始。
g[i]位置的值,表示以i为结尾的最大连续子数组和。注意,是以i为结尾,并没有规定从那个位置开始。
i位置有两种状态:长度为1,长度大于1。
长度为1时,以i结尾,就是i位置的值,即nums[i]
长度大于1时,以i结尾,就是要包括前面i-1位置的和,即f[i-1] + nums[i]
f[i] = min(nums[i], f[i-1] + nums[i]);
g[i] = max(nums[i], g[i-1] + nums[i]);
一个求最大,一个求最小。
初始化解决的是填表越界的问题
根据状态转移方程,我们需要初始化f[0]的位置
f[0] = nums[0]
g[0] = nums[0]
从左往右
找出最大值,和sum-最小值对比,返回大者。
但是,需要注意,如果所有的数组都是负数,最小值就是所有数字的和。
sum - min = 0;就会返回0
但是返回值不能是0,因为必须包含一个元素。
所以需要特别判断
- class Solution {
- public:
- int maxSubarraySumCircular(vector<int>& nums) {
- //1、创建dp表
- int n = nums.size();
- if(n == 1) return nums[0];
- vector<int> f(n);
- auto g = f;
-
- int sum = 0;
- for(auto e : nums)
- {
- sum += e;
- }
-
- //2、初始化
- f[0] = nums[0];
- g[0] = nums[0];
-
- //3、填表
- for(int i = 1; i<n; ++i)
- {
- f[i] = min(nums[i], f[i-1] + nums[i]);//找最小值
- g[i] = max(nums[i], g[i-1] + nums[i]);//找最大值
- }
-
- //4、返回值
- int max_value = -0x3f3f3f3f;
- int min_value = 0x3f3f3f3f;
- for(int i= 0; i<n; ++i)
- {
- if(g[i] >= max_value)
- max_value = g[i];
- if(f[i] <= min_value)
- min_value = f[i];
- }
-
- if(sum == min_value)
- return max_value;
-
- return max(max_value, sum - min_value);
-
-
- }
- };
也是一个连续子数组求最大最小的问题。
我们创建一个一维dp表。
dp[i]的值,表示以i为结尾的最大乘积子数组,注意,并没有规定是从那个位置作为开始位置。
i位置有两种状态:长度为1,长度大于1。
长度为1时,以i结尾,就是i位置的值,即nums[i]
长度大于1时,以i结尾,就是要包括前面i-1位置的和,即dp[i-1] * nums[i]
按照原来的逻辑,上述的推导是没有问题的。
但是,如果nums[i]是一个负数,dp[i-1]是一个最大值,那么最大值×负数,直接变成最小值。
出问题了。
正确的逻辑应该是:
如果nums[i]是一个正数,那么dp[i-1]的值,应该是最大值
如果nums[i]是一个负数,那么dp[i-1]的值,应该是最小值。
所以,按照我们的逻辑推导,我们需要两个状态表:
f[i]代表表示以i为结尾的最大乘积子数组
g[i]代表表示以i为结尾的最小乘积子数组
如图:
初始化解决的是填表越界的问题
根据状态转移方程,我们需要初始化f[0]的位置
f[0] = g[0] = nums[0];
从左往右
返回f表中的最大值。
- class Solution {
- public:
- int maxProduct(vector<int>& nums) {
- //1、创建dp表
- int n = nums.size();
- if(n == 1) return nums[0];
-
- vector<int > f(n);
- auto g =f;
-
- //2、初始化
- f[0] = g[0] = nums[0];
-
- //f[i]为最小值
- //g[i]为最大值
-
- //3、填表
- for(int i = 1; i<n; ++i)
- {
- f[i] = min(nums[i], min(g[i-1] * nums[i], f[i-1] * nums[i]));
- g[i] = max(nums[i], max(f[i-1] * nums[i], g[i-1] * nums[i]));
- }
-
- //4、返回值
- int ret = INT_MIN;
- for(int i = 0; i<n; ++i)
- {
- if(g[i] >= ret)
- ret = g[i];
- }
-
- return ret;
- }
- };
-
1567. 乘积为正数的最长子数组长度 - 力扣(LeetCode)
一般来说,子数组系列的问题,使用动态规划思想来解决。
一般的dp状态就是:以i为结尾的,某某某。
同时把数组分成两种,如图:
一个是以i位置的元素,长度为1为一个结果
一个是以i-1以前,长度大于1作为一个结果
也是一个连续子数组求最大最小的问题。
我们创建一个一维f表。
f[i]的值,表示以i为结尾的最大乘积子数组为正数的长度。
i位置有两种状态:长度为1,长度大于1。
长度为1时,i位置有两种状态:一个是i位置本身为正数,或者是负数
长度大于1时,i位置也有两种状态:一个是i位置本身是正数*前面i-1序列中,最大乘积为正数的子数组长度+1
一个是i位置本身为负数*前面i-1序列中,最大乘积为负数的子数组长度+1
如图:
初始化解决的是填表越界的问题
根据状态转移方程,我们需要初始化f[0]和g[0]的位置
f[0]记录正数,g[0]记录负数。
如果nums[0]为正数,g[0] = 0, f[0] = 1;
否则反之。
从左往右
返回f表中的最大值。
- class Solution {
- public:
- int getMaxLen(vector<int>& nums) {
- //1、创建dp表
- int n = nums.size();
- vector<int> f(n), g(n);
- if(n == 1) return nums[0] >= 0 ? 1 : 0;
-
- //2、初始化
- g[0] = 1;
- f[0] = 0;
- if(nums[0] > 0)
- {
- g[0] = 0;
- f[0] = 1;
- }
- else if(nums[0] == 0)
- {
- g[0] = f[0] = 0;
- }
-
- //3、填表
- int ret = INT_MIN;
- for(int i = 1; i<n; ++i)
- {
- if(nums[i] < 0)
- {
- f[i] = g[i-1] == 0 ? 0 : g[i-1] + 1;
- g[i] = f[i-1] + 1;
- }
- else if(nums[i] > 0)
- {
- f[i] = f[i-1] + 1;
- g[i] = g[i-1] == 0 ? 0 : g[i-1] + 1;
- }
- ret = max(ret, f[i]);
- }
-
- //4、返回值
- return ret;
- }
-
- };
-
也是一个连续子数组求最大最小的问题。
我们创建一个一维f表。
f[i]的值,表示以i为结尾的,所有为等差数组的子数组个数。
注意,是以i为结尾的等差数列,也就是说把i作为结尾。
以i为结尾,两种状态:
一是i和前面的两个i-1 i-1 构成等差数列
此时,在i-1为结尾的等差数组个数的基础上,再+1
因为把i-1为结尾的三个以上的子数组,再加上一个i位置,也构成等差数组,就是dp[i-1]
在此基础上,多了一个i-2 i-1 i 这一组等差数列
所以 dp[i] = dp[i-1] + 1
二是i和前面的而两个 i-1 i-2 不构成等差数列,此时,为0
如图:
初始化解决的是填表越界的问题
根据状态转移方程,我们需要初始化:
dp[0] = 0
dp[1] = 0
从左往右
返回dp表的所有总和。
- class Solution {
- public:
- int numberOfArithmeticSlices(vector<int>& nums) {
- //1、创建dp表
- int n = nums.size();
- if(n < 3 ) return 0;
- vector<int> dp(n);
-
- //2、初始化
- dp[0] = dp[1] = 0;
-
- //3、填表
- for(int i = 2; i<n; i++)
- {
- dp[i] = nums[i] - nums[i-1] == nums[i-1] - nums[i-2] ? dp[i-1] + 1 : 0;
- }
-
- //4、返回值
- int num = 0;
- for(int i = 2; i<n; ++i)
- {
- num += dp[i];
- }
- return num;
-
-
- }
- };
最长瑞流子序列
直接确定状态:
dp[i]:表示以i为结尾的所有瑞流子序列中,最长的子序列。
再细分:
以i结尾的瑞流子序列,有两种状态:
一个是最后呈现“上升”,就是arr[i] > arr[i-1]
一个是最后呈现“下降”,就是arr[i] < arr[i-1]
所以,仅仅一个状态表无法全部表示
需要两个状态表
f[i]:最后是上升
g[i]:最后是下降
当时最后呈现上升时:
f[i] = g[i-1] + 1
当时最后呈现下降时:
g[i] = f[i-1] + 1
- class Solution {
- public:
- int maxTurbulenceSize(vector<int>& arr) {
- //1、创建dp表
- int n = arr.size();
- vector<int> f(n, 1), g(n, 1);
-
- //2、初始化
-
- //3、填表
- int ret = 1;
- for(int i = 1; i<n; ++i)
- {
- if(arr[i] > arr[i-1])
- {
- f[i] = g[i-1] + 1;
- }
- else if(arr[i] < arr[i-1])
- {
- g[i] = f[i-1] + 1;
- }
- ret = max(ret, max(g[i], f[i]));
- }
-
- //4、返回值
- return ret;
-
-
-
- }
- };
dp[i]:0到i的序列可以被字典中的单词组成
设一个j:表示最后一个单词的开始位置
dp[i]的值,等于dp[j]为true,并且[j+1,i]位置也可以被字典序组成
也就是说将整个字符串序列分成两个部分:最后一个单词+之前位置的是否能被构成。
- class Solution {
- public:
- bool wordBreak(string s, vector<string>& wordDict) {
- //1、创建dp表
- int n = s.size();
- vector<bool> dp(n+1);
- unordered_set<string> hash;
- for(auto& e : wordDict)
- {
- hash.insert(e);
- }
- s = ' ' + s;
-
- //2、初始化
- dp[0] = true;
-
- //3、填表
- for(int i = 1; i<n+1; ++i)
- {
- for(int j = i; j>=1; j--)
- {
- if(dp[j-1] && hash.count(s.substr(j, i-j+ 1)))
- {
- dp[i] = true;
- break;
- }
- }
- }
-
- //4、返回值
- return dp[n];
-
- }
- };
467. 环绕字符串中唯一的子字符串 - 力扣(LeetCode)
dp[i]:以i为结尾的所有字串的个数
i位置结尾:
长度为1
长度大于1 以i-1位置为前一个 dp[i] = dp[i-1] + 1
- class Solution {
- public:
- int findSubstringInWraproundString(string s) {
- //1、创建dp表
- int n = s.size();
- vector<int> dp(n,1);
- if(n == 1) return 1;
-
- //2、初始化
-
- //3、填表
- for(int i = 1; i<n; ++i)
- {
- if(s[i] - s[i-1] == 1 || (s[i-1] == 'z' && s[i] == 'a'))
- {
- dp[i] = dp[i-1] + 1;
- }
- }
-
- //4、返回值
- int hash[26] = {0};
- for(int i = 0; i<n; ++i)
- {
- hash[s[i] - 'a'] = max(hash[s[i] - 'a'], dp[i]);
- //dp[i] 的值,对应的就是s[i]为结尾的字串的结果
- }
-
- int ret = 0;
- for(int e : hash)
- {
- ret += e;
- }
- return ret;
- }
- };
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。