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. 吃苹果的最大数目

LeetCode 807 保持城市天际线[贪心] HERODING的LeetCode之路

LeetCode﹝堆ி﹞移除石子的最大得分,吃苹果的最大数目等