这周有点拉跨啊,就搞出来2题。

记录一下吧:

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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class Solution {
    public int maximumLengthSubstring(String S) {
        char[] s = S.toCharArray();
        int ans = 0;
        int left = 0;
        int[] cnt = new int[26];
        for (int i = 0; i < s.length; i++) {
            int b = s[i] - 'a';
            cnt[b]++;
            while (cnt[b] > 2) {
                cnt[s[left++] - 'a']--;
            }
            ans = Math.max(ans, i - left + 1);
        }
        return ans;
    }
}

最高频率的 ID

你需要在一个集合里动态记录 ID 的出现频率。给你两个长度都为 n 的整数数组 nums 和 freq ,nums 中每一个元素表示一个 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 。

方法一:哈希表 + 有序集合

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class Solution {
    public long[] mostFrequentIDs(int[] nums, int[] freq) {
        Map<Integer, Long> cnt = new HashMap<>();
        TreeMap<Long, Integer> m = new TreeMap<>();
        int n = nums.length;
        long[] ans = new long[n];
        for (int i = 0; i < n; i++) {
            int x = nums[i];
            if (cnt.containsKey(x) && m.containsKey(cnt.get(x)) && m.merge(cnt.get(x), -1, Integer::sum) == 0) { // --m[cnt[x]] == 0
                m.remove(cnt.get(x));
            }
            long c = cnt.merge(x, (long) freq[i], Long::sum); // cnt[x] += freq[i]
            m.merge(c, 1, Integer::sum); // ++m[cnt[x]]
            ans[i] = m.lastKey();
        }
        return ans;
    }
}

方法二:哈希表 + 懒删除堆

也可以不用有序集合,而是用一个最大堆保存数对 (cnt[x],x)(\textit{cnt}[x], x)(cnt[x],x)。

在堆中查询 cnt[x]\textit{cnt}[x]cnt[x] 的最大值时,如果堆顶保存的数据并不是目前实际的 cnt[x]\textit{cnt}[x]cnt[x],那么就弹出堆顶。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19

class Solution {
    public long[] mostFrequentIDs(int[] nums, int[] freq) {
        int n = nums.length;
        long[] ans = new long[n];
        Map<Integer, Long> cnt = new HashMap<>();
        PriorityQueue<Pair<Long, Integer>> pq = new PriorityQueue<>((a, b) -> Long.compare(b.getKey(), a.getKey()));
        for (int i = 0; i < n; i++) {
            int x = nums[i];
            long c = cnt.merge(x, (long) freq[i], Long::sum);
            pq.add(new Pair<>(c, x));
            while (!pq.peek().getKey().equals(cnt.get(pq.peek().getValue()))) { // 堆顶保存的数据已经发生变化
                pq.poll(); // 删除
            }
            ans[i] = pq.peek().getKey();
        }
        return ans;
    }
}

最长公共后缀查询

给你两个字符串数组 wordsContainer 和 wordsQuery 。

对于每个 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 。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class Node {
    Node[] son = new Node[26];
    int minL = Integer.MAX_VALUE;
    int i;
}

class Solution {
    public int[] stringIndices(String[] wordsContainer, String[] wordsQuery) {
        Node root = new Node();
        for (int idx = 0; idx < wordsContainer.length; ++idx) {
            char[] s = wordsContainer[idx].toCharArray();
            int l = s.length;
            Node cur = root;
            if (l < cur.minL) {
                cur.minL = l;
                cur.i = idx;
            }
            for (int i = s.length - 1; i >= 0; i--) {
                int b = s[i] - 'a';
                if (cur.son[b] == null) {
                    cur.son[b] = new Node();
                }
                cur = cur.son[b];
                if (l < cur.minL) {
                    cur.minL = l;
                    cur.i = idx;
                }
            }
        }

        int[] ans = new int[wordsQuery.length];
        for (int idx = 0; idx < wordsQuery.length; idx++) {
            char[] s = wordsQuery[idx].toCharArray();
            Node cur = root;
            for (int i = s.length - 1; i >= 0 && cur.son[s[i] - 'a'] != null; i--) {
                cur = cur.son[s[i] - 'a'];
            }
            ans[idx] = cur.i;
        }
        return ans;
    }
}

代码参考自灵茶山艾府