📝 第 390 场周赛记录-快手

1. 每个字符最多出现两次的最长子字符串

给你一个字符串 s ,请找出满足每个字符最多出现两次的最长子字符串,并返回该子字符串的 最大 长度。

示例 1:

输入: s = "bcbbbcba"

输出: 4

解释:

以下子字符串长度为 4,并且每个字符最多出现两次:"bcbbbcba"

示例 2:

输入: s = "aaaa"

输出: 2

解释:

以下子字符串长度为 2,并且每个字符最多出现两次:"aaaa"

提示:

  • 2 <= s.length <= 100
  • s 仅由小写英文字母组成。

✏️ 题解

求连续子字符串,且需要遍历,用滑动窗口解决。

class Solution {
    public int maximumLengthSubstring(String s) {
        // 滑动窗口 + 哈希(小写英文字母可以用数组记录数字
        // 每个字符最多出现两次:则子字符串的长度最小都为 2
        if(s.length() == 2)return 2;
        int len = 0;
        int[] letter_cnt = new int[26];
        int i = 0;
        int j = 1;
        // j - i >= 1恒成立
        letter_cnt[s.charAt(i)-'a']++;
        // letter_cnt[s[j]-'a']++;
        while(j < s.length()){
            if(letter_cnt[s.charAt(j)-'a'] == 2){
                // 字符串中重复字符出现次数超过 2
                while(s.charAt(i) != s.charAt(j)){
                    letter_cnt[s.charAt(i) - 'a']--;
                    i++;
                }
                letter_cnt[s.charAt(i) - 'a']--;
                i++;
            }else{
                letter_cnt[s.charAt(j)-'a']++;
                len = Math.max(j+1-i,len);
                j++;
            }
            
        }
        return len;
    }
}

2. 执行操作使数据元素之和大于等于 K

给你一个正整数 k 。最初,你有一个数组 nums = [1]

你可以对数组执行以下 任意 操作 任意 次数(可能为零):

  • 选择数组中的任何一个元素,然后将它的值 增加 1
  • 复制数组中的任何一个元素,然后将它附加到数组的末尾。

返回使得最终数组元素之 大于或等于 k 所需的 最少 操作次数。

示例 1:

输入:k = 11

输出:5

解释:

可以对数组 nums = [1] 执行以下操作:

  • 将元素的值增加 1 三次。结果数组为 nums = [4]
  • 复制元素两次。结果数组为 nums = [4,4,4]

最终数组的和为 4 + 4 + 4 = 12 ,大于等于 k = 11
执行的总操作次数为 3 + 2 = 5

示例 2:

输入:k = 1

输出:0

解释:

原始数组的和已经大于等于 1 ,因此不需要执行操作。

提示:

  • 1 <= k <= 105

✏️ 题解

这道题采用贪心的思路,要先加后减,通过找规律发现开根的方式可以获得最小的操作次数。(a 次加操作、 b 次复制操作 a 次复制操作、 b 次加操作是一样的,因为是求和)。

class Solution {
    public int minOperations(int k) {
        // 先加后减
        if(k == 1)return 0;
        
        int e = (int)Math.sqrt(k);// 加操作
        int q = (int)Math.ceil((k*1.0)/e); // 复制操作
        
        return e+q-2;
    }
}

3. 最高频率的 ID

你需要在一个集合里动态记录 ID 的出现频率。给你两个长度都为 n 的整数数组 numsfreqnums 中每一个元素表示一个 ID ,对应的 freq 中的元素表示这个 ID 在集合中此次操作后需要增加或者减少的数目。

  • 增加 ID 的数目:如果 freq[i] 是正数,那么 freq[i] 个 ID 为 nums[i] 的元素在第 i 步操作后会添加到集合中。
  • 减少 ID 的数目:如果 freq[i] 是负数,那么 -freq[i] 个 ID 为 nums[i] 的元素在第 i 步操作后会从集合中删除。

请你返回一个长度为 n 的数组 ans ,其中 ans[i] 表示第 i 步操作后出现频率最高的 ID 数目 ,如果在某次操作后集合为空,那么 ans[i] 为 0 。

示例 1:

输入:nums = [2,3,2,1], freq = [3,2,-3,1]

输出:[3,3,2,2]

解释:

第 0 步操作后,有 3 个 ID 为 2 的元素,所以 ans[0] = 3
第 1 步操作后,有 3 个 ID 为 2 的元素和 2 个 ID 为 3 的元素,所以 ans[1] = 3
第 2 步操作后,有 2 个 ID 为 3 的元素,所以 ans[2] = 2
第 3 步操作后,有 2 个 ID 为 3 的元素和 1 个 ID 为 1 的元素,所以 ans[3] = 2

示例 2:

输入:nums = [5,5,3], freq = [2,-2,1]

输出:[2,0,1]

解释:

第 0 步操作后,有 2 个 ID 为 5 的元素,所以 ans[0] = 2
第 1 步操作后,集合中没有任何元素,所以 ans[1] = 0
第 2 步操作后,有 1 个 ID 为 3 的元素,所以 ans[2] = 1

提示:

  • 1 <= nums.length == freq.length <= 105
  • 1 <= nums[i] <= 105
  • -105 <= freq[i] <= 105
  • freq[i] != 0
  • 输入保证任何操作后,集合中的元素出现次数不会为负数。

✏️ 题解

直接看代码,思路是比较清晰的,主要是要选好数据结构,这道题的核心就是选择 TreeMap,不然会超时...

class Solution {
    // int[] de;
    // long[] cnt;
    
    // public void init(int[] nums){
    //     Set<Integer> hashset = new HashSet<>();
    //     for(int num:nums){
    //         hashset.add(num);
    //     }
    //     int i = 0;
    //     cnt = new long[hashset.size()];
    //     Arrays.fill(cnt,0);
    //     de = new int[hashset.size()];
    //     for(int num: hashset){
    //         de[i++] = num;
    //     }
    // }
    
    // public int getId(int x){
    //     return Arrays.binarySearch(de,x);
    // }

    public long[] mostFrequentIDs(int[] nums, int[] freq) {
        // 方法 1:大根堆 + 哈希表 (通过 563/622,超时)
//         PriorityQueue<Integer> pq = new PriorityQueue<>((a,b) -> b - a);
//         Map<Integer, Integer> hashmap = new HashMap<>();
        
//         int n = nums.length;
//         long[] ans = new long[n];
//         for(int i = 0; i < n; i++){
//             int num = nums[i];
//             int fre = freq[i];
//             if(hashmap.containsKey(num)){
//                 // 说明之前存到过集合中,因此也要同步更新大根堆
//                 int pre_cnt = hashmap.get(num);
//                 // 减小的话可以先不删除

//                 pq.remove(pre_cnt);
//                 hashmap.put(num,hashmap.get(num)+fre);
//                 pq.add(hashmap.get(num));
//             }else{
//                 // 说明之前没有存到过集合中
//                 hashmap.put(num, fre);
//                 pq.add(fre);
//             }
//             ans[i] = pq.peek();
//         }
//         return ans;
        
        // 方法 2:哈希表(通过 562/622,超时)
//         Map<Integer, Integer> hashmap = new HashMap<>();
        
//         int n = nums.length;
//         long[] ans = new long[n];
//         for(int i = 0; i < n; i++){
//             int num = nums[i];
//             int fre = freq[i];
//             if(hashmap.containsKey(num)){
//                 // 说明之前存到过集合中,因此也要同步更新大根堆
//                 int pre_cnt = hashmap.get(num);
//                 hashmap.put(num,hashmap.get(num)+fre);
//             }else{
//                 // 说明之前没有存到过集合中
//                 hashmap.put(num, fre);
//             }
//             ans[i] = Collections.max(hashmap.values());
//         }
//         return ans;
        
        // 方法 3:数组(通过522/622,超时)
        // int n = nums.length;
        // long[] ans = new long[n];
        // init(nums);
        // for(int i = 0; i < n; i++){
        //     int num = nums[i];
        //     int fre = freq[i];
        //     int idx = getId(num);
        //     cnt[idx] += fre;
        //     ans[i] = Arrays.stream(cnt).max().getAsLong();
        // }
        // return ans;

        // 方法 4:TreeMap + HashMap
        int n = nums.length;
        TreeMap<Long, Integer> count = new TreeMap<>(Collections.reverseOrder()); // key是id个数,val是各个个数对应id的数目
        count.put(0L, 1000000);
        
        HashMap<Integer, Long> map = new HashMap<>(); // key是id,val是id对应的个数
        
        long[] res = new long[n];
        
        for(int i = 0; i < n; i++){
            int id = nums[i];
            long before = map.getOrDefault(id, 0L); // 旧值
            long cur = before + freq[i]; // 新值
            
            count.compute(before, (k, v) -> v == 1 ? null : v - 1); // 更新旧值,这里compute第二个参数为null,则对应的键值对会被删除
            count.compute(cur, (k, v) -> v == null ? 1 : v + 1); // 更新新值
            
            map.put(id, cur);
            
            res[i] = count.firstKey(); // 注意,firstKey()只有TreeMap才有,如果count声明为Map是会报错的。
        }
        
        
        return res;
    }
}

4. 最长公共后缀查询(未完成)

给你两个字符串数组 wordsContainerwordsQuery

对于每个 wordsQuery[i] ,你需要从 wordsContainer 中找到一个与 wordsQuery[i]最长公共后缀 的字符串。如果 wordsContainer 中有两个或者更多字符串有最长公共后缀,那么答案为长度 最短 的。如果有超过两个字符串有 相同 最短长度,那么答案为它们在 wordsContainer 中出现 更早 的一个。

请你返回一个整数数组 ans ,其中 ans[i]wordsContainer中与 wordsQuery[i]最长公共后缀 字符串的下标。

示例 1:

输入:wordsContainer = ["abcd","bcd","xbcd"], wordsQuery = ["cd","bcd","xyz"]

输出:[1,1,1]

解释:

我们分别来看每一个 wordsQuery[i]

  • 对于 wordsQuery[0] = "cd"wordsContainer 中有最长公共后缀 "cd" 的字符串下标分别为 0 ,1 和 2 。这些字符串中,答案是下标为 1 的字符串,因为它的长度为 3 ,是最短的字符串。
  • 对于 wordsQuery[1] = "bcd"wordsContainer 中有最长公共后缀 "bcd" 的字符串下标分别为 0 ,1 和 2 。这些字符串中,答案是下标为 1 的字符串,因为它的长度为 3 ,是最短的字符串。
  • 对于 wordsQuery[2] = "xyz"wordsContainer 中没有字符串跟它有公共后缀,所以最长公共后缀为 "" ,下标为 0 ,1 和 2 的字符串都得到这一公共后缀。这些字符串中, 答案是下标为 1 的字符串,因为它的长度为 3 ,是最短的字符串。

示例 2:

输入:wordsContainer = ["abcdefgh","poiuygh","ghghgh"], wordsQuery = ["gh","acbfgh","acbfegh"]

输出:[2,0,2]

解释:

我们分别来看每一个 wordsQuery[i]

  • 对于 wordsQuery[0] = "gh"wordsContainer 中有最长公共后缀 "gh" 的字符串下标分别为 0 ,1 和 2 。这些字符串中,答案是下标为 2 的字符串,因为它的长度为 6 ,是最短的字符串。
  • 对于 wordsQuery[1] = "acbfgh" ,只有下标为 0 的字符串有最长公共后缀 "fgh" 。所以尽管下标为 2 的字符串是最短的字符串,但答案是 0 。
  • 对于 wordsQuery[2] = "acbfegh"wordsContainer 中有最长公共后缀 "gh" 的字符串下标分别为 0 ,1 和 2 。这些字符串中,答案是下标为 2 的字符串,因为它的长度为 6 ,是最短的字符串。

提示:

  • 1 <= wordsContainer.length, wordsQuery.length <= 104
  • 1 <= wordsContainer[i].length <= 5 * 103
  • 1 <= wordsQuery[i].length <= 5 * 103
  • wordsContainer[i] 只包含小写英文字母。
  • wordsQuery[i] 只包含小写英文字母。
  • wordsContainer[i].length 的和至多为 5 * 105
  • wordsQuery[i].length 的和至多为 5 * 105

✏️ 题解

待定...

posted @ 2024-03-24 12:08  无发可说  阅读(10)  评论(0编辑  收藏  举报