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;
    }
};

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;
    }
};

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;
    }
};

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];
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值