一、最大字段和----->最大子矩阵和
最大的连续子段和。f[i]表示必须以第i个数作为结尾,意思就是这个数一定会加上去,那么要看的就是这个数前面的部分要不要加上去。大于零就加,小于零就舍弃。经典的动态规划,这里我用单个变量实现它。时间复杂度O(n).
class Solution {
public:
int maxSubArray(vector<int>& nums) {
if (nums.size()==0) return 0;
int former=0;
int ans=-0x3f3f3f3f;
for (int i=0;i<nums.size();++i)
{
if (former>0) former+=nums[i];//这边也就是说必须以nums[i]为结尾的最大价值。
else former=nums[i];
if (former>ans) ans=former;
}
return ans;
}
};
1.2蓝桥杯-最大子矩阵和
给一个矩阵,求最大子矩阵和,算法是多行压成一行做,枚举从i行到j行(i<=j),把这几行压成一行,然后O(m)求最大子段和,总的时间复杂度是O(n*n*m)
#include<iostream>
#include<cstdio>
#include<vector>
#define INF 0x7fffffff
using namespace std;
int getmax(vector<int>& nums)
{
if (nums.size()==0) return 0;
int former=0;
int ans=-INF;
for (int i=0;i<nums.size();++i)
{
if (former>0) former+=nums[i];//这边也就是说必须以nums[i]为结尾的最大价值。
else former=nums[i];
if (former>ans) ans=former;
}
return ans;
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
vector<vector<int> > a(n+10,vector<int>(m+10));
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
scanf("%d",&a[i][j]);
int ans=-INF;
for(int i=0;i<n;i++)//枚举行级子矩阵第i行到第j行
{
vector<int> sum(m+10,0);//sum用于求第i行到第j行的对应第k列元素之和
for(int j=i;j<n;j++)
{
for(int k=0;k<m;k++)
sum[k]+=a[j][k];
int tmp=getmax(sum);//得到行级子矩阵的列级子矩阵的最大值 也就是求第i行到第j行这个子矩阵里0-m列中最大的子矩阵
if(tmp>ans)
ans=tmp;
}
}
cout << ans << endl;
return 0;
}
测试用例:
3 3
-1 -4 3
3 4 -1
-5 -2 8
10
二、最小的覆盖子串----->最短宽度的覆盖子列矩阵
可以参考我之前写的博客,方法是用的「滑动窗口」这种高级双指针技巧的算法框架,
下面写的方法真正做到了O(lens+lent),而需要判断check的方法则还要再扫描一遍字符集C,所以时间复杂度是O(C*lens+lent),虽然字符集最多256个,大小写最多128(A=65,'z'=122)即下标从65-122,开128的数组就足够了,但是10^2的优化有时候也是很重要的,这里还有一个优化是把hasht和hashs变成一个,减少空间开销。具体见https://leetcode-cn.com/problems/minimum-window-substring/solution/zui-xiao-fu-gai-zi-chuan-by-leetcode-solution/409078
评论说这个判断窗口元素是否满足要求的做法有点妙,被删到0以下的过剩元素和原先为0的非T元素都不会被计入count,保证了count的定义。不过还是有些重复计算。我觉得这里重复计算就是既然要删到cnt--为止,中间就没有必要判断minans,一直删就行了,最后判断一下就ok了。
class Solution {
public:
string minWindow(string s, string t) {
int lens=s.length(),lent=t.length();
if (lent==0) return "";
map<char,int> hasht;
map<char,int> hashs;
for (char c:t) hasht[c]++;
int l=0,r=0;
int minans=lens+1,begans=0;
int cnt=0;//精髓所在,记录窗口S中已经存在多少个T中元素。
//[l,r)
while (r<lens)
{
if (++hashs[s[r]]<=hasht[s[r]]) cnt++;//必须<=因为更多的相同字符没啥用了。
r++;
if (cnt==lent)//已经满足了T中所有元素,现在要开始减左边了。
{
//while (l<r && --hashs[s[l]]>=hasht[s[l]]) l++;
while (--hashs[s[l]]>=hasht[s[l]]) l++;
if (r-l<minans) {minans=r-l;begans=l;}
l++;cnt--;
}
}
if (minans==lens+1) return "";
else return s.substr(begans,minans);
}
};
2.2(没有题目链接,纯属个人理解)给个n*m的矩阵,要求子列矩阵(连续的几列)中包含k个数(可能有重复数),求子列矩阵的最小宽度
比如 1 2 2 3 1
2 3 2 3 2
如果要包含的k(=4)个数为 1 ,2 ,3 ,3 的话
可以选0~3列,宽度为4,或者3~4列,宽度为2,答案为2.
那么这里也采用滑动窗口这种高级双指针,这里不能叫滑动窗口了,该叫滑动矩形了
从第一列开始加入,加到可行情况,然后从第一列开始删除,删除到最极限还可行的位置l,ans = min(ans, r - l + 1),然后再把 l 列删掉,r列继续向后推,直至可行。思路跟滑动窗口可以说是一摸一样。整体复杂度O(n*m+k)
#include<iostream>
#include<cstdio>
#include<vector>
#include<map>
using namespace std;
int minwidth(vector<vector<int> >& s, vector<int>& t,int n,int m,int k)
{
int lens=m,lent=k;
if (lent==0) return 0;
map<int,int> hasht;
map<int,int> hashs;
//for (int c:t) hasht[c]++;
for (int i=0;i<lent;i++) hasht[t[i]]++;
int l=0,r=0;
int minans=lens+1,begans=0;
int cnt=0;//精髓所在,记录窗口S中已经存在多少个T中元素。
//[l,r)
while (r<lens)
{
for (int i=0;i<n;i++)
if (++hashs[s[i][r]]<=hasht[s[i][r]]) cnt++;//必须<=因为更多的相同字符没啥用了。
r++;
if (cnt==lent)//已经满足了T中所有元素,现在要开始减左边了。
{
bool p=true;
while (p)
{
for (int i=0;i<n;i++)
if (--hashs[s[i][l]]<hasht[s[i][l]]) {p=false;cnt--;}
if (p) l++;
}
if (r-l<minans) {minans=r-l;begans=l;}
l++;
}
}
if (minans==lens+1) return -1;
else return minans;
}
int main()
{
int n,m,k;
scanf("%d%d%d",&n,&m,&k);
vector<vector<int> > s(n+10,vector<int>(m+10));
vector<int> t(k+10);
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
scanf("%d",&s[i][j]);
for (int i=0;i<k;i++)
cin>>t[i];
int ans=minwidth(s,t,n,m,k);
cout << ans << endl;
return 0;
}
2 5 4
1 2 2 3 1
2 3 2 3 2
1 2 3 3
2
三、