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];          //苹果数目
                //当前能吃到的是两者的最小值
                int eat = Math.min(day, app);
                res += eat;
                //然后时间更新为最新的时间
                time += eat;
            }
        }
        return res;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值