记录两道相似升级版(好像是华为的题)

29 篇文章 0 订阅

一、最大字段和----->最大子矩阵和

1.1leetcode53.最大子序和

最大的连续子段和。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

 

二、最小的覆盖子串----->最短宽度的覆盖子列矩阵

2.1leetcode 76 最小覆盖子串

可以参考我之前写的博客,方法是用的「滑动窗口」这种高级双指针技巧的算法框架

下面写的方法真正做到了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

 

三、

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值