第二次 leetcode周赛总结(开心!多总结,多进步)

T1:k件物品的最大和

袋子中装有一些物品,每个物品上都标记着数字 1 、0 或 -1 。

给你四个非负整数 numOnes 、numZeros 、numNegOnes 和 k 。

袋子最初包含:

  • numOnes 件标记为 1 的物品。
  • numZeroes 件标记为 0 的物品。
  • numNegOnes 件标记为 -1 的物品。

现计划从这些物品中恰好选出 k 件物品。返回所有可行方案中,物品上所标记数字之和的最大值。

解:

1.关键:

(1)贪心,“能选1不选0,能选0不选-1”

2.代码:

class Solution {
public:
    int kItemsWithMaximumSum(int numOnes, int numZeros, int numNegOnes, int k) {
        //贪心:
        if(k<=numOnes)
        {
            return k;
        }
        else if(k<=numOnes + numZeros)
        {
            return numOnes;
        }
        else
        {
            return numOnes - (k-numOnes-numZeros);
        }
    }
};

T2:质数减法运算

给你一个下标从 0 开始的整数数组 nums ,数组长度为 n 。

你可以执行无限次下述运算:

  • 选择一个之前未选过的下标 i ,并选择一个 严格小于 nums[i] 的质数 p ,从 nums[i] 中减去 p 。

如果你能通过上述运算使得 nums 成为严格递增数组,则返回 true ;否则返回 false 。

严格递增数组 中的每个元素都严格大于其前面的元素

解:

1.关键:

(1)核心:“贪心算法” + 从后往前遍历 ,

(2)最后一个元素 nums[size-1] 不要动它!

(3)计算nums[i] - nums[i+1] = diff  得到 需要的 j > diff ,为了尽可能的 贪心,j应该从2开始去找尽可能小的 素数 ,然后nums[i] - j 得到更新后的 “最贪心的nums[i]”

2.代码:

class Solution {
public:
    bool primeSubOperation(vector<int>& nums) {
        //特殊 :
        //贪心即可 ,建议从后往前遍历 ,最后一个元素尽可能的大 ,
        //前一个元素nums[i] 只要达到 < nums[i+1]的 尽可能大的 数值就可以了
        int size = nums.size(); //最后一个元素 不要动它
        //特殊:
        if(size == 1)
        {
            return true;
        }
        for(int i=size-2;i>=0 ;i--)
        {
            //让nums[i] - 一个素数p, 注意 , 这个操作只能实行1次!!!
            if(nums[i] < nums[i+1])
            {
                //不用执行,
                continue;
            }
            else
            {
                //需要减去一个 尽可能小的素数
                int diff = nums[i] - nums[i+1];
                //找到一个 p > diff 刚好成立
                for(int j=2;j<nums[i];j++)
                {
                    if(is_prime(j) && j>diff )
                    {
                        //i 是一个素数 ,且 i>diff
                        nums[i] -=j;
                        break; //此时 nums[i] < nums[i+1]
                    }
                }
                if(nums[i] < nums[i+1])
                {
                    continue;
                }
                return false; //没有找到呗
            }
        }
        return true;
    }
    //--辅助函数 , 判断一个 num是否为素数
    bool is_prime(int num)
    {
        for(int i=2;i<=sqrt(double(num)); i++)
        {
            if(num%i == 0)
            {
                return false;
            }
        }
        return true;
    }
};

T3:使数组元素全部相等的最少操作次数

给你一个正整数数组 nums 。

同时给你一个长度为 m 的整数数组 queries 。第 i 个查询中,你需要将 nums 中所有元素变成 queries[i] 。你可以执行以下操作 任意 次:

  • 将数组里一个元素 增大 或者 减小 1 。

请你返回一个长度为 m 的数组 answer ,其中 answer[i]是将 nums 中所有元素变成 queries[i] 的 最少 操作次数。

注意,每次查询后,数组变回最开始的值。

解:

法一:

1.关键:

(1)这个题 :看上去 暴力求解似乎很简单 ,但是 只能通过21个测试用例就会超时

(2)我当时 采用了3种方式进行优化:

<1>利用一个map1<queries[i],cnt>  记录已经计算过的 queries[i]需要计算的 次数

<2>对nums数组进行排序,虽然不知道这个排序怎么用

<3>分别 计算max_cnt 和 min_cnt  对  “全部变成数组最大值 和 最小值”需要的cnt,

这么做到好处是 :之后 只要有queries[i ]比max_num大 或者 比 min_num小可以直接利用公式计算得到所需的次数

2.代码:(超时,只通过了24个测试用例)

class Solution {
public:
    vector<long long> minOperations(vector<int>& nums, vector<int>& queries) {
        //(1)排序:然后,优化,利用set找lower_bound
        sort(nums.begin(),nums.end());
        
        int size_nums = nums.size();
        int m = queries.size();
        vector<long long> ans(m,0);
        unordered_map<long long,long long> map1;
        
        //(2)queries[0] 和 queries[1] 手算一次
        //map1[queries[i]] = cnt //记录已经走访过的 次数
        long long sum = 0;
        long long sum_min = 0;
        long long sum_max = 0;
        
        long long  max_num = nums[size_nums-1];
        long long  min_num = nums[0]; //得到最大最小值
        
        for(int j=0;j<=size_nums-1;j++)
        {
            sum+= abs(queries[0] - nums[j]);
            sum_min +=abs(min_num - nums[j]);
            sum_max += abs(max_num - nums[j]);
        }
        ans[0] = sum;
        map1[queries[0]] = ans[0];
        //特殊:
        if(m == 1)
        {
            return ans;
        }
        //--一般:
        long long lower_b = queries[0];
        long long up_b = queries[0]; //分别 作为 计算差值的 下限 和 上限
        //只要是 queries[i] > lower_b 的, 所有 <=lower_b的nums[0-i]都可以  --不过 似乎 并不可行 ,除非 
        
        
        
        //这个简单啊,只要求abs(queries[i] - nums[j])之和即可
        //尼玛的 ,我就直到可能会超时:
        //应该和 queries[i] 和 queries[i+1]之间有某种联系
        
        for(int i=1;i<m;i++)
        {
            long long sum=0;
            //case1:queries[i] 已经计算过了
            if(map1.count(queries[i]))
            {
                ans[i] = map1[queries[i]];
                continue;
            }
            //case2:这个queries 比max_num还要大 -- 可以直接得到
            else if(queries[i] >= max_num)
            {
                ans[i] = sum_max + size_nums*(queries[i] - max_num);
                map1[queries[i]] = ans[i];
                continue;
            }
            //case3:这个queries 比min_num还要小--中间的就没办法了
            else if(queries[i] <= min_num)
            {
                ans[i] = sum_min + size_nums*(min_num - queries[i]);
                map1[queries[i]] = ans[i];
                continue;
            }
            for(int j=0;j<=size_nums-1;j++) //如果是 中间值 就只能逐个计算了
            {
                sum+= abs(queries[i] - nums[j]);
            }
            ans[i] = sum;
        }
        return ans;
    }
};

法二: 借鉴讨论区的思路:

1.关键:

(1)其实,我比赛的时候 也想过这种类似的思路 ,但是 !!!没有熟练掌握 “前缀和”数组这个东西,所以。。。

(2)这个公式 ,其实和 我上面那个第3中优化的计算方式的思路是一直的,主要是找上下界

(3)最核心的一点,就是 “二分思想”,排序之后 ,一边全部小于等于 queries[i] ,另一边全部大于queries[i],所以可以直接利用 公式进行计算 (一边全部 加法 , 另一边全部减法)

1.5:发布题解的思路总结:

1.思路:

- (1)先排序

- (2)前缀和数组num_sum[i] :含义--从nums[0]一直求和到nums[i]

- (3)遍历queries[k]数组,可以利用二分查找在nums数组中找到“刚好大于”queries[k]的那个元素nums[i],

这样,nums[0]-nums[i]均小于等于queries[k]  作为part1

     nums[i+1] - nums[size-1] 均大于queries[k]  作为part2

然后,part1都是“加上一个数”变成的 queries[k] , part2都是“减去一个数”变成的queries[k],

注意:我的代码中初始num_sum加入了一个0,所以相当于从下标1开始用

- cnt_part1 = i*queries[k] - num_sum[i];

- cnt_part2 = (num_sum.back() - num_sum[i]) - (size - i)*queries[k];

总的来说,可以得到这个公式

- ans[i](第i个数需要的操作数) = cnt_part1 + cnt_part2

2.代码:

class Solution {
public:
    vector<long long> minOperations(vector<int>& nums, vector<int>& queries) {
        //(1)排序:
        sort(nums.begin() , nums.end());
        //(2)前缀和数组
        vector<long long> num_sum={0};
        for(int num: nums)
        {
            num_sum.push_back(num+ num_sum.back());
        }
        //(3)二分 计算找到位置,计算ans
        vector<long long> ans;
        //遍历queries
        for(long long querie:queries)
        {
            //第一步:找到第一个大于querie的元素 的下标
            //底层实现--二分查找!!
            //查找[first, last)区域中第一个大于 val 的元素。
            int i = upper_bound(nums.begin(),nums.end(),querie) - nums.begin();
            //公式计算:
            ans.push_back(i*querie - num_sum[i] + (num_sum.back() - num_sum[i])-(nums.size() - i)*querie);
        }
        return ans;

    }
};

T4:收集 树中的 全部金币

给你一个 n 个节点的无向无根树,节点编号从 0 到 n - 1 。给你整数 n 和一个长度为 n - 1 的二维整数数组 edges ,其中 edges[i] = [ai, bi] 表示树中节点 ai 和 bi 之间有一条边。再给你一个长度为 n 的数组 coins ,其中 coins[i] 可能为 0 也可能为 1 ,1 表示节点 i 处有一个金币。

一开始,你需要选择树中任意一个节点出发。你可以执行下述操作任意次:

  • 收集距离当前节点距离为 2 以内的所有金币,或者
  • 移动到树中一个相邻节点。

你需要收集树中所有的金币,并且回到出发节点,请你返回最少经过的边数。

如果你多次经过一条边,每一次经过都会给答案加一。

解:

先延申学习一下 拓扑排序相关的 题目: 这里 引用 一个csdn博主的总结,并自主去刷这些练习题

207 Course schedule                                                  √
210 course schedule 2                                                √   
269 Alien dictionary                                                     ×
329 longest increasing path in a matrix                      ×
444 sequence reconstruction                                      √
1203 sort items by groups respecting dependencies  ×      、、困难题--先放弃,先理解拓扑的思想

拓扑排序无关DFS/BFS 只要能做出来就行

都是高频题目
遇到这种题目,想一下:什么数据结构用的比较多?什么样的手法(技巧)用的比较多?有哪些需要 注意的点?
数据结构:HashMap(HashSet)用的比较多 经常用在图的存取和维护上(有向图就存一次,无向图存两个?)
这种题目的技巧就是:先存图,然后干该干的

技巧:
如何检测图中的环
用出度入度数组来控制一些东西(BFS)
用viisted来检测之前是不是走过(DFS)
图的表示可以用hashmap也可以用邻接矩阵来做
处度入度可以用数组或者用Hahsmap来做

总结:
对于题目中需要有排序的 比如说谁在谁前面我们知道 现在我们想要一条valid的路径什么什么之类的 大致上就是拓扑排序了,拓扑排序的话,用BFS DFS都可以 注意在此过程中需要维护的东西但是同时也要注意很多corner case,而且最开始的话 一定要想清楚应该如何去做,要有一个清醒的认识 corner case要敏感。总之这类题很考验一个人是不是有清醒的头脑和把一个复杂问题进行分析 并用代码实现,而且很好的处理那些corner case的问题。
————————————————
版权声明:本文为CSDN博主「Tech In Pieces」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_44337445/article/details/110215057

……………………………………

leetcode 207课程表:

你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。

在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程  bi 。

  • 例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1 。

请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。

解:

法一:(纯靠个人 独立思考 , 虽然只通过了47个测试用例,逻辑上还有一些问题,但是收获很大)

1.关键:

(1)利用一个map1<ai,cnt_restriction> 记录 对于 课程ai的 先修课程的 “当前限制数目”

(2)如果 map1[pre[i][1]] == 0,说明这个课程的限制 又少了一个 map1[pre[i][0]] --;

如果    map1[pre[i][1]] == 0 ,说明这个限制 可以 从pre中被拿掉了,erase掉这个位置的迭代器

2.代码:

class Solution {
public:
    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
        //判断是否 可以 完成所有课程的学习
        //bfs广度优先搜索一波 , 走起(虽然深搜也可以)
        //(1)set容器记录所有已经学过的课程--能学都学一遍
        //(2)初始把不在bi中的课程全部学了
        //(3)一门课 可能有多个 先修课程!!!
        //(4)一个条件一旦满足 就从这个数组数组pre中删除掉
        //(5)一个非常绝妙的想法,map<ai,cnt_restriction>
        //每次得到一个bi 只要不在map中就相当于是 学过了这门课程
        //遍历一次pre,如果bi不在,就把这个元素erase掉
        //同时对应map[ai]--,
        //直接开while(1)循环,如果pre的size没有变化--直接false
        //如果size==0,true
        //tmd,要想学习课程5 必须先学习课程5 ,你。。你不想让我们学就直说,好吗?
        unordered_map<int,int> map1;
        int size_raw = prerequisites.size();
        for(int i=0;i<=size_raw-1;i++)
        {
            //tmd的这种测试用例:
            if(prerequisites[i][0] == prerequisites[i][1])
            {
                return false;
            }
            map1[prerequisites[i][0]]++; //记录课程ai 对应的先驱课程数量
        }
        //特殊:
        //开启while(1)循环
        while(1)
        {
            //遍历pre数组
            int size1 = prerequisites.size();
            //出口:
            if(size1 == 1 || size1 == 0) //1个限制相当于没有限制
            {
                return true;
            }
            //遍历
            vector<vector<int>> pre_tmp = prerequisites; //遍历pre_tmp
            for(int i=0;i<=size1-1;i++)
            {
                //?怎么让map1[ai]--呢?
                //怎么直到pre[i][1]这个条件被满足了呢?
                if(map1[pre_tmp[i][1]] == 0)
                {
                    //cout<<"i= "<<i<<endl; //这里有问题
                    map1[pre_tmp[i][0]]--;
                }
                
            }
            int cnt = 0; //记录erase相差步数!!
            for(int i=0;i<=size1-1;i++)
            {
                //--
                //cout<<"i = "<<i<<endl;
                //cout<<pre_tmp[i][0] <<"  :map1[pre_tmp[i][0]]"<<map1[pre_tmp[i][0]]<<endl;
                if(map1[pre_tmp[i][1]] == 0) //有可能是这里有问题
                {
                    //拿到不是因为map1[bi]已经 == 0了吗????????????
                    //cout<<"来过吗"<<endl;
                    //按道理来说 ,{1,4} 和 {2,4}这两对已经被满足了,map1[1] ==0 
                    //也就是说
                    //说明这个ai的限制bi都消去了,从而可以让这个ai干掉
                    //cout<<"i = "<<i<<endl;
                    cout<<i-cnt<<endl; //为什么是2呢?我尼玛
                    prerequisites.erase(prerequisites.begin() + i -cnt);
                    cnt++; //干掉的个数 ,同时 i是递增向后的
                    //我去,干掉的位置不对!!!! i==2 和 i== 3进来的
                    
                }
            }
            int size2 = prerequisites.size();
            if(size2 == size1) return false;
            //我认为这里就是最终的问题所在地
        }
    }
    
};

法二:参考讨论区的 一些思路

借鉴 那位 讲拓扑排序讲的很好:(总的来说 就是 记录入度,然后 每次去寻找入度==0的课程)

每次只能选你能上的课
每次只能选入度为 0 的课,因为它不依赖别的课,是当下你能上的课。
假设选了 0,课 3 的先修课少了一门,入度由 2 变 1。
接着选 1,导致课 3 的入度变 0,课 4 的入度由 2 变 1。
接着选 2,导致课 4 的入度变 0。
现在,课 3 和课 4 的入度为 0。继续选入度为 0 的课……直到选不到入度为 0 的课。
这很像 BFS
让入度为 0 的课入列,它们是能直接选的课。
然后逐个出列,出列代表着课被选,需要减小相关课的入度。
如果相关课的入度新变为 0,安排它入列、再出列……直到没有入度为 0 的课可入列。
BFS 前的准备工作
每门课的入度需要被记录,我们关心入度值的变化。
课程之间的依赖关系也要被记录,我们关心选当前课会减小哪些课的入度。
因此我们需要选择合适的数据结构,去存这些数据:
入度数组:课号 0 到 n - 1 作为索引,通过遍历先决条件表求出对应的初始入度。
邻接表:用哈希表记录依赖关系(也可以用二维矩阵,但有点大)
key:课号
value:依赖这门课的后续课(数组)
怎么判断能否修完所有课?
BFS 结束时,如果仍有课的入度不为 0,无法被选,完成不了所有课。否则,能找到一种顺序把所有课上完。
或者:用一个变量 count 记录入列的顶点个数,最后判断 count 是否等于总课程数。

作者:笨猪爆破组
链接:https://leetcode.cn/problems/course-schedule/solutions/250377/bao-mu-shi-ti-jie-shou-ba-shou-da-tong-tuo-bu-pai-/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

解:(自己的思路总结)

1.关键:(相当于是 一个 bfs深搜的 思路)

(1)进行bfs需要用到的 4个数组结构:

<1>vector<int> enter_degree,记录当前所有 课程的 入度! 需要在update函数中进行更新

<2>unordered_map<int,vector<int>> map1 记录 受到class_bi 牵制的 所有 后续其它课程,用一个vector存储后续这些课程

<3>unordered_set<int> Set1,记录已经入队过的 课程

<4>queue<int> Que1 ,存储当前 入度==0 的所有课程

(2)cnt记录已经入队 过 的 课程的数量

(3)bfs函数: 

<1>先让开始所有可以入队的元素全部入队,

<2>让后!empty的while循环

(4)update函数:

<1>先将 Que1队列中的 课程依次取出,同时 让所有受到这些课程限制的 课程i的 vec[i]--

<2>遍历依次 vec ,找出那些最新的 变成 degree == 0 的课程(其实这里可以用2个队列的思想进行优化,算了,之后再说)

2.代码:

class Solution {
public:
    int cnt = 0; //记录已经遍历的 课程的数量
    unordered_set<int> Set1;
    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
        //直接利用 bfs进行求解 , 每次找到那种 入度为0的课程
        //(1)需要的做好的准备工作:
        vector<int> enter_degree(numCourses,0); //记录 0-n-1号课程的入度
        unordered_map<int,vector<int>> map1;
        //选修课程 ai 必须先 选修课程 bi 所以 bi就是ai入度条件
        int size_p = prerequisites.size();
        for(int i=0;i<=size_p - 1;i++)
        {
            //如果 记得 给下标为ai的课程的入度 加1
            enter_degree[prerequisites[i][0]]++;
            map1[prerequisites[i][1]].push_back(prerequisites[i][0]);
        }
        //现在enter_degree数组中已经记录 0 -n-1号课程的 所有 入度
        //利用一个map<class1,vector<依赖于class1的所有课程>>
        bfs(enter_degree,map1);
        if(cnt == numCourses)
        {
            return true;
        }
        return false;

    }
    void bfs(vector<int>& vec,unordered_map<int,vector<int>>& map1)
    {
        //设置一个队列,记录当前 入度 == 0 的课程
        //(0)初始:将所有入度 == 0 的课程全部入队,每次入队 cnt++
        queue<int> Que1;
        int n = vec.size();
        for(int i=0;i<=n-1;i++)
        {
            if(vec[i] == 0)
            {
                Que1.push(i); //找到一个入度 == 0的课程
                //利用set记录已经入队过的 课程
                Set1.insert(i);
                cnt++; //记录访问过的 课程
            }
        }
        //(1)while循环遍历
        while(!Que1.empty())
        {
            //直到队列为空
            update(Que1,vec,map1); // 更新队列
        }
    }
    void update(queue<int>& Que1,vector<int>& vec,unordered_map<int,vector<int>>& map1)
    {
        //--每次从Que1中取出所有元素,作为这一层 遍历的 课程
        //然后 , 让所有 受到这一门课程约束的 其它后续课程的vec入度减1
        //最后 , 再次遍历 vec得到新的入度为0的课程入队同时cnt++
        while(!Que1.empty())
        {
            int class_bi = Que1.front();
            Que1.pop();
            //利用map1
            int size_tmp = map1[class_bi].size();
            for(int i=0;i<=size_tmp-1;i++)
            {
                vec[map1[class_bi][i]]--; //入度减1
            }
        }
        //遍历vec,入队,cnt++
        //利用 一个 set记录已经入队过的 课程
        int n = vec.size();
        for(int i=0;i<=n-1;i++)
        {
            if(vec[i] == 0 && Set1.count(i)==0)
            {
                Set1.insert(i); // 记录已经入队的元素
                Que1.push(i);
                cnt++; //访问过的 课程++
            }
        }
    }
};

……………………………………………………

leetcode210 课程表II

现在你总共有 numCourses 门课需要选,记为 0 到 numCourses - 1。给你一个数组 prerequisites ,其中 prerequisites[i] = [ai, bi] ,表示在选修课程 ai 前 必须 先选修 bi 。

  • 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示:[0,1] 。

返回你为了学完所有课程所安排的学习顺序。可能会有多个正确的顺序,你只要返回 任意一种 就可以了。如果不可能完成所有课程,返回 一个空数组 。

解:

1.关键:

(1)这一题 不过是 在 上一题的 基础上 多了一个ans数组记录 加入 队列 次序罢了

代码中多出的 3句 用!!!!!标记出来了

2.代码:

class Solution {
public:
    int cnt = 0; //记录已经遍历的 课程的数量
    unordered_set<int> Set1;
    vector<int> ans; //最终的 入队选修 次序 //!!!!!!!!!!!!!!!!!!
    vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
        //直接利用 bfs进行求解 , 每次找到那种 入度为0的课程
        //(1)需要的做好的准备工作:
        vector<int> enter_degree(numCourses,0); //记录 0-n-1号课程的入度
        unordered_map<int,vector<int>> map1;
        //选修课程 ai 必须先 选修课程 bi 所以 bi就是ai入度条件
        int size_p = prerequisites.size();
        for(int i=0;i<=size_p - 1;i++)
        {
            //如果 记得 给下标为ai的课程的入度 加1
            enter_degree[prerequisites[i][0]]++;
            map1[prerequisites[i][1]].push_back(prerequisites[i][0]);
        }
        //现在enter_degree数组中已经记录 0 -n-1号课程的 所有 入度
        //利用一个map<class1,vector<依赖于class1的所有课程>>
        bfs(enter_degree,map1);
        if(cnt == numCourses)
        {
            return ans;
        }
        return {}; //不能完成课程,返回 空数组

    }
    void bfs(vector<int>& vec,unordered_map<int,vector<int>>& map1)
    {
        //设置一个队列,记录当前 入度 == 0 的课程
        //(0)初始:将所有入度 == 0 的课程全部入队,每次入队 cnt++
        queue<int> Que1;
        int n = vec.size();
        for(int i=0;i<=n-1;i++)
        {
            if(vec[i] == 0)
            {
                Que1.push(i); //找到一个入度 == 0的课程
                //利用set记录已经入队过的 课程
                Set1.insert(i);
                cnt++; //记录访问过的 课程
                ans.push_back(i); //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
            }
        }
        //(1)while循环遍历
        while(!Que1.empty())
        {
            //直到队列为空
            update(Que1,vec,map1); // 更新队列
        }
    }
    void update(queue<int>& Que1,vector<int>& vec,unordered_map<int,vector<int>>& map1)
    {
        //--每次从Que1中取出所有元素,作为这一层 遍历的 课程
        //然后 , 让所有 受到这一门课程约束的 其它后续课程的vec入度减1
        //最后 , 再次遍历 vec得到新的入度为0的课程入队同时cnt++
        while(!Que1.empty())
        {
            int class_bi = Que1.front();
            Que1.pop();
            //利用map1
            int size_tmp = map1[class_bi].size();
            for(int i=0;i<=size_tmp-1;i++)
            {
                vec[map1[class_bi][i]]--; //入度减1
            }
        }
        //遍历vec,入队,cnt++
        //利用 一个 set记录已经入队过的 课程
        int n = vec.size();
        for(int i=0;i<=n-1;i++)
        {
            if(vec[i] == 0 && Set1.count(i)==0)
            {
                Set1.insert(i); // 记录已经入队的元素
                Que1.push(i);
                cnt++; //访问过的 课程++
                ans.push_back(i);  //!!!!!!!!!!!!!!!!!!!!!!!!!!!!
            }
        }
    }
};

………………………………………………………………

T269 火星字典 : (根据给出的 string的 字典 , 对 出现过的 字母进行排序)

现有一种使用英语字母的火星语言,这门语言的字母顺序与英语顺序不同。

给你一个字符串列表 words ,作为这门语言的词典,words 中的字符串已经 按这门新语言的字母顺序进行了排序 。

请你根据该词典还原出此语言中已知的字母顺序,并 按字母递增顺序 排列。若不存在合法字母顺序,返回 "" 。若存在多种可能的合法字母顺序,返回其中 任意一种 顺序即可。

字符串 s 字典顺序小于 字符串 t 有两种情况:

  • 在第一个不同字母处,如果 s 中的字母在这门外星语言的字母顺序中位于 t 中字母之前,那么 s 的字典顺序小于 t 。
  • 如果前面 min(s.length, t.length) 字母都相同,那么 s.length < t.length 时,s 的字典顺序也小于 t 。

解:

1.一些想法:

然而, 并不能直接上手

class Solution {

public:

    string alienOrder(vector<string>& words) {

        //这是一个很明显的 拓扑排序的问题 , 然而 转化为 代码的语言并不是那么容易

        //(1)最朴素的 想法 就是 利用 双层循环,+ 一个map记录 任意 2个元素的大小关系

        //双层循环: 从第一个string 依次和 后面的 string进行比较

        //既然是 字典的话,好吧,还是有可能有相同的 string的

        //这个map就是 之前那个 前置map<key, vector< int > > 所有 受到key牵制的课程

        //一个set容器,

    }

};

那就 先pass好了!!!!!!!!!!!!!!!!!!!!!!!!!!

………………………………………………………………………………………………

T444:序列重建

给定一个长度为 n 的整数数组 nums ,其中 nums 是范围为 [1,n] 的整数的排列。还提供了一个 2D 整数数组 sequences ,其中 sequences[i] 是 nums 的子序列。
检查 nums 是否是唯一的最短 超序列 。最短 超序列 是 长度最短 的序列,并且所有序列 sequences[i] 都是它的子序列。对于给定的数组 sequences ,可能存在多个有效的 超序列 。

  • 例如,对于 sequences = [[1,2],[1,3]] ,有两个最短的 超序列 ,[1,2,3] 和 [1,3,2] 。
  • 而对于 sequences = [[1,2],[1,3],[1,2,3]] ,唯一可能的最短 超序列 是 [1,2,3] 。[1,2,3,4] 是可能的超序列,但不是最短的。

如果 nums 是序列的唯一最短 超序列 ,则返回 true ,否则返回 false 。
子序列 是一个可以通过从另一个序列中删除一些元素或不删除任何元素,而不改变其余元素的顺序的序列。

解:

1.关键:

(1)依然是 考虑 广度优先搜索 bfs的思想

(2)这里的 数据容器 是借鉴的 答案的 思想: 非常巧妙!

<1>vector<int> vec1(n+1) : 只是用 下标1-n存储 1-n号元素当前被牵制的 数量(也就是 入度的 大小)

<2>vector<unordered_set<int>> vec2(n+1): 只使用 下标 1-n,用于存放 对应 数字 可以 牵制其它所有的数字,都存放到一个set容器中

(3)初始操作:遍历一次 二维数组 sequences, 然后 得到 vec1 和 vec2的初始条件

(4)设置一个队列(bfs的标配):queue<int>,然后初始 找出 所有 入度为0的元素 ---入队

(5)开启 while循环 广搜:

<1>先判断 队列中的元素 个数 是否 大于1 , 若是 ,直接false

<2>取出 front元素 num,然后 出队

<3>注意:下一次只有被 num牵制的 元素 入度 有可能 减少为0

!!!所以 ,只要遍历 vec2[num] 这个set容器即可

2.这么做的合理性:

(1)一旦 那一次 有>1个元素的入度==0 , 就会造成 拓扑排序产生多种可能

(2)注意:给出的nums序列一定是一种可能的 “超序列”, 所以,只要证明 构造拓扑排序的 “唯一性”即可

3.代码:

class Solution {
public:
    bool sequenceReconstruction(vector<int>& nums, vector<vector<int>>& sequences) {
        //自己构造一个最短超序列,然后和nums进行比较
        //1.先考虑特殊情况:
        int n = nums.size();//原来nums的长度就是n,就是1-n的一种排列
        //如果都为空,false
        //如果有 不在1-n范围的 数字--false
        //如果有1-n中的数字没有出现--false
        //算了,先考虑一般case
        //先不考虑是否唯一,只要是 这些子序列的 最短超序列就完事了
        //好了,思路已经有了:
        //就只需要利用 拓扑排序 , 
        //<1>容器vec(n+1,0):记录数字1-n的 入度
        //<2>map<key,vec<int>>:记录所有 被数字key约束的 下一个数组,存储到一个vec中
        //通过bfs广搜,每次找到 入度为0的而且没有在set中的节点 ,
        //如果有 多个 -- 直接false , 否则去找下一个,cnt == n 才能返回true
        //而且数字必须要 1-n 的范围之内
        //vec需要更新,但是 map不需要更新
        vector<int> vec1(n+1,0);
        vector<unordered_set<int>> vec2(n+1);
        //unordered_map<int,vector<int>> map1;
        //先不要管 这个 nums序列,先直接看sequences数组中的 那些子序列
        int size_i = sequences.size();
        //我觉得官方题解 用到 2个数据容器非常巧妙:
        //一个indegree数组vec用于存放1-n数字的入度
        //一个vec<set<int>>(n+1)容器,用于存放1-n号元素牵制的后续元素
        //初始条件,得到vec1 和 vec2 的初始情况
        for(auto sequen: sequences)
        {
            for(int i=1;i<sequen.size();i++)
            {
                int prev = sequen[i-1];
                int next = sequen[i];
                //next元素 受到 prev元素的 牵制
                if(!vec2[prev].count(next))
                {
                    vec2[prev].insert(next); //prev元素 可以 牵制next元素
                    vec1[next]++; //next元素受到的 牵制 ++
                }
            }
        }
        //开始进行bfs广搜
        queue<int> Que1; // 队列--将入度为0的元素入队
        for(int i =1 ;i<=n;i++)
        {
            if(vec1[i] == 0)
            {
                Que1.push(i);
            }
        }
        //循环广搜, 一旦queue中有多个元素 ,直接false
        //先判断队列中的元素是否>1 , 
        //然后 取出那个元素,
        //最后遍历vec2[front]就是队列开头元素 牵制的所有其它元素,
        //只有它们的 vec1 才可能在--之后变成0
        while(!Que1.empty())
        {
            //(1)判断是否有 多个 元素
            if(Que1.size() > 1)
            {
                return false;
            }
            //(2)取出 front元素
            int num = Que1.front();
            Que1.pop();
            //(3)从否遍历 vec2[num]
            for(auto item:vec2[num])
            {
                vec1[item]--;
                if(vec1[item] == 0)
                {
                    Que1.push(item);
                }
            }
        }
        return true;
    }   
};

…………………………………………………………………………………………

好了,“言归正传”,我们还是回到 那个 收集树中的金币 这个问题上面来:

解:(几乎都是借鉴的 那个 艾府的解答)

1.关键:

(1)数据结构选择:

         vec1<vector<int>> : 一个二维数组,用于存放 一个节点所 连接的所有 其它节点

         vec2<int> : 一个一维数组, 用于存放0-n-1号节点的 度数

(2)通过遍历egdes这个二维数组--进行“构图”-构建一个无向图:

<1>更新每个节点的 度数

<2>构建 “邻接表”--记录每个节点 相邻的节点

(3)利用 拓扑排序 进行 “剪枝”操作--去掉那些 没有 金币的叶子节点:

好吧,本质上就是 让那些和叶子节点 有连接的节点的度数 “减1”,同时判断是否会 产生新的叶子新的叶子节点---需要使用到一个queue<int> 队列

(4)让所有 有金币的叶子节点 全部加入到队列中,这些节点的入队时间设置为0,当然,我们会初始化一个大小为n的一维数组time,用于记录那些没有被剪掉的节点的入队时间(反正,有金币的叶子节点的入队时间都是0)

(5)从任意一个有金币的叶子节点出发,再次进行拓扑排序,利用队列,每次遇到的节点的度数 都要"减1",从而方便下一次探索

2.代码:(我的代码好像还是 有个地方有问题,但是没找到,就先放到 “笔记”中了)

这里直接把 “艾符”的代码给出来了

class Solution {
public:
    int collectTheCoins(vector<int> &coins, vector<vector<int>> &edges) {
        int n = coins.size();
        vector<vector<int>> g(n);
        int deg[n]; memset(deg, 0, sizeof(deg));
        for (auto &e: edges) {
            int x = e[0], y = e[1];
            g[x].push_back(y);
            g[y].push_back(x); // 建图
            ++deg[x];
            ++deg[y];
        }

        // 用拓扑排序「剪枝」:去掉没有金币的子树
        queue<int> q;
        for (int i = 0; i < n; ++i)
            if (deg[i] == 1 && coins[i] == 0) // 无金币叶子
                q.push(i);
        while (!q.empty()) {
            int x = q.front();
            q.pop();
            for (int y: g[x])
                if (--deg[y] == 1 && coins[y] == 0)
                    q.push(y);
        }

        // 再次拓扑排序
        for (int i = 0; i < n; ++i)
            if (deg[i] == 1 && coins[i]) // 有金币叶子
                q.push(i);
        if (q.size() <= 1) return 0; // 至多一个有金币的叶子,直接收集
        int time[n]; memset(time, 0, sizeof(time));
        while (!q.empty()) {
            int x = q.front();
            q.pop();
            for (int y: g[x])
                if (--deg[y] == 1) {
                    time[y] = time[x] + 1; // 记录入队时间
                    q.push(y);
                }
        }

        // 统计答案
        int ans = 0;
        for (auto &e: edges)
            if (time[e[0]] >= 2 && time[e[1]] >= 2)
                ans += 2;
        return ans;
    }
};

作者:灵茶山艾府
链接:https://leetcode.cn/problems/collect-coins-in-a-tree/solutions/2191371/tuo-bu-pai-xu-ji-lu-ru-dui-shi-jian-pyth-6uli/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

总的来说:

这一次周赛比上一次有所进步,不过呢,第3题因为算法的原因,一直超时,就是那个前缀和数组的问题,第4题的话,对于“拓扑排序”这个知识点 之前没有进行系统的学习,但是,收获很多,包括第一次认识到“剪枝”的一种实现方法,前缀和数组的使用,拓扑排序问题的识别

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值