• LeetCode——Weekly Contest 318


    LeetCode周赛第318场记录

    感觉这场周赛的质量还是很高的,题目出得非常有水平,而且也学到了不少东西。

    2460. 对数组执行操作

    在这里插入图片描述
    这是本次周赛的第一题,要求将数组元素进行处理之后所有0元素移动到序列的末尾

    对数组元素进行处理的操作非常简单,只需要按照题意模拟即可。但是对于如何将数组中所有0元素全部移动到序列的尾部,并且同时保持非0元素相对位置不变(稳定性),这本身就是另一道LeetCode题了:

    LeetCode283-移动0

    这道题虽然也是一道简单题,但是它的思维难度并不低,这道题证明了上述的问题存在时间复杂度为 O ( n ) O(n) O(n),空间复杂度为 O ( 1 ) O(1) O(1)的高效算法。所以若能把移动0的做法迁移到本题中,这道题的代码性能可以达到一个最优,这是很多人的代码没有做到的:

    所以这道题的完整代码如下:

    class Solution {
    public:
        vector<int> applyOperations(vector<int>& nums) {
            int n = nums.size();
    		for(int i = 0 ; i < n - 1 ; ++i)		// 这个循环就是按照题意修改数组即可。
    		{
    			if(nums[i] == nums[i + 1])
    			{
    				nums[i] <<= 1;					
    				nums[i + 1] = 0;
    			}
    		}
    		
    		int Left = 0, Right = 0;				// 参考LeetCode283-移动零
    		while(Right < n)						// 时间复杂度O(n),空间复杂度O(1),就地算法
    		{										// 这是将所有0元素移动到数组最后的最优算法
    			if(nums[Right])
    			{
    				swap(nums[Left], nums[Right]);
    				++Left;
    			}
    			++Right;
    		}
    		
    		return nums;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    2461. 长度为 K 子数组中的最大和(滑动窗口+Hash表)

    在这里插入图片描述
    这道题在比赛时我的第一想法是用前缀和,前缀和的时间复杂度也是O(n),但是很难检测到某一个窗口中是否含有重复的数字

    所以这道题的难点不在于求和,而在于如何能快速地检测出当前窗口中是否含有重复数字,这就需要我们使用Hash表去动态地管理当前窗口中各个数字出现的次数。如果当前窗口中框住的数字不符合要求,那么这个窗口内的数值不能用来更新最大值

    完整的代码和注释如下:

    class Solution {
    public:
        long long maximumSubarraySum(vector<int>& nums, int k) {
            long long Ans = 0, Tmp = 0;
    		unordered_map<int, int> HashTable;	// 使用Hash表来检测当前窗口中是否有重复数字
    		int n = nums.size(), i = 0, j = 0;
    		
    		for(int i = 0 ; i < k ; ++i) 	// 首先充满长度为k的窗口
    		{
    			Tmp += nums[i];				// 更新临时和
    			++HashTable[nums[i]];		// 记录当前窗口中每个数字出现的次数
    		}
    		
    		if(HashTable.size() == k)		// 如果窗口大小正好等于Hash表项数,说明无重复数字
    			Ans = max(Ans, Tmp);
    			
    		/*从这里开始,窗口开始向前滑动,每一次加入一个新数字,同时排出一个新数字*/
    		for(int i = k ; i < n ; ++i)	
    		{
    			Tmp -= nums[i - k];	// 排出末尾数字,更新当前和
    			--HashTable[nums[i - k]];	// 递减Hash表中的此项计数值
    			
    			/*如果当前计数值为0,删去此项,防止对后面造成干扰*/
    			if(HashTable[nums[i - k]] == 0)	
    				HashTable.erase(nums[i - k]);
    			Tmp += nums[i];		// 加入新数字更新部分和
    			++HashTable[nums[i]];	// 记录新加入的数字
    			if(HashTable.size() == k)	// 同上,如果当前窗口内无重复数字的判断
    				Ans = max(Ans, Tmp);
    		}
    		return Ans;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    2462. 雇佣 K 位工人的总代价(模拟+堆排序)

    在这里插入图片描述
    这道题其实大致也是一个模拟问题,但是一个比较关键的问题在于,我们如何高效地有序地维护开头和结尾的candidates个元素,这里完全可以选用堆,也就是C++中的优先队列。接下来只需要随时保证在还有剩余元素( i ≤ j i \le j ij)的前提下,保证充满两个队列,依次贪心求解即可:

    class Solution {
    public:
        long long totalCost(vector<int>& costs, int k, int candidates) {
            long long Ans = 0;
    		int n = costs.size();
    		int i = 0, j = n - 1, Count = 0;	// Count记录着当前已经雇佣的员工个数
    		priority_queue<int, vector<int>, std::greater<int>> Front, Tail;
    		
    		for( ; i < candidates ; ++i)		// 将前candidates个元素插入队列
                Front.push(costs[i]);
    		
    		/*将后candidates个元素插入队列*/
    		for( ; j > n - 1 - candidates && j >= i ; --j)
                Tail.push(costs[j]);
          	/*注意,i和j始终指向还没有入队的员工,所以i == j时也可以推入*/
          	/*但是i和j不可以是i>j,这时后面的元素已经都入队了,不需要再加入前面的队列*/
    		while(Count < k)
    		{
    			/*此循环依次取出两个队列中较小的元素,并及时充满刚刚弹出元素的队列,直到我们找出k个员工为止*/
    			if(!Front.empty() && !Tail.empty())
    			{
    				if(Front.top() > Tail.top())
    				{
    					Ans += Tail.top();
    					Tail.pop();
    					if(j >= i)
    						Tail.push(costs[j--]);
    				}
    				else // <=
    				{
    					Ans += Front.top();
    					Front.pop();
    					if(i <= j)
    						Front.push(costs[i++]);
    				}
    			}
    			else if(!Front.empty() && Tail.empty())
    			{
    				Ans += Front.top();
    				Front.pop();
    			}
    			else
    			{
    				Ans += Tail.top();
    				Tail.pop();
    			}
    			
    			Count++;
    		}
    		return Ans;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    2463. 最小移动总距离(贪心+DP)

    在这里插入图片描述
    这道题其实可以看作背包问题的增强版,理解这道题可以首先不要考虑复杂的空间优化技巧。另外这道题其实还有贪心的策略,也就是说首先可以对机器人坐标和工厂位置进行排序,找每一个机器人的坐标最近的工厂,这个策略在本题中是最优的,具体证明法是交换邻项法

    代码如下:

    /*
    f[i][j]表示前i个工厂修理前j个机器人需要的最小移动距离
    k表示第i个工厂维修的机器人个数,需要遍历此值来获取最优解,k的取值范围是min(factory[i - 1][1], j)
    */
    
    class Solution {
    static const int N = 110;
    private:
        long long f[N][N];
    public:
        long long minimumTotalDistance(vector<int>& robot, vector<vector<int>>& factory) {
            sort(robot.begin(), robot.end());
            sort(factory.begin(), factory.end());
            memset(f, 0x3f, sizeof f);
            int n = robot.size(), m = factory.size();
            for (int i = 0; i <= m; i++)
                f[i][0] = 0;
            for (int i = 1; i <= m; i++)
                for (int j = 1; j <= n; j++) {
                    f[i][j] = f[i - 1][j];// 第i个工厂不修理机器人
                    long long cost = 0;	// 第i个工厂修理k个机器人,前i-1个工厂修理j-k个机器人
                    					// 枚举k找出最小值
                    for (int k = 1; k <= min(factory[i - 1][1], j); k++) {
                        cost += abs(factory[i - 1][0] - robot[j - k]);	// 滚动记录
                        f[i][j] = min(f[i][j], f[i - 1][j - k] + cost);
                    }
                }
            return f[m][n];
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
  • 相关阅读:
    LLMs——扩展数据受限的语言模型解决方案
    C语言练习百题之位符号&的使用
    你好,法语!A2知识点总结(3)
    第4.2关 标准输入输出——大连交通大学
    刷题数据结构实现类
    C++ opencv模板匹配
    EtcdServer初始化
    SpringBean的装配与注入
    python 小案例83
    微信扫码授权登录手游(你使用的浏览器暂不支持微信登录)
  • 原文地址:https://blog.csdn.net/zzy980511/article/details/127794889