刷题记录(3)

文章介绍了五个编程挑战题目,涉及无向图的点对统计、数学组合的元组计数、容器盛水问题、分数优化和洪水控制策略,展示了并查集、优先队列和搜索算法在解决这些问题中的应用。
摘要由CSDN通过智能技术生成


2316.统计无向图中无法相互到达点对数

题目:
给你一个整数 n ,表示一张 无向图 中有 n 个节点,编号为 0 到 n - 1 。同时给你一个二维整数数组 edges ,其中 edges[i] = [ai, bi] 表示节点 ai 和 bi 之间有一条 无向 边。

请你返回 无法互相到达 的不同 点对数目 。
思路:
很容易看出目的考察并查集,由于要求返回无法相互到达的顶点的对数,所以再给并查集多添加一个变量size,方便返回各树中有多少元素(在根节点下标存即可)
题解:

class DisjSet{
private:
    vector<int> parents;
    vector<int> size;
public:
    DisjSet(int n):parents(n),size(n,1){
        iota(parents.begin(),parents.end(),0);
    }
    int Find(int x){return parents[x]==x?x:(parents[x]=Find(parents[x]));}
    void merge(int i,int j){
        int x=Find(i),y=Find(j);
        if(x==y)return ;
        parents[x]=y;
        size[y]+=size[x];
    }
    int getSize(int x){return size[x];}
};
class Solution { 
public:
    long long countPairs(int n, vector<vector<int>>& edges) {
        DisjSet dj(n);
        for(int i=0;i<edges.size();i++){
            dj.merge(edges[i][0],edges[i][1]);
        }
        long long sum=0;
        for(int i=0;i<n;i++){
            sum+=n-dj.getSize(dj.Find(i));
        }
        return sum/2;
    }
};

心得:
1)第一次实现并查集代码,很多参考了别人的,受益匪浅- -
2)此题merge时要注意如果两个元素已经在同一集合中,那么不再对其合并。无向边的性质原因,顶点前后调换不影响其边的表示,但本人的代码段默认是前者的将后者作为父节点,因此可能在size中出现点问题;
3)复习了C++的构造函数的一些使用方法,顺便学会了使用iota()函数递增赋值;
4)Find()函数使用了路径压缩的方法,将每一个元素都直接连接到其父节点上,且精简为了一行代码,很有意思


1726.同积元组

题目:
给你一个由 不同 正整数组成的数组 nums ,请你返回满足 a * b = c * d 的元组 (a, b, c, d) 的数量。其中 a、b、c 和 d 都是 nums 中的元素,且 a != b != c != d 。
思路:
首先找四个数然后*8是显而易见的。一开始我想的是排序后双指针前后遍历一下,因为abcd各不相同所以必然是两边乘积等于中间的,但是具体实现有些阻碍遂放弃。同样,由于abcd各不相同,所以乘积的两个乘数组也不会重复,直接两层循环将所有两数乘积哈希记录,然后用数学的思想排列组合一下即可。
题解:

class Solution {
public:
    int tupleSameProduct(vector<int>& nums) {
        int sum=0,n=nums.size();
        unordered_map<int,int> m;
        for(int i=0;i<n;i++){
            for(int j=i+1;j<n;j++){
                m[nums[i]*nums[j]]++;
            }
        }
        for(auto &[k,v]:m){
            sum+=v*(v-1)*4;
        }
        return sum;
    }
};

心得:
1)主要还是思路问题,转换为数学题就容易的多了;
2)学到了map遍历值的for方法:&[key , value]。


11.盛最多水的容器

题目:
给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。

找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

返回容器可以储存的最大水量。

说明:你不能倾斜容器。
思路:
起初思路是对于线 i 而言,遍历0~i-1计算保存面积最大值,如果遇到 i 前第一条大于或等于 i 的长度的线,那么就跳出循环(因为此时 i 作为短边,容器最多也就只能容纳那么多水,因为是第一条大于它的所以面积的“长”此时也是最长的),最后返回存的maxx,此种方法见以下的第一条代码段。
看了题解于是学到了双指针的办法,两边取边,谁短移谁。此种见以下第二条代码段。
题解:
贴一下原始版本和双指针版本:

/*原始版本*/
class Solution {
public:
    int maxArea(vector<int>& height) {
        int maxx=0;
        for(int i=1;i<height.size();i++){
            for(int j=0;j<i;j++){
                maxx=max(maxx,(i-j)*min(height[j],height[i]));
                if(height[j]>=height[i])break;
            }
        }
        return maxx;
    }
};
class Solution {
public:
    int maxArea(vector<int>& height) {
        int maxx=0,i=0,j=height.size()-1;
        while(i!=j){
            int minn=min(height[i],height[j]);
            maxx=max(maxx,(j-i)*minn);
            if(minn==height[i])i++;
            else j--;
        }
        return maxx;
    }
};

心得:
1)学到了新的解题思路,存水问题也有变种,后续再练一下。
2)


2530.执行k次操作后的最大分数

题目:
给你一个下标从 0 开始的整数数组 nums 和一个整数 k 。你的 起始分数 为 0 。

在一步 操作 中:

选出一个满足 0 <= i < nums.length 的下标 i ,
将你的 分数 增加 nums[i] ,并且
将 nums[i] 替换为 ceil(nums[i] / 3) 。
返回在 恰好 执行 k 次操作后,你可能获得的最大分数。

向上取整函数 ceil(val) 的结果是大于或等于 val 的最小整数。
思路:
这题没什么特别要说的,排序后改变最大值即可。
题解:

class Solution {
public:
    long long maxKelements(vector<int>& nums, int k) {
        priority_queue<int> q(nums.begin(),nums.end());
        long long sum=0,temp;
        while(k--){
            temp=q.top();
            sum+=temp;
            q.pop();
            q.push(ceil(temp/3.0));
        }
        return sum;
    }
};

心得:
1)此题主要是优先队列priority_queue的使用。起初用的是multiset,ac但是用时有点长了,于是又学了新的stl:优先队列。priority_queue是大根堆存储,降序排列。因为只能取top值且排序实现方式异于set和multiset的红黑树,速度要更快。在只需要返回最大值的情况下是优先使用的。


1488.避免洪水泛滥

题目:
你的国家有无数个湖泊,所有湖泊一开始都是空的。当第 n 个湖泊下雨前是空的,那么它就会装满水。如果第 n 个湖泊下雨前是 满的 ,这个湖泊会发生 洪水 。你的目标是避免任意一个湖泊发生洪水。

给你一个整数数组 rains ,其中:

rains[i] > 0 表示第 i 天时,第 rains[i] 个湖泊会下雨。
rains[i] == 0 表示第 i 天没有湖泊会下雨,你可以选择 一个 湖泊并 抽干 这个湖泊的水。
请返回一个数组 ans ,满足:

ans.length == rains.length
如果 rains[i] > 0 ,那么ans[i] == -1 。
如果 rains[i] == 0 ,ans[i] 是你第 i 天选择抽干的湖泊。
如果有多种可行解,请返回它们中的 任意一个 。如果没办法阻止洪水,请返回一个 空的数组 。

请注意,如果你选择抽干一个装满水的湖泊,它会变成一个空的湖泊。但如果你选择抽干一个空的湖泊,那么将无事发生。
思路:
先说起初自己的思路,见题想到了生产者-消费者问题的进程池,遂存了一个vector记录数组中0的下标,后面遇到哪个重复的了就把第一个空的分给他。但是,完全疏忽了可以抽水的天数 在 连续在同一个湖泊下雨之前的情况(抽水在下雨之前- -),之后又修补了一下但运行超时。这里超时是查找最早的那一天超时,比如(0 1 2 0 0 2)显然要在2最开始出现的 i = 2 后的 i = 3 天抽水,那么问题就是如何找到这个下标,答案也很明显了,如代码所示,用lower——bound函数,二分查找到第一个2后的0的位置。
题解:

class Solution {
public:
    vector<int> avoidFlood(vector<int>& rains) {
        int n=rains.size();
        unordered_map<int,int> m;
        vector<int> v(rains.size(),1);
        set<int> st;
        for(int i=0;i<n;i++){
            if(rains[i]==0){
                st.insert(i);
            }
            else{
                v[i]=-1;
                if(m.find(rains[i])!=m.end()){
                    auto it=st.lower_bound(m[rains[i]]);
                    if(it!=st.end()){
                        v[*it]=rains[i];
                        st.erase(it);
                    }
                    else{
                        v.clear();
                        return v;
                    }
                }
                m[rains[i]]=i;
            }
        }
        return v;
    }
};

心得:
1)主要是补漏lower_bound()的使用,详细都在思路里,不赘述了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值