5.25【A】

1871

自最后向前遍历,如果从最后往前断了,就false,不然就true

复制代码
class Solution {
public:
    bool canReach(string s, int minJump, int maxJump) {
        int n=s.size(),cur=n-1;
        if(s[cur]!='0'){return false;}
        for(int i=n-2;i>=0;i--){
            if(s[i]=='0'){
                if(cur-i<=maxJump&&cur-i>=minJump){
                    cur=i;
                }else{
                    return false;
                }
            }
        }
        return true;
    }
};

这样一次遍历不行,因为会有多条路径,这个cur只保存了一条路径,所以会漏掉一些情况从而误判

那还是bfs吧,最省事

复制代码
class Solution {
public:
    bool canReach(string s, int minJump, int maxJump) {
        int n=s.size();
        if(s[n-1]!='0'){return false;}
        vector<bool>flag(n,false);
        queue<int>q;
        q.push(0);
        flag[0]=true;
        while(!q.empty()){
            int cur=q.front();
            q.pop();
            for(int i=cur+minJump;i<=min(cur+maxJump,n-1);i++){
                if(s[i]=='0'&&!flag[i]){
                    if(i==n-1){return true;}
                    flag[i]=true;
                    q.push(i);
                }
            }
        }
        return flag[n-1];
    }
};

这代码怎么还能超时?就是全是0的情况,

但bfs每个点都只遍历一次阿,不也就On级别的复杂度?怎么对于10^5级别的数据还能超时?

如果用DP,该怎么划分子问题才能无后效性?

dp[i]=dp[j]或的和,j为i所能跳转到的那些元素;

但如果不限制上界,又何来缩减问题规模一说?

那还是从后往前遍历,还是维护一个队列,来往前寻找吗?这样才可能有多条路径返回起点

维护一个滑动窗口,就是从左侧开始,表示能到达当前i的所有元素;那他应该是[i-maxJump,i-minJump];然后维护其中的0是否能被到达;当滑动窗口移动,即i向前移动时,对于右侧新增的值,如果是0,看他能否被达到,用一个flag数组来维护每个0能否被到达,如果能就把它的下标入窗口队列,不然不管;对于左侧,检测下标是否过时了,即Index<i-maxJump,如果过时了就移除;甚至应该不用窗口队列,就是维护这个窗口里所能到达的0的最大下标即可;如果这个最大下标出了窗口,即maxIndex<i-maxJump,那就直接return false;否则就一直到i为数组末尾,表示可达

我说的最大值,是说滑动窗口内部存在0且能被到达的下标的最大值index,它本身就一定是在窗口内部的,而且是不断被维护的最大值,所以当他小于i-maxJump时,就意味着后面的0一定都不可达;此外,如何完成滑动窗口的初始化,即我知道要维护[i-maxJump,

复制代码
class Solution {
public:
    bool canReach(string s, int minJump, int maxJump) {
        int n=s.size(),maxIndex=0;;
        if(s[n-1]!='0'){return false;}
        //vector<bool>flag(n,false);//[i-maxJump,i-minJump]
        for(int i=minJump;i<=maxJump;i++){
            //flag[i]=s[i]=='0';
            if(s[i]=='0'){
                //flag[i]=true;
                maxIndex=i;
            }
        }
        for(int i=maxJump+1;i<n;i++){
            cout<<"cur i:"<<i<<endl;
            if(maxIndex<i-maxJump){return false;}
            //if(maxIndex>i-minJump){return false;}
            if(s[i]=='0'){maxIndex=i;}
        }
        return true;
    }
};

这个确实就是太近所导致的

就是应该要保证这个维护的值,确实是该在窗口里

入队的位置是在i-minJump,出队的位置是在i-maxJump

我现在对于出队的处理,就是if(maxIndex<i-maxJump){return false;}阿,这个maxIndex就是指窗口当中那个最不可能被淘汰的计数,即cnt=1时的情况,如果中间的某个窗口存在cnt=0,那么后面的0不都到不了吗,而且能保证这个cnt是能被此时的i访问到的

就是说只保留最右侧满足条件的话,相当于只保留了一条路径,不过由于本身是I的滑动窗口,所以本身也没什么问题;

但对于

错的就是几种情况,就是一开始窗口初始化的时候,是否应该给maxIndex赋值,如果赋值的话,那肯定是赋值成maxJump,这个值,对应的是哪个下标的?说不清楚

如果不赋值,那么遍历的时候,从哪里开始遍历?之前是maxIndex+1,如果从MaxIndex,那这个下标的窗口就是[0,maxIndex-minIndex]

即,如果还是从MaxIndex-minIndex来移动右端点赋值的话,一开始的maxIndex是0,就会忽略掉中间的可能最大值

所以对于maxJump的起点的情况,对于这个下标的滑动窗口,要完整判断其窗口内的所有元素,因为除了其最有端点maxJump-minJump,其前面的所有点,都是没有元素来检验入队过的

这个样例没过是因为,当前的0确实是不可达的,是不能被其窗口里的值所能到达的,但当前的0不能被到达,不意味着到达不了终点

就是前面,比如前一个0可达,那就可以到达比当前这个0更远的地方,所以这个提前终止的判断就是不对的

复制代码
class Solution {
public:
    bool canReach(string s, int minJump, int maxJump) {
        int n=s.size(),maxIndex=0;;
        if(s[n-1]!='0'){return false;}
        vector<bool>flag(n,false);//[i-maxJump,i-minJump]
        for(int i=minJump;i<=maxJump;i++){
            //flag[i]=s[i]=='0';
            if(s[i]=='0'){
                flag[i]=true;
                //maxIndex=i;
            }
        }
        for(int i=0;i<=maxJump-minJump;i++){
            if(flag[i]){maxIndex=i;}
        }
        for(int i=maxJump;i<n;i++){

            cout<<"cur i:"<<i<<"当前元素为"<<s[i]<<"cur maxIndex"<<maxIndex<<endl;
            if(s[i-minJump]=='0'&&flag[i-minJump]){maxIndex=i-minJump;}
            if(s[i]!='0')continue;
            if(maxIndex<i-maxJump){return false;}
            //if(maxIndex>i-minJump){return false;}
            //if(s[i]=='0'){flag[i]=true;}
            flag[i]=true;
        }
        return true;
    }
};

class Solution {
public:
    bool canReach(string s, int minJump, int maxJump) {
        int n=s.size(),cnt=0;;
        if(s[n-1]!='0'){return false;}
        vector<bool>flag(n,false);//[i-maxJump,i-minJump]
        flag[0]=true;
        for(int i=minJump;i<=maxJump;i++){
            if(s[i]=='0'){
                flag[i]=true;
            }
        }
        for(int i=0;i<=maxJump-minJump;i++){
            if(flag[i]){cnt++;}
        }
        for(int i=maxJump+1;i<n;i++){
            //cout<<"cur i:"<<i<<"当前元素为"<<s[i]<<"cur maxIndex"<<maxIndex<<endl;
            if(flag[i-maxJump-1]){cnt--;}
            if(flag[i-minJump]){cnt++;}
            flag[i]=(cnt>0)&&s[i]=='0';
        }
        // for(int i=0;i<n;i++){
        //     cout<<flag[i]<<" ";
        // }
        return flag[n-1];
    }
};

319

就是统计每个数的因数数量

如果是奇数,最后就是开,否则就是偶数

如何快速求解因数的数量?如何证明完全平方数的因数数量是奇数?

树上背包

那如何知道处理顺序?就是能够在处理完3和4后,找到1,处理完1后找到2

如果DP,那dp[i][j]应该表示?表示在能选j个课的情况下,由i为根节点所展开的子树,所能达到的最大值

如果他有两个子节点l和r

如果由i展开,那i根节点必然被选择,

所以dp[i][j]=max(dp[l][j-1],dp[r][j-1])+num[i]吗

然后对于定值,就是

先构建树,如何构建树?

然后对这个树进行后序遍历,在叶子节点,那么就是dp[i][j]=num[i]

最后返回根节点的dp值

那就是说再加一重循环,从0到j-1,表示分给左子树的任务数量

即dp[i][j]=max(for 0->j-1)(dp[l][k]+dp[r][j-1-k]))+num[i]

不应该就一个循环码?对于每个根节点,这个v是什么?什么叫分配给前面子树的容量?

就是说,这个数不是二叉树,而是可能有多个孩子的,有多个子树的

那确实是好几重循环

先是遍历每个孩子节点,然后确定这个孩子节点要花几个容量

怎么列式?好复杂,

假设根节点有ABCD等子节点,根节点有J个容量,合并是什么意思?看起来,有多少个孩子,就有几个循环,循环的内容就是确定分给每个孩子的容量数量,其加和为j-1,即对于A,有a为分给它的数量,从0循环到j-1,然后有b为分给B的数量,从0循环到j-1-a,直到最后一个孩子节点

然后DP就是

dp[i][j]=max(dp[A][a]+dp[B][b]+dp[C][c]+dp[D][d]+num[i],dp[i][j])

这个循环,就是说是除了自己以外的其它孩子节点所分到的容量数量

但是这样,如何体现出各种孩子之间的组合?

就是说当在遍历孩子A的时候,给自己分的是k,那么分给其它孩子的容量就是v-k,那就必须得知道dp[i][v-k]

目前每个孩子的dp[][*]都是知道的,因为是后序处理,所以当处理到根节点时,子节点的DP都已经确定了;

每个孩子下的容量v是什么物理含义?不应该就是分给某个孩子的容量吗?取值不就是0到j-1吗

即给自己k个,与给其他人j-1-k个的组合

知道了给其他人j-1-k个时的最佳组合,那最优就是max(dp[A][k]+其它孩子在j-1-k时的最优组合+num[i],dp[i][j])

感觉说白了还是当成了一个二叉树处理了,即看成了自己和别人,即使这个别人可能包含多个元素

但不对吧

这样看不就一个循环,即分给这个孩子的容量v的循环吗?为什么内部还有k的循环,k是什么意思?

我现在想的DP过程就是,

遍历所有孩子

然后DP[i][j]=max(dp[i][j-1-v],dp[这个孩子][v]+dp[i][v]+num[i])

不过这样的话,dp[i][v]就没被赋值,????

但是这个应该是更顶层要做的事吧?不对,好像就是本层要确定的?好乱

那如果给定dp[i][j]后,dp[i]的0到j-1都要自己确定,那整个树上DP的进行过程是怎样的?

就是先构建了一颗依赖树,然后说只能上K门课

对于根节点,要确定根节点的DP值,就要知道所有孩子节点的DP值,那在确定孩子节点DP值的时候,对于容量,应该是怎样的范围?

就是DP赋值的顺序和上限是怎么确定?

这是对于树形的情况,如果是图上的情况,怎么搞?就是每个节点的入度和出度最大不再是1,而可能是更大?

那对于森林的情况,如果加上了学分为0的0号根节点,那意味着在原来的M门课基础上,要再多修一门0号课程呗,这也是为什么要用m+2而不是m+1的原因吗?;但是之前的合并写法,将对根节点访问也包含了,这是否意味着对根节点的访问方案是可以被换掉的?就是相较于访问根节点,将这个容量全部给到子节点上会取得更高的收益情况下,会更新DP吗?这样的话不就违背了,就是说必须强制访问根节点的

线段树

现在是直接给定的一个数列;对于长度为n为的数列,就是以N/2,即中点来划分左右子树,前一半是左子树,后一半是右子树;

对于[x,y]区间上加上k的操作,判断y与中点mid的关系,如果小于,就说明全部都在左区间上

否则有在有区间的部分

再判断x与中点mid的关系,如果大于,就说明全在右区间上

两个判断都不满足,那就说明左右都有,对于左区间,应该是[x,mid];右区间应该是[mid+1,y]

那对于修改,好像还是一个一个地去修改树上的节点,还是On

对于求和,把递归的部分加起来

不过加速的部分在哪?如果是y刚好等于mid的情况,应该可以不再递归,x等于mid+1的情况也是

即取等时的情况

就是说N是树的底层叶子节点,先是要保证把它扩充成2的整数次幂,最差情况是刚好过了1

为2N即可,

现在知道了N个叶子节点,怎么由这N个节点构筑出来线段树?

相关推荐
武子康13 分钟前
Java-07 深入浅出 MyBatis数据库一对多关系模型实战:表结构设计与查询实现
java·后端
REDcker2 小时前
Linux OverlayFS详解
java·linux·运维
玖釉-2 小时前
下一个排列:从字典序到原地算法的完整推导
数据结构·c++·windows·算法
IronMurphy2 小时前
【算法五十】62. 不同路径
算法
Royzst2 小时前
xml知识点
java·服务器·前端
影寂ldy3 小时前
C#一维数组
算法
枕星而眠3 小时前
数据结构八大排序详解(一):四大简单排序
c语言·数据结构·c++·后端
鱼鳞_3 小时前
苍穹外卖-Day08(缓存套餐)
java·redis·缓存
过期动态3 小时前
【LeetCode 热题 100】移动零
java·数据结构·算法·leetcode·职场和发展·rabbitmq