力扣第 268 场力扣周赛

题目一

力扣:2081. k 镜像数字的和

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

思路

1、这个题目很简单,就是拿出一个数,首先是十进制回文数,将之转化成为K进制之后,也是回文的,那么这个二进制数就是答案之一,这个题的思路就是这样。
2、但是,我们不能硬枚举从1开始的所有十进制数,这样一定会超时,因为你看样例,可能的十进制数会非常大,所以直接枚举十进制数肯定行不通,那么我干脆,直接从一个十进制回文数,推出下一个十进制回文数,这样的话,大幅减少了计算次数,降低了复杂度。

通过上一个回文数推下一个回文数

1、如何从一个十进制回文数推出下一个十进制回文数?
<1> 通过观察很多回文数,发现他们的变化规律是先动中间,后动两边。
<2> 当中间遇到9,就得进位。
<3> 因为是镜像,所以只需要每次生成前半部分,后半部分直接复制前半部分即可
在这里插入图片描述
2、特殊:当9999->10001时候,因为全都是9,所以整体进位,直接生成就行,1后面跟了4(这个数要看传进来的回文数长度)个零,最后保证回文,要加一。
3、传进来的数,长度为奇数偶数对结果影响很大,所以一定要区分,主要影响在中轴的位置。

进制转化

我代码中的有点问题,比如十进制2,会被转化成二进制 01,反过来了,但没关系,我们要的是回文数,所以正反都是一样的。

回文数比较

一定记住,直接用string比较最方便快捷,先反转字符串,反转前后一样的话,说明是回文的。

细节

因为数都很大,所以用long long 防止越界。

代码

class Solution {
public:
	int k;
	bool is_mirror(string str_k) {
		string tmp(str_k);
		reverse(tmp.begin(), tmp.end());
		return tmp == str_k ? true : false;
	}

	string trans(long long item) {
		string ans = "";
		while (item > 0) {
			ans += (item % k) + '0';
			item /= k;
		}
		return ans;
	}

	long long next_mirror(long long mirror) {//手动产生下一个回文数
		string str_mirror = to_string(mirror);
		int n = str_mirror.size();
		//通过观察,回文数的变化一定是先中间在两边,所以一切都从中间开始变化
		int edge = (n % 2 == 0) ? n / 2 - 1 : n / 2;//奇偶数对中间的判定有影响
		for (int i = edge; i >= 0; i--) {
			if (str_mirror[i] != '9') {//定位到第一个不是9的,承受进位
				str_mirror[i]++;
				for (int j = i + 1; j <= edge; j++) {//为之前的9手动进位
					str_mirror[j] = '0';
				}
				for (int j = edge + 1; j < n; j++) {//为后半段手动镜像
					str_mirror[j] = str_mirror[n - j - 1];
				}
				return stoll(str_mirror);//转换成long long 返回
			}
		}
		//特殊情况,全都是9,集体进大位!!!!!!!
		long long ans = 1;
		for (int i = 0; i < n; i++) {
			ans *= 10;
		}
		return ans + 1;
	}

	long long kMirror(int k, int n) {
		this->k = k;
		long long ans = 0;
		long long num = 0;
		long long mirror = 1;
		while (n > 0) {
			string trans_k = trans(mirror);
			if (is_mirror(trans_k)) {
				ans += mirror;
				n--;
			}
			mirror = next_mirror(mirror);
			num++;
		}
		return ans;
	}
};  

所有代码均以通过力扣测试
(经过多次测试最短时间为):

在这里插入图片描述

题目二

力扣:2080. 区间内查询数字的频率

在这里插入图片描述
在这里插入图片描述

思路

这个题就思路就更简单了,每一次查询就在给定的查询范围内,查找对应值出现次数就行了。但是请看本题数据规模,查询次数10万次,整体数据量10万,那么这个题就不简单了,我们就得想办法降低查询的复杂度!!!!!

记忆化处理

这个很好想,既然查询次数很多很多,那么我们就不能做重复查询的事情,所以,直接每次查询之前先看看之前查过了吗,没查过就查,最后别忘了记录。

记录位置+二分查找优化查询过程

那么,还有什么可以优化的呢?很显然,我们统计区间内目标出现次数的方法太笨了。
1、我们老套的办法,是枚举所有的位置,看该位置对应的值是不是想要的。

2、但那我们为什么不记下要查找的东西的全部的位置,反其道而行之(必须从前到后记录,保证位置严格递增),用已知边界去匹配上面的位置,看已知边界能包住多少次出现记录,再用二分法找到距离边界最近的左右出现位置(要求就是出现的位置在给定区间里且最靠近左右边界),这样我们就能找到符合题目要求的数,最开始在第几次时候出现,最后是在第几次时候出现,二者做差加一,即为答案。这样,平均每次查询,复杂度都在O(lgN)左右,就降低了复杂度。

3、这种方法极为重要,可以将区域内查询某种元素个数复杂度下降到O(lgN)左右,是一种优化方案,我们在用二分法匹配好左右边界之后,剩下的操作就要看具体的题目要求了
<1> 比如力扣5900. 蜡烛之间的盘子这道题中,我们用上述方法确定了两根蜡烛之后,要看求什么,人家要求这两根蜡之间有多少盘子,所以有涉及区间和,所以在用前缀和法快速求解
<2> 回到本题,我们匹配到两个边界位置之后,知道了第一次符合要求的是第几次目标出现,最后一次符合要求的是第几次目标出现,二者相减+1,在O(1)内找到答案
在这里插入图片描述
所以匹配到了符合的左右边界之后具体怎么做要看题意。

细节

1、在做记忆化的时候,需要存三元组和哈希表,这里用的map,用结构体定义了三元组,值得注意的是map需要内置排序函数,所以要给出结构体的排序方法,这里随便给,但是要注意一定要把这三个元素都比进去,要不然比如right 和value没进行比较,map就会认定二者是一样的,在.count({1,0,1}) 和 .count({1,1,0})的时候就会认为二者一样。
2、在查找左右边界时候,一定要注意:
<1> 为啥想到二分法。不论你是查找某一个值,还是左右边界,只要是跟查找有关系,建议都先首选二分。
<2> 不管具体题意咋变,二分法核心就是,确定左右边界,每次找到中间,看是否查找成功,成功了根据题意写怎么办,不成功了再根据题意写咋办,这种核心思想不会变。
<3> 本题中的二分和力扣5900. 蜡烛之间的盘子一致,请读者看我的那篇博客学习,这里不作赘述。

代码

struct record {//三元组
	int left;
	int right;
	int value;
	bool operator < (const record & r) const {//随便写比较方法,,但是三个元素得写全
		if (left == r.left&& right == r.right) {
			return value < r.value;
		}
		else if (left == r.left) {
			return right < r.right;
		}
		return left < r.left;
	}
};	
class RangeFreqQuery {
public:
	RangeFreqQuery(vector<int>& arr) {
		this->arr = arr;//存下来arr
		for (int i = 0; i < arr.size(); i++) {//预处理,存一下每个元素出现的位置信息
			item[arr[i]].push_back(i);
		}
	}

	int query(int left, int right, int value) {
		if (items.count({ left,right,value }) > 0) {//记忆化
			return items.at({ left,right,value });
		}
		if (left == right && value == arr[left]) {//特殊情况直接处理
			return 1;
		}
		if (item.count(value) == 0) {//不存在的元素
			return 0;
		}
		vector<int> tmp = item[value];//该元素存在,取出出现的路径信息
		int n = tmp.size();
		if (left > tmp[n - 1]) {//排除掉直接错误情况
			return 0;
		}
		if (right < tmp[0]) {//排除掉直接错误情况
			return 0;
		}
		this->tmp = tmp;//进行赋值,准备查询
		this->left = left;
		this->right = right;
		if (find(false) == -1 || find(true) == -1) {//只有左右边界都存在,才有非零解
			items[{ left, right, value }] = 0;
			return 0;
		}
		items[{ left, right, value }] = find(false) - find(true) + 1;//右边界次数-左边界次数+1
		return items[{ left, right, value }];
	}
	int find(bool is_left) {//参数为true为左边界,反之右边界
		int i = 0, j = tmp.size() - 1, ans = -1;//这里的tmp是该元素全部出现的位置序列
		while (i <= j) {
			int mid = (i + j) / 2;
			if (tmp[mid] >= left && tmp[mid] <= right) {//该位置在给定的区间,即成功情况
				ans = mid;
				if (is_left) {//向左走
					j = mid - 1;
				}
				else {
					i = mid + 1;
				}
			}
			else if (tmp[mid] > right) {//失败情况
				j = mid - 1;
			}
			else {//失败情况
				i = mid + 1;
			}
		}
		return ans;//返回左右边界在tmp中下标,代表出现次数
	}
private:
	map<record, int> items;//用于记忆化
	unordered_map<int, vector<int>> item;//存储 值-出现位置序列
	vector<int> arr;//存数组
	vector<int> tmp;//存某一个值对应的位置序列
	int left;//左边界
	int right;//右边界
};

所有代码均以通过力扣测试
(经过多次测试最短时间为):

在这里插入图片描述

题目三

力扣:2079. 给植物浇水

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

思路

这个题很简单,直接模拟就行。
1、每次向前走一步,如果可以浇水,就把水量减下去,答案+1,位置+1
2、发现此时交不了水,就退回去取水,用(pos-1)*2步,回来再浇水即可。

代码

(这个题你就按照我说的自己在纸上画一遍就懂了,很简单的模拟题)

class Solution {
public:
	int reload_water(int pos) {//取水步数
		int step = pos + 1;
		return step * 2;
	}
	int wateringPlants(vector<int>& plants, int capacity) {
		int ans = 0;
		int pos = 0;
		int n = plants.size();
		int tmp_capacity = capacity;//初始化,单次水量
		while (pos < n) {
			if (tmp_capacity >= plants[pos]) {//能浇水
				tmp_capacity -= plants[pos];
				pos++;//位置++
				ans++;
			}
			else {
				ans += reload_water(pos - 1);//回去取水
				tmp_capacity = capacity;//水量回满
			}
		}
		return ans;
	}
};

所有代码均以通过力扣测试
(经过多次测试最短时间为):

在这里插入图片描述

题目四

力扣:2078. 两栋颜色不同且距离最远的房子
在这里插入图片描述
在这里插入图片描述

思路

直接模拟+暴力破解即可

代码

class Solution {
public:
	int maxDistance(vector<int>& colors) {
		int ans = 0;
		for (int i = 0; i < colors.size(); i++) {//直接枚举,任意一个房子作为起点
			for (int j = i+1; j < colors.size(); j++) {//找它后面的放子
				if (colors[i] != colors[j]) {
					ans = max(ans, j - i);//找最大的
				}
			}
		}
		return ans;
	}
};

所有代码均以通过力扣测试
(经过多次测试最短时间为):

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JLU_LYM

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值