LeetCode/最大化城市的最小供电站数目
Posted 929code
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LeetCode/最大化城市的最小供电站数目相关的知识,希望对你有一定的参考价值。
政府批准了可以额外建造 k 座供电站,你需要决定这些供电站分别应该建在哪里,这些供电站与已经存在的供电站有相同的供电范围。
给你两个整数 r 和 k ,如果以最优策略建造额外的发电站,返回所有城市中,最小供电站数目的最大值是多少。
一. 二分法+前缀和+贪心
分析:最大化最小值,首先考虑使用二分法,逐步试探满足条件的上界值
从左往右遍历,贪心地使得左边城市的先满足条件
同时贪心将电站建到最远处,计算此时需要的供电站数,同时更新其他位置的值(使用临时数据)
这里要预先计算出各个城市实际所拥有的电站数,通过前缀和的方式,发电范围内做差即可(注意边界处理)
每次新生成临时数组更新超时
class Solution
public:
long long maxPower(vector<int>& stations, int r, int k)
//最大化供电站的数目
int n = stations.size();
vector<long> presum(n+1);
vector<long> power(n);
for(int i=0;i<n;i++)
presum[i+1] = presum[i] +stations[i];
for (int i = 0; i < n; i++)
power[i] = presum[min(i + r + 1, n)] - presum[max(i - r, 0)]; // 电量
long left = *min_element(power.begin(),power.end());
long right = *max_element(power.begin(),power.end())+k;
while(left<right)
vector<long> cur = power;//每次循环重置
long need = 0;
long mid = (left+right+1)/2;//目标是让所有供电站都大于等于mid
for(int i=0;i<n;i++)//从左往右贪心建电站,不用考虑左侧,建在最远处
long m = mid - cur[i];//差值
if(m>0)//需要m个电站
need+=m;//建到最远处,同时给其他地方供电
for(int j=i;j<min(n,2*r+i+1);j++)
cur[j] = cur[j]+m;
if(need>k) right = mid-1;
else left = mid ;//最大化
return left;
;
二. 差分数组优化
class Solution
public:
long long maxPower(vector<int>& stations, int r, int k)
//最大化供电站的数目
int n = stations.size();
vector<long> presum(n+1);
vector<long> power(n);
for(int i=0;i<n;i++)
presum[i+1] = presum[i] +stations[i];
for (int i = 0; i < n; i++)
power[i] = presum[min(i + r + 1, n)] - presum[max(i - r, 0)]; // 电量
long left = *min_element(power.begin(),power.end());
long right = *max_element(power.begin(),power.end())+k;
while(left<right)
vector<long> dif(n);//差分数组初始化
long sum_dif = 0;
long need = 0;//用于与给定值做判断
long mid = (left+right+1)/2;//目标是让所有供电站都大于等于mid
for(int i=0;i<n;i++)//从左往右贪心建电站,不用考虑左侧,建在最远处
sum_dif+=dif[i];//累加差分值,辅助后面的更新
long m = mid - (power[i]+sum_dif);//差值
if(m>0)//需要m个电站
need+=m;//建到最远处,同时给其他地方供电
if(need>k)right =mid-1;break;//剪枝
sum_dif+=m;//后面范围内的电站的同样进行更新
if(i+2*r+1<n) dif[i+2*r+1]-=m;//这个值只作用这该范围,后面要剪掉
if(need>k) right = mid-1;
else left = mid ;//最大化
return left;
;
LeetCode 686. 重复叠加字符串匹配 / 1044. 最长重复子串(字符串哈希) / 1705. 吃苹果的最大数目
686. 重复叠加字符串匹配
2021.12.22 每日一题
题目描述
给定两个字符串 a 和 b,寻找重复叠加字符串 a 的最小次数,使得字符串 b 成为叠加后的字符串 a 的子串,如果不存在则返回 -1。
注意:字符串 “abc” 重复叠加 0 次是 “”,重复叠加 1 次是 “abc”,重复叠加 2 次是 “abcabc”。
示例 1:
输入:a = “abcd”, b = “cdabcdab”
输出:3
解释:a 重复叠加三遍后为 “abcdabcdabcd”, 此时 b 是其子串。
示例 2:
输入:a = “a”, b = “aa”
输出:2
示例 3:
输入:a = “a”, b = “a”
输出:1
示例 4:
输入:a = “abc”, b = “wxyz”
输出:-1
提示:
1 <= a.length <= 10^4
1 <= b.length <= 10^4
a 和 b 由小写英文字母组成
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/repeated-string-match
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路
错了好多次,终于对了
先找出b中a的数目,然后拼接num次a,然后再判断拼接0个、1个、2个a的情况
可以将indexOf写成KMP算法或者字符串哈希,这里就不写了
class Solution
public int repeatedStringMatch(String a, String b)
//叠加几次不是问题,主要问题是是否存在这样的叠加字符串
//想到一个思路就是首先在b里面找a字符串,找到最左边和最右边的字符串位置,
//然后在这两个位置前后再向前向后遍历,如果能匹配上,就说明可以,否则不行
int la = a.length(), lb = b.length();
int start = b.indexOf(a);
int end = b.lastIndexOf(a);
//期间出现的次数
int num = (end - start) / la + 1;
String temp = "";
//计算出num以后,拼接num次
for(int i = 0; i < num; i++)
temp += a;
if(temp.indexOf(b) != -1)
return num;
temp += a;
if(temp.indexOf(b) != -1)
return num + 1;
temp += a;
if(temp.indexOf(b) != -1)
return num + 2;
return -1;
1044. 最长重复子串
2021.12.23 每日一题
题目描述
给你一个字符串 s ,考虑其所有 重复子串 :即,s 的连续子串,在 s 中出现 2 次或更多次。这些出现之间可能存在重叠。
返回 任意一个 可能具有最长长度的重复子串。如果 s 不含重复子串,那么答案为 “” 。
示例 1:
输入:s = “banana”
输出:“ana”
示例 2:
输入:s = “abcd”
输出:""
提示:
2 <= s.length <= 3 * 10^4
s 由小写英文字母组成
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-duplicate-substring
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路
根据提示写了下面的代码,然后一些短例子能过,长例子有问题,我能想到的问题就是哈希出现了错误
思路应该是没啥问题的
包括该取余的地方也取余了,溢出的情况,也重新加上了MOD
class Solution
public static final int MOD = (int)1e9 + 7; //取余
String s;
public String longestDupSubstring(String s)
//一看到这种题,下意思反应就是动态规划,不过这个好像不太行
//然后看到标签,就想到了昨天的字符串哈希,就是在这个字符串中找一个固定长度的子串,看是否有相同哈希值的两个子串
//首先二分查找,找这个长度;然后check中用字符串哈希的方法,滑动窗口解决
//用过一次字符串哈希的方法,我能想到的就是给每个字符乘一个基数,然后因为会很大,所以对一个数取余
//然后呢,是先计算出这个字符串的哈希值,然后由此来方便计算子串的哈希值吗
this.s = s;
int l = s.length();
int left = 0;
int right = l - 1;
String res = "";
while(left < right)
int mid = (right - left + 1) / 2 + left;
//如果这个长度可行,那么变长
String temp = check(mid);
if(!temp.equals(""))
left = mid;
res = temp;
else
right = mid - 1;
return res;
public String check(int len)
int hash = 0;
int base = 1;
for(int i = 0; i < len; i++)
int temp = s.charAt(i) - 'a';
hash = (hash * 26 % MOD + temp) % MOD;
if(hash < 0)
hash += MOD;
base = base * 26 % MOD;
if(base < 0)
base += MOD;
Set<Integer> set = new HashSet();
set.add(hash);
int right = len;
int left = 0;
int tempHash = hash;
while(right < s.length())
int lnum = s.charAt(left) - 'a';
int rnum = s.charAt(right) - 'a';
tempHash = (tempHash * 26 % MOD + rnum) - lnum * base % MOD;
if(tempHash < 0)
tempHash += MOD;
right++;
left++;
if(set.contains(tempHash))
return s.substring(left, right);
set.add(tempHash);
return "";
而解决这个哈希冲突,官解用的是双哈希函数,并且是随机生成的进制和模值,第一次见
为了验证所写代码的正确性,我也加了一个哈希函数
随便两个模值还不行。。确实难搞
class Solution
public static final int MOD1 = (int)1e9 + 7; //取余
public static final int MOD2 = (int)1e9 + 7; //取余
String s;
public String longestDupSubstring(String s)
//一看到这种题,下意思反应就是动态规划,不过这个好像不太行
//然后看到标签,就想到了昨天的字符串哈希,就是在这个字符串中找一个固定长度的子串,看是否有相同哈希值的两个子串
//首先二分查找,找这个长度;然后check中用字符串哈希的方法,滑动窗口解决
//用过一次字符串哈希的方法,我能想到的就是给每个字符乘一个基数,然后因为会很大,所以对一个数取余
//然后呢,是先计算出这个字符串的哈希值,然后由此来方便计算子串的哈希值吗
this.s = s;
int l = s.length();
int left = 0;
int right = l - 1;
String res = "";
while(left < right)
int mid = (right - left + 1) / 2 + left;
//如果这个长度可行,那么变长
String temp = check(mid);
if(!temp.equals(""))
left = mid;
res = temp;
else
right = mid - 1;
return res;
public String check(int len)
long hash1 = 0;
long base1 = 1;
long hash2 = 0;
long base2 = 1;
for(int i = 0; i < len; i++)
int temp = s.charAt(i) - 'a';
hash1 = (hash1 * 26 % MOD1 + temp) % MOD1;
hash2 = (hash2 * 31 % MOD2 + temp) % MOD2;
if(hash1 < 0)
hash1 += MOD1;
if(hash2 < 0)
hash2 += MOD2;
base1 = base1 * 26 % MOD1;
base2 = base2 * 31 % MOD2;
if(base1 < 0)
base1 += MOD1;
if(base2 < 0)
base2 += MOD2;
Set<Long> set1 = new HashSet();
Set<Long> set2 = new HashSet();
set1.add(hash1);
set2.add(hash2);
int right = len;
int left = 0;
long tempHash1 = hash1;
long tempHash2 = hash2;
while(right < s.length())
int lnum = s.charAt(left) - 'a';
int rnum = s.charAt(right) - 'a';
tempHash1 = ((tempHash1 * 26 % MOD1 + rnum) - lnum * base1 % MOD1 + MOD1) % MOD1;
tempHash2 = ((tempHash2 * 31 % MOD2 + rnum) - lnum * base2 % MOD2 + MOD2) % MOD2;
if(tempHash1 < 0)
tempHash1 += MOD1;
if(tempHash2 < 0)
tempHash2 += MOD2;
right++;
left++;
if(set1.contains(tempHash1) && set2.contains(tempHash2))
return s.substring(left, right);
set1.add(tempHash1);
set2.add(tempHash2);
return "";
以后还是用三叶姐这样写的字符串哈希吧,我最开始也想过这样写,但是感觉没太大区别就算了
直接创建两个long数组,预先处理整个字符串,方便又简洁
class Solution
long[] h, p;
public String longestDupSubstring(String s)
int P = 1313131, n = s.length();
h = new long[n + 10]; p = new long[n + 10];
p[0] = 1;
for (int i = 0; i < n; i++)
p[i + 1] = p[i] * P;
h[i + 1] = h[i] * P + s.charAt(i);
String ans = "";
int l = 0, r = n;
while (l < r)
int mid = l + r + 1 >> 1;
String t = check(s, mid);
if (t.length() != 0) l = mid;
else r = mid - 1;
ans = t.length() > ans.length() ? t : ans;
return ans;
String check(String s, int len)
int n = s.length();
Set<Long> set = new HashSet<>();
for (int i = 1; i + len - 1 <= n; i++)
int j = i + len - 1;
long cur = h[j] - h[i - 1] * p[j - i + 1];
if (set.contains(cur)) return s.substring(i - 1, j);
set.add(cur);
return "";
后缀数组就算了看了,下次一定
1705. 吃苹果的最大数目
2021.12.24 每日一题,平安夜吃苹果,不过今天是真的冷
题目描述
有一棵特殊的苹果树,一连 n 天,每天都可以长出若干个苹果。在第 i 天,树上会长出 apples[i] 个苹果,这些苹果将会在 days[i] 天后(也就是说,第 i + days[i] 天时)腐烂,变得无法食用。也可能有那么几天,树上不会长出新的苹果,此时用 apples[i] == 0 且 days[i] == 0 表示。
你打算每天 最多 吃一个苹果来保证营养均衡。注意,你可以在这 n 天之后继续吃苹果。
给你两个长度为 n 的整数数组 days 和 apples ,返回你可以吃掉的苹果的最大数目。
示例 1:
输入:apples = [1,2,3,5,2], days = [3,2,1,4,2]
输出:7
解释:你可以吃掉 7 个苹果:
- 第一天,你吃掉第一天长出来的苹果。
- 第二天,你吃掉一个第二天长出来的苹果。
- 第三天,你吃掉一个第二天长出来的苹果。过了这一天,第三天长出来的苹果就已经腐烂了。
- 第四天到第七天,你吃的都是第四天长出来的苹果。
示例 2:
输入:apples = [3,0,0,0,0,2], days = [3,0,0,0,0,2]
输出:5
解释:你可以吃掉 5 个苹果:
- 第一天到第三天,你吃的都是第一天长出来的苹果。
- 第四天和第五天不吃苹果。
- 第六天和第七天,你吃的都是第六天长出来的苹果。
提示:
apples.length == n
days.length == n
1 <= n <= 2 * 10^4
0 <= apples[i], days[i] <= 2 * 10^4
只有在 apples[i] = 0 时,days[i] = 0 才成立
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/maximum-number-of-eaten-apples
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路
我刚开始想的是统一写成那种时间不是一直加1的那种形式,然后越想越绕,加上是开组会的时候做的,脑子里面嗡嗡响,很乱
而把时间一一拆开,就很好做了
对于每一个时间,有腐烂、长和吃三个动作,而之前长的苹果,在当前时间肯定是可以吃的(如果没有腐烂的话),所以优先队列里按腐烂时间排序,就能知道当前能吃的是哪些苹果
然后对于不长苹果的时间,因为不再生长了,所以只需要考虑腐烂和吃两个动作,所以就可以不用一个个遍历时间,而是直接加上跳跃的时间
也可以写在一起,时间不断增长也是可以的
class Solution
public int eatenApples(int[] apples, int[] days)
//优先队列,按时间存储,然后腐烂时间短的先被吃掉,不管是第几天的
//好像还没有那么简单
//对于每天长出的苹果,比如长出来x个,过y天腐烂,如果y<x,那么就相当于只长出了y个苹果,那么到腐烂的那一天其实都可以覆盖
//如果y>x,那么在到腐烂的那一天,都可以吃
//如果以腐烂的时间排序,那么腐烂那天y可以有最多y个苹果
//但是这个苹果数怎么统计呢,例如第二天长出两个第五天腐烂的苹果,那么在第五天那里就加2,
//此时,第三天也长出两个第五天腐烂的苹果,那么相当于也加了2,但是其实只能吃三天
//这种情况怎么处理呢,加一个left值,表示到第五天腐烂,还能有多少个苹果吃
int l = apples.length;
//不知道咋回事,可能是我自己想太多了吧,其实按照每天来遍历就非常简单
//我想的是不要遍历每一天
//按照腐烂时间排序,数组形式是腐烂时间,苹果数目
PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[0] - b[0]);
int res = 0;
//遍历每一个时间,这个时间会长出苹果,也会吃苹果
for(int i = 0; i < l; i++)
//如果这个时间苹果已经腐烂,就弹出
while(!pq.isEmpty() && pq.peek()[0] <= i)
pq.poll();
//然后加入苹果
int fulan = days[i] + i; //腐烂时间
if(apples[i] > 0)
pq.offer(new int[]fulan, apples[i]);
//然后吃苹果
if(!pq.isEmpty())
res++;
int[] temp = pq.poll();
if(temp[1] > 1)
pq.offer(new int[]temp[0], temp[1] - 1);
//然后对于后面的苹果,只能吃,不能长
int time = l;
while(!pq.isEmpty())
//如果这个时间苹果已经腐烂,就弹出
while(!pq.isEmpty() && pq.peek()[0] <= time)
pq.poll();
if(!pq.isEmpty())
int[] temp = pq.poll();
int day = temp[0] - time; //吃苹果的天数
int app = temp[1]; //苹果数目
LeetCode 1414 和为K的最小斐波那契数字数目[贪心] HERODING的LeetCode之路
Electrification Plan 最小生成树(prim+krusl)
LeetCode 686. 重复叠加字符串匹配 / 1044. 最长重复子串(字符串哈希) / 1705. 吃苹果的最大数目
LeetCode 686. 重复叠加字符串匹配 / 1044. 最长重复子串(字符串哈希) / 1705. 吃苹果的最大数目