第三次周赛 总结:是的,我在进步中。。。

文章提供了四个使用贪心算法解决的编程问题,包括寻找最长平衡子字符串、构建具有不同元素的二维数组、最大化老鼠吃奶酪的得分以及确定最少翻转操作数。每个问题的解答都涉及对输入数据的遍历、分析和优化策略,如计算差值数组、利用排序和特殊边界条件处理。
摘要由CSDN通过智能技术生成

T1:最长平衡子字符串:

给你一个仅由 0 和 1 组成的二进制字符串 s 。  

如果子字符串中 所有的 0 都在 1 之前 且其中 0 的数量等于 1 的数量,则认为 s 的这个子字符串是平衡子字符串。请注意,空子字符串也视作平衡子字符串。 

返回  s 中最长的平衡子字符串长度。

子字符串是字符串中的一个连续字符序列。

解:

1.关键:

(1)主要还是 贪心的 过程

(2)遍历一次,每次记录当前的 '0' 或者 '1'

 (3)具体情况,还是要分情况讨论,具体见代码,别忘了当s[n-1] == '1' 时的边界情况即可

2.代码:

class Solution {
public:
    int findTheLongestBalancedSubstring(string s) {
        //所有'0' 都在 '1'之前, 而且 0的数量必须等于1的数量,return cnt*2
        //直接 贪心遍历即可
        int cnt_0 = 0,cnt_1 = 0 , ans = 0;
        int n = s.size();
        //--
        for(int i=0;i<n;i++)
        {
            //记录当前位置的 '0' 或者 '1'
            if(s[i] == '0')
            {
                cnt_0++;
            }
            else if(s[i]=='1')
            {
                cnt_1++;
            }
            //--然后 :处理刷新操作 和 得到ans的操作:
            //case1:if(i>0 && s[i-1] == '1' && s[i] == '0') 此时:更新ans并且刷新 cnt_0 和 cnt_1
            if(i>0 && s[i-1] == '1' && s[i] == '0')
            {
                cnt_0--;
                int num = min(cnt_0,cnt_1);
                ans = max(ans,num);
                cnt_0 = 1;
                cnt_1 = 0; //刷新
            }
        }
        //最后一个位置还是需要考虑一下的:
        if(s[n-1] == '1')
        {
            int num = min(cnt_0,cnt_1);
            ans = max(ans,num);
        }
        return ans*2;
    }
};

T2:转换为二维数组

给你一个整数数组 nums 。请你创建一个满足以下条件的二维数组:

  • 二维数组应该  包含数组 nums 中的元素。
  • 二维数组中的每一行都包含 不同 的整数。
  • 二维数组的行数应尽可能  。

返回结果数组。如果存在多种答案,则返回其中任何一种。

请注意,二维数组的每一行上可以存在不同数量的元素。

解:

1.关键:

之前没有读懂题目,其实就是 一行中每个元素都不同
直接利用一个map容器,记录每个元素nums[i] 重复的次数
好吧,本质上就是贪心算法 中的 “木桶 短板理论”,
行数 : 由重复最多的哪个元素的 重复次数决定
(1)先预处理,将答案存到二维数组ans中,用map1记录<val,cnt>,用max记录当前最大的重复次数
(2)从头 对nums数组进行遍历,并且分2中情况进行讨论
<1>这个元素nums[i]重复次数比max还要多,需要建立 新的一行
<2>否则,这个元素可以到ans[map1[nums[i]]].push_back(nums[i])中即可

2.代码:

class Solution {
public:
    vector<vector<int>> findMatrix(vector<int>& nums) {
        //我.。。,之前没有读懂题目,其实就是 一行中每个元素都不同
        //直接利用一个map容器,记录每个元素nums[i] 重复的次数
        //好吧,本质上就是贪心算法 中的 “木桶 短板理论”,
        //行数 : 由重复最多的哪个元素的 重复次数决定
        //1.预处理:
        vector<vector<int>> ans;
        unordered_map<int,int> map1;//<val,cnt> ,打擂台,找到cnt中的那个最大值
        int max = 0;
        int n= nums.size();
        //2.从头 对nums数组遍历一次
        for(int i=0;i<n;i++)
        {
            map1[nums[i]]++;
            //(1)这个元素重复次数比max还要多,需要建立 新的一行
            if(map1[nums[i]] > max) 
            {
                max = map1[nums[i]];
                vector<int> new_col;
                new_col.push_back(nums[i]); //把这个元素加入进去
                //把这个行加入到ans中
                ans.push_back(new_col);
            }
            //(2)否则,这个元素可以到ans[map1[nums[i]]].push_back(nums[i])中即可
            else
            {
                ans[map1[nums[i]]-1].push_back(nums[i]); //(次数-1)等于对应行的下标
            }
        }
        return ans;
        
    }
};

T3:老鼠 和 奶酪

有两只老鼠和 n 块不同类型的奶酪,每块奶酪都只能被其中一只老鼠吃掉。

下标为 i 处的奶酪被吃掉的得分为:

  • 如果第一只老鼠吃掉,则得分为 reward1[i] 。
  • 如果第二只老鼠吃掉,则得分为 reward2[i] 。

给你一个正整数数组 reward1 ,一个正整数数组 reward2 ,和一个非负整数 k 。

请你返回第一只老鼠恰好吃掉 k 块奶酪的情况下,最大 得分为多少。

解:

1.关键:

又是一个贪心的问题: 只要计算reward1[i] 和 reward2[i] 的差值数组diff[i]即可
(1)因为需要取k个reward1,所以diff[i] = reward1[i] - reward2[i]
(2)然后,找到diff[i]中最大的那k个, 求和sum_diff
直接对diff进行排序即可,然后从后面取到k个元素的和
(3)计算 reward2中的所有和 sum2, 最终 ans = sum2 + sum_diff
总的来说,就一点,尽可能的让第一只老鼠吃到的那k个奶酪“赚得相对于让第二只老鼠来吃 更多一些”

2.代码:

class Solution {
public:
    int miceAndCheese(vector<int>& reward1, vector<int>& reward2, int k) {
        //简单,又是一个贪心的问题: 只要计算reward1[i] 和 reward2[i] 的差值数组diff[i]即可
        //因为需要取k个reward1,所以diff[i] = reward1[i] - reward2[i]
        //然后,找到diff[i]中最大的那k个, 求和sum_diff
        //直接对diff进行排序即可,然后从后面取到k个元素的和
        //计算 reward2中的所有和 sum2, 最终 ans = sum2 + sum_diff
        
        int n = reward1.size();
        int sum_diff = 0;
        int sum2 = 0;
        vector<int> diff(n,0);
        for(int i=0;i<n;i++)
        {
            sum2+= reward2[i];
            diff[i] = reward1[i] - reward2[i];
        }
        //排序
        sort(diff.begin(),diff.end()); //从后往前求和
        for(int i=n-1;i>=(n-k);i--)
        {
            sum_diff += diff[i];
        }
        return sum2+sum_diff;
    }
};

T4:最少翻转操作数

给你一个整数 n 和一个在范围 [0, n - 1] 以内的整数 p ,它们表示一个长度为 n 且下标从 0 开始的数组 arr ,数组中除了下标为 p 处是 1 以外,其他所有数都是 0 。

同时给你一个整数数组 banned ,它包含数组中的一些位置。banned 中第 i 个位置表示 arr[banned[i]] = 0 ,题目保证 banned[i] != p 。

你可以对 arr 进行 若干次 操作。一次操作中,你选择大小为 k 的一个 子数组 ,并将它 翻转 。在任何一次翻转操作后,你都需要确保 arr 中唯一的 1 不会到达任何 banned 中的位置。换句话说,arr[banned[i]] 始终 保持 0 。

请你返回一个数组 ans ,对于 [0, n - 1] 之间的任意下标 i ,ans[i] 是将 1 放到位置 i 处的 最少 翻转操作次数,如果无法放到位置 i 处,此数为 -1 。

  • 子数组 指的是一个数组里一段连续 非空 的元素序列。
  • 对于所有的 i ,ans[i] 相互之间独立计算。
  • 将一个数组中的元素 翻转 指的是将数组中的值变成 相反顺序 。

解:

1.关键:

备注这里借鉴了 妙蛙种子 的思路,添加了一些个人的理解

(1)先考虑特殊情况,k==1时,不能跳到其它位置

(2)将banned数组转移到 一个set中,方便访问

(3)把除了p下标位置 和 ban中的下标位置,分奇偶放到2个set容器中:

之所以这么做,就是因为 每次跳跃的区间向右移动一次后,从当前的下标位置now可以达到的下一个位置now' 其实向左 移动了2个位置,所以,对于任何一个k值,先看now+k-1也就是向右最远可以跳到的下标位置时 奇数 还是 偶数,之后就只能在 奇数或者 偶数容器set中去找下一个可以到达的位置

注意:只要在 最左边可以到达的位置 和 最右边可以到达的位置之间的 所有的奇数 或者 偶数 set中的位置,从当前的now位置下一次跳跃都是 可以到达的!!!

(4)利用一个 队列容器q进行bfs 广搜:

初值:起点位置 --就是p点下标,先入队 ,-- 一下解释 bfs的while循环的具体代码的含义

<1>取出队首元素 , 并且记得出队

<2>先Left可以移动 和 向Right可以移动的 最大距离:

<3>//悟了:之所以计算x = (now+k-1)%2作为st的下标

//是因为,从now这个点可以到达的点之间 都是相隔2位的,这一点见csdn笔记

//从st[x]中找到刚好大于now+L的位置

//然后,从左 到右,然所有now可以到达的位置全部到达一次

2.具体代码如下:

class Solution {
public:
    vector<int> minReverseOperations(int n, int p, vector<int>& banned, int k) {
        //审题:
        //第一:只有下标 p 位置为1
        //第二:banned[i]这个下标位置 永远不能为1
        //第三:每一次操作,选择一个大小为k的子数组,并反转它
        //保证,每次反转后,这个唯一的“1”还是不会到达banned[i]这个下标
        //也就是说arr[banned[i]] == 0 恒成立
        //第四:返回一个一维数组vector<int> ans
        //ans[i]--的含义是 将唯一的1 移动到下标i位置需要的最少“操作次数”
        //不能在某一次移动的结果中让banned[i]位置变成1
        //如果做不到,ans[i] = -1即可
        //开始动手:
        //思路:算了,先借鉴 妙蛙种子的 思路好了
        //(1)特殊
        vector<int> ans(n,-1);
        if(k == 1)
        {
            ans[p] = 0;
            return ans;
        }
        //(2)将banned数组转移到 一个set中,方便访问
        unordered_set<int> ban;
        for(auto item:banned)
        {
            ban.insert(item);
        }
        //(3)把除了p下标位置 和 ban中的下标位置,分奇偶放到2个set容器中:
        set<int> st[2];
        for(int i=0;i<n;i++)
        {
            if(i!=p && ban.count(i) == 0)
            {
                st[i%2].insert(i);
            }
        }
        //(4)利用一个 队列容器q进行bfs 广搜:
        queue<int> q;
        ans[p] = 0; // 初值:起点位置(相当于 一个跳跃游戏)
        q.push(p);
        //while循环进行搜索 和 update
        while(!q.empty())
        {
            //<1>取出队首元素 , 并且记得出队
            int now = q.front(); q.pop();
            //<2>先Left可以移动 和 向Right可以移动的 最大距离:
            int L = max(-(k-1) , k-1 - 2*now);
            int R = min(k-1 ,(n-1)-(k-1)+(n-1)-2*now);
            //<3>
            //悟了:之所以计算x = (now+k-1)%2作为st的下标:
            //是因为,从now这个点可以到达的点之间 都是相隔2位的,这一点见csdn笔记
            int x = (now+k-1)%2;
            //从st[x]中找到刚好大于now+L的位置
            auto it =st[x].lower_bound(now+L);
            //然后,从左 到右,然所有now可以到达的位置全部到达一次
            while(it!= st[x].end())
            {
                if(*it > now+R)
                {
                    break; //已经超出可以到达的 最右边的位置了
                }
                //访问这个位置,并且更新ans,同时从st[x]中删除这个位置
                ans[*it] = ans[now] + 1;
                q.push(*it); //入队。作为之后的起点
                it = st[x].erase(it);//删除后,返回下一个位置的 迭代器
            }
        }
        return ans;
    }
};

总结part:

这一次明显在思维上有所进步,能够独立思考出 很多东西,而且我发现,如果一边嗑巴西松子一边思考,更容易 集中注意的 思考出大体的思路!非常美妙

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值