双周赛107(模拟、贪心、DP、双指针+离线、排序+差分(正难则反))

双周赛107

2744. 最大字符串配对数目

难度简单0

给你一个下标从 0 开始的数组 words ,数组中包含 互不相同 的字符串。

如果字符串 words[i] 与字符串 words[j] 满足以下条件,我们称它们可以匹配:

  • 字符串 words[i] 等于 words[j] 的反转字符串。
  • 0 <= i < j < words.length

请你返回数组 words 中的 最大 匹配数目。

注意,每个字符串最多匹配一次。

示例 1:

输入:words = ["cd","ac","dc","ca","zz"]
输出:2
解释:在此示例中,我们可以通过以下方式匹配 2 对字符串:
- 我们将第 0 个字符串与第 2 个字符串匹配,因为 word[0] 的反转字符串是 "dc" 并且等于 words[2]。
- 我们将第 1 个字符串与第 3 个字符串匹配,因为 word[1] 的反转字符串是 "ca" 并且等于 words[3]。
可以证明最多匹配数目是 2 。

示例 2:

输入:words = ["ab","ba","cc"]
输出:1
解释:在此示例中,我们可以通过以下方式匹配 1 对字符串:
- 我们将第 0 个字符串与第 1 个字符串匹配,因为 words[1] 的反转字符串 "ab" 与 words[0] 相等。
可以证明最多匹配数目是 1 。

示例 3:

输入:words = ["aa","ab"]
输出:0
解释:这个例子中,无法匹配任何字符串。

提示:

  • 1 <= words.length <= 50
  • words[i].length == 2
  • words 包含的字符串互不相同。
  • words[i] 只包含小写英文字母。

模拟 + 哈希

class Solution {
    public int maximumNumberOfStringPairs(String[] words) {
        int ans = 0;
        Set<String> set = new HashSet<>();
        for(String w : words){
            String rw = new StringBuilder(w).reverse().toString();
            if(set.contains(rw)){
                ans += 1;
                set.remove(rw);
            }else{
                set.add(w);
            }
        }
        return ans;
    }
}

2745. 构造最长的新字符串

难度中等1

给你三个整数 xyz

这三个整数表示你有 x"AA" 字符串,y"BB" 字符串,和 z"AB" 字符串。你需要选择这些字符串中的部分字符串(可以全部选择也可以一个都不选择),将它们按顺序连接得到一个新的字符串。新字符串不能包含子字符串 "AAA" 或者 "BBB"

请你返回新字符串的最大可能长度。

子字符串 是一个字符串中一段连续 非空 的字符序列。

示例 1:

输入:x = 2, y = 5, z = 1
输出:12
解释: 我们可以按顺序连接 "BB" ,"AA" ,"BB" ,"AA" ,"BB" 和 "AB" ,得到新字符串 "BBAABBAABBAB" 。
字符串长度为 12 ,无法得到一个更长的符合题目要求的字符串。

示例 2:

输入:x = 3, y = 2, z = 2
输出:14
解释:我们可以按顺序连接 "AB" ,"AB" ,"AA" ,"BB" ,"AA" ,"BB" 和 "AA" ,得到新字符串 "ABABAABBAABBAA" 。
字符串长度为 14 ,无法得到一个更长的符合题目要求的字符串。

提示:

  • 1 <= x, y, z <= 50

O(1)贪心

https://leetcode.cn/problems/construct-the-longest-new-string/solution/nao-jin-ji-zhuan-wan-zhu-yi-abzong-shi-y-a441/

假设只有AA串和BB串,显然必须交替才能满足题目要求。因此结果串里AA和BB的数量最多只能差1。

AB串如何处理?实际上AB串总是有处放的,所有AB串可以连成一个合法的交替串,如果AA和BB组成的串两端都是AA,那么交替串可以放在最前面。如果AA和BB组成的串两端都是BB,那交替串可以放在最后面。如果AA和BB数量相同,那可以让AA在最左边,BB在最右边,这样交替串两端就都能放了。

可以通过将AB串插到AA和BB中间,来让AA和BB的数量差超过1吗?这是不可能的,因为AAABAA,BBABBB都是不合法的,而BBABAA里的AB对增大答案没有帮助。

class Solution {
    public int longestString(int x, int y, int z) {
        int ans = z;
        if(x == y) ans += x+y;
        else{
            ans += Math.min(x, y) * 2 + 1;
        }
        return ans * 2;
    }
}	

记忆化搜索(DP解法)

https://leetcode.cn/problems/construct-the-longest-new-string/solution/liang-chong-fang-fa-o1-gong-shi-ji-yi-hu-7fdi/

class Solution:
    # 类似 状态机 DP,分类讨论:
    # A 后面只能接 BB;
    # BB 后面可以接 AA 或 AB;
    # AB 后面可以接 AA 或 AB。
    # 定义dfs(x, y, z, k)其中 x,y,z 为 AA/BB/AB 的剩余数量,k=0,1,2 表示上一个字符串是 AA/BB/AB
    def longestString(self, x: int, y: int, z: int) -> int:
        return max(dfs(x, y, z, 0), dfs(x, y, z, 1)) # 枚举结尾为A 和结尾为B

@cache
def dfs(x, y, z, k: int) -> int:
    if k == 0: # AA -> BB
        return dfs(x, y-1, z, 1) + 2 if y else 0
    res1 = dfs(x-1, y, z, 0) + 2 if x else 0
    res2 = dfs(x, y, z-1, 2) + 2 if z else 0
    return max(res1, res2)

2746. 字符串连接删减字母

难度中等0

给你一个下标从 0 开始的数组 words ,它包含 n 个字符串。

定义 连接 操作 join(x, y) 表示将字符串 xy 连在一起,得到 xy 。如果 x 的最后一个字符与 y 的第一个字符相等,连接后两个字符中的一个会被 删除

比方说 join("ab", "ba") = "aba"join("ab", "cde") = "abcde"

你需要执行 n - 1连接 操作。令 str0 = words[0] ,从 i = 1 直到 i = n - 1 ,对于第 i 个操作,你可以执行以下操作之一:

  • stri = join(stri - 1, words[i])
  • stri = join(words[i], stri - 1)

你的任务是使 strn - 1 的长度 最小

请你返回一个整数,表示 strn - 1 的最小长度。

示例 1:

输入:words = ["aa","ab","bc"]
输出:4
解释:这个例子中,我们按以下顺序执行连接操作,得到 str2 的最小长度:
str0 = "aa"
str1 = join(str0, "ab") = "aab"
str2 = join(str1, "bc") = "aabc" 
str2 的最小长度为 4 。

示例 2:

输入:words = ["ab","b"]
输出:2
解释:这个例子中,str0 = "ab",可以得到两个不同的 str1:
join(str0, "b") = "ab" 或者 join("b", str0) = "bab" 。
第一个字符串 "ab" 的长度最短,所以答案为 2 。

示例 3:

输入:words = ["aaa","c","aba"]
输出:6
解释:这个例子中,我们按以下顺序执行连接操作,得到 str2 的最小长度:
str0 = "aaa"
str1 = join(str0, "c") = "aaac"
str2 = join("aba", str1) = "abaaac"
str2 的最小长度为 6 。

提示:

  • 1 <= words.length <= 1000
  • 1 <= words[i].length <= 50
  • words[i] 中只包含小写英文字母。

记忆化搜索 => 动态规划(O(n^3)解法)

枚举第i位选哪个的代表题

  • 定义dfs(i, j, k)表示当前枚举到第i位,0-i组成的字符串中第一个字符为j,最后一个字符为k,能构成的最小长度

记忆化搜索

// 比赛中的写法
class Solution {
    String[] words;
    int[][][] cache;
    public int minimizeConcatenatedLength(String[] words) {
        this.words = words;
        String w = words[0];
        cache = new int[words.length][27][27];
        for(int i = 0; i < words.length; i++)
                for(int k = 0; k < 27; k++)
                    Arrays.fill(cache[i][k], -1);
        return dfs(1, w.charAt(0), w.charAt(w.length()-1)) + w.length();
    }
    
    // 定义dfs(i, j, k)表示当前枚举到第i位,0-i组成的字符串中第一个字符为j,最后一个字符为k,能构成的最小长度
    // 转移:两种方式,令 w = words[i]
    // 	1. 令 `stri = join(stri - 1, words[i])`
    // 		如果stri[-1] == w[0],则dfs(i, j, k) = dfs(i+1, stri[0], w[-1]) + len(w)-1
    //		如果stri[-1] != w[0],则dfs(i, j, k) = dfs(i+1, stri[0], w[-1]) + len(w)
	//  2. 令 `stri = join(words[i], stri - 1)`
    //		如果stri[0] == w[-1],则dfs(i, j, k) = dfs(i+1, w[0], stri[-1]) + len(w)-1
    //		如果stri[0] != w[-1],则dfs(i, j, k) = dfs(i+1, w[0], stri[-1]) + len(w)
    //  两种方式取最大值
    // 递归边界: i == len(words),返回0
    // 递归入口:dfs(1, len(words[0]), words[0][0], words[0][-1]) + w.length();
    public int dfs(int idx, Character pre, Character suf){
        if(idx == words.length){
            return 0;
        }
        int k = pre - 'a', l = suf - 'a';
        if(cache[idx][k][l] >= 0) return cache[idx][k][l];
        int res = Integer.MAX_VALUE;
        String w = words[idx];
        // 令 stri = join(stri - 1, words[i])
        if(w.charAt(0) == suf){
            res = Math.min(res, dfs(idx+1, pre, w.charAt(w.length()-1)) + w.length()-1);
        }else{
            res = Math.min(res, dfs(idx+1, pre, w.charAt(w.length()-1)) + w.length());
        }
        // 令 stri = join(words[i], stri - 1)
        if(w.charAt(w.length()-1) == pre){
            res = Math.min(res, dfs(idx+1, w.charAt(0), suf) + w.length()-1);
        }else{
            res = Math.min(res, dfs(idx+1, w.charAt(0), suf) + w.length());
        }
        return cache[idx][k][l] = res;
    }
}
// 优化一下:
// 递归边界: i == len(words),返回len(words[0]) : 返回第一个words的长度
// 递归入口:dfs(1, len(words[0]), words[0][0], words[0][-1]);
class Solution {
    String[] words;
    int[][][] cache;
    public int minimizeConcatenatedLength(String[] words) {
        this.words = words;
        String w = words[0];
        cache = new int[words.length][27][27];
        for(int i = 0; i < words.length; i++)
                for(int k = 0; k < 27; k++)
                    Arrays.fill(cache[i][k], -1);
        return dfs(1, w.charAt(0), w.charAt(w.length()-1));
    }
    
    public int dfs(int idx, Character pre, Character suf){
        if(idx == words.length){
            return words[0].length();
        }
        int k = pre - 'a', l = suf - 'a';
        if(cache[idx][k][l] >= 0) return cache[idx][k][l];
        int res = Integer.MAX_VALUE;
        String w = words[idx];
        // 令 stri = join(stri - 1, words[i])
        if(w.charAt(0) == suf){
            res = Math.min(res, dfs(idx+1, pre, w.charAt(w.length()-1)) + w.length()-1);
        }else{
            res = Math.min(res, dfs(idx+1, pre, w.charAt(w.length()-1)) + w.length());
        }
        // 令 stri = join(words[i], stri - 1)
        if(w.charAt(w.length()-1) == pre){
            res = Math.min(res, dfs(idx+1, w.charAt(0), suf) + w.length()-1);
        }else{
            res = Math.min(res, dfs(idx+1, w.charAt(0), suf) + w.length());
        }
        return cache[idx][k][l] = res;
    }
}

转递推

class Solution {
    public int minimizeConcatenatedLength(String[] words) {
        int n = words.length;
        int[][][] f = new int[n][26][26];
        for(int i = 0; i < n; i++){
            for(int j = 0; j < 26; j++){
                Arrays.fill(f[i][j], Integer.MAX_VALUE / 2);
            }
        }
        String w = words[0];
        f[0][w.charAt(0) - 'a'][w.charAt(w.length()-1) - 'a'] = w.length();
        for(int i = 1; i < n; i++){ // words
            w = words[i];
            for(int j = 0; j < 26; j++) // pre
                for(int k = 0; k < 26; k++){ // suf
                    int wl = w.charAt(0) - 'a'; 
                    int wr = w.charAt(w.length()-1) - 'a';
                    if(wl == k){
                        f[i][j][wr] = Math.min(f[i][j][wr], f[i-1][j][k] + w.length()-1);
                    }else{
                        f[i][j][wr] = Math.min(f[i][j][wr], f[i-1][j][k] + w.length());
                    }
                    if(wr == j){
                        f[i][wl][k] = Math.min(f[i][wl][k], f[i-1][j][k] + w.length()-1);
                    }else{
                        f[i][wl][k] = Math.min(f[i][wl][k], f[i-1][j][k] + w.length());
                    }
            }
        }
        int ans = Integer.MAX_VALUE;
        for(int i = 0; i < 26; i++){
            for(int j = 0; j < 26; j++){
                ans = Math.min(ans, f[n-1][i][j]);
            }
        }
        return ans;
    }
}

O(n^2)DP解法

class Solution {
    String[] words;
    int[][] cache;
    public int minimizeConcatenatedLength(String[] words) {
        this.words = words;
        int n = words.length;
        cache = new int[n][n];
        for(int i = 0; i < n; i++)
            Arrays.fill(cache[i], -1);
        return dfs(0, 0) + words[0].length();
    }

    // 定义dfs(i, j) 表示 字符串以words[i]为开头,words[j]为结尾,能构成的最短长度
    // 怎么判断当前枚举到第几位了?   max(i, j) + 1 , 因为 i 或者 j肯定有一个是右端点
    public int dfs(int i, int j){
        int k = Math.max(i, j) + 1;
        if(k == words.length)
            return 0;
        if(cache[i][j] >= 0) return cache[i][j];
        String w = words[k];
        int res1 = dfs(i, k) - (words[j].charAt(words[j].length()-1) == w.charAt(0) ? 1 : 0); // k拼接再后面
        int res2 = dfs(k, j) - (w.charAt(w.length()-1) == words[i].charAt(0) ? 1 : 0); // k拼接再前面
        return cache[i][j] = Math.min(res1, res2) + w.length();
    }
}

2747. 统计没有收到请求的服务器数目

难度中等0

给你一个整数 n ,表示服务器的总数目,再给你一个下标从 0 开始的 二维 整数数组 logs ,其中 logs[i] = [server_id, time] 表示 id 为 server_id 的服务器在 time 时收到了一个请求。

同时给你一个整数 x 和一个下标从 0 开始的整数数组 queries

请你返回一个长度等于 queries.length 的数组 arr ,其中 arr[i] 表示在时间区间 [queries[i] - x, queries[i]] 内没有收到请求的服务器数目。

注意时间区间是个闭区间。

示例 1:

输入:n = 3, logs = [[1,3],[2,6],[1,5]], x = 5, queries = [10,11]
输出:[1,2]
解释:
对于 queries[0]:id 为 1 和 2 的服务器在区间 [5, 10] 内收到了请求,所以只有服务器 3 没有收到请求。
对于 queries[1]:id 为 2 的服务器在区间 [6,11] 内收到了请求,所以 id 为 1 和 3 的服务器在这个时间段内没有收到请求。

示例 2:

输入:n = 3, logs = [[2,4],[2,1],[1,2],[3,1]], x = 2, queries = [3,4]
输出:[0,1]
解释:
对于 queries[0]:区间 [1, 3] 内所有服务器都收到了请求。
对于 queries[1]:只有 id 为 3 的服务器在区间 [2,4] 内没有收到请求。

提示:

  • 1 <= n <= 105
  • 1 <= logs.length <= 105
  • 1 <= queries.length <= 105
  • logs[i].length == 2
  • 1 <= logs[i][0] <= n
  • 1 <= logs[i][1] <= 106
  • 1 <= x <= 105
  • x < queries[i] <= 106

同向双指针 + 离线计算(正难则反)

https://leetcode.cn/circle/discuss/LyraUJ/

正难则反,考虑有多少台服务器的请求落在查询的区间即可。

查询的区间长度固定为x,不是严格意义上的离线查询(第一眼以为是莫队),直接用双指针即可。

思路与上两周的周赛T4类似,对查询和logs按照时间顺序排序,保证有序的插入和删除。

同向双指针left和right表示落在查询区间的一系列请求,用Counter记录请求对应的服务器,n - len©就是当前这个查询的答案了。

class Solution {
    public int[] countServers(int n, int[][] logs, int x, int[] Queries) {
        int qn = Queries.length;
        int[][] queries = new int[qn][2];
        for(int i = 0; i < qn; i++){
            queries[i][0] = i;
            queries[i][1] = Queries[i];
        }
        //对查询和logs按照时间顺序排序
        Arrays.sort(queries, (a, b) -> a[1] - b[1]);
        Arrays.sort(logs, (a, b) -> a[1] - b[1]);
        int[] res = new int[qn];
        HashMap<Integer,Integer> servers = new HashMap<>();
        int left = 0, right = 0;
        // 从小到大遍历每一个询问,针对每个询问,使用相向双指针找到满足的区间
        for(int[] q : queries){
            int start = q[1] - x, end = q[1];
            while(right < logs.length && logs[right][1] <= end){
                servers.put(logs[right][0], servers.getOrDefault(logs[right][0], 0) + 1);
                right++;
            }
            while(left < right && logs[left][1] < start){
                servers.put(logs[left][0], servers.getOrDefault(logs[left][0], 0) - 1);
                if(servers.get(logs[left][0]) == 0) servers.remove(logs[left][0]);
                left++;
            }
            res[q[0]] = n - servers.keySet().size();
        }
        return res;
    }
}

排序 + 差分(正难则反)

https://leetcode.cn/problems/count-zero-request-servers/solution/pai-xu-chai-fen-by-flower1dance-vtbk6my1-osg8/

正着想,就是求每一个查询的区间内,有多少服务器收到了请求。如果只是单纯的求查询区间内的请求数,就是树状数组/线段树的裸题,但是求的是查询区间内发生过请求的服务器数。

那么可以把每条请求记录看作是一个区间(或者说数轴上的线段),对于每一个查询,它在多少个不同请求记录的线段内,这个问题就等价于查询区间内的请求数了。(相当于求一个区间内的点数,变成求蕴含一个点的区间数)。

通过对每一个服务器的所有请求记录线段进行预处理,使之成为不相交的多个线段,就可以保证求出来的蕴含查询点的所有区间只代表一个服务器了。

求蕴含查询点的区间数,只需要把所有线段的差分数组求出来即可(在每个区间内的左右端点分别加1减1,代表服务器的数量)


https://leetcode.cn/problems/count-zero-request-servers/solution/chi-san-hua-you-xu-ji-he-chai-fen-you-xi-617e/

差分

假设query[i] = 10x = 5,即求区间为[5,10]中有多少个点,逆向思维,假设有一个点为8,那么该点覆盖了[8,13],其中10就在[8,13]中,所有包括了点,因此可以对点进行差分处理,当然不是简单的差分,如logs = [[1,3],[1,5]],对于相同的点1就进行普通的差分,数组为[0,0,0,1,1,2,2,2,2,1,1]

很明显求前缀和时会重复计算点1,因此,对于相同的点,要确定差分起始位置,如下

  • 1起始位置为0time = 3时出现一次,那么diff[3] += 1, diff[9] -= 1,起始位置要变为9
  • time = 5时出现第二次,由于起始位置为9,那么diff[9] += 1, diff[11] -= 1,起始位置要变为11
  • 如此类推,这样就不会出现重复计算了

离散化

虽然所有数最大值为1e6,但遇到这种样例

[[1, 849161]]
71959
[816496]

会有很多重复计算
因此,需要进行离散化差分,参考731. 我的日程安排表 II

离散化包含两类点,log上的time值,还有queries里面的值

优先队列

queries 塞入最小堆中,求前缀和时,遇到就可以弹出堆并获得答案

class Solution:
    def countServers(self, n: int, logs: List[List[int]], x: int, queries: List[int]) -> List[int]:
        MAX_NUM = max(queries) + 1
        
        from sortedcontainers import SortedDict
        # 离散化差分有序集合
        diff = SortedDict()
        
        # 相同点按时间升序排序
        logs.sort()
        
        # 每个数字起始位置
        maxt = 0
        # 上一个数字
        last = -1
        
        for num,t in logs:
            if last != num:
                last = num
                maxt = 0
                
            # [t,t+x]
            # 离散化差分
            if maxt < t:
                maxt = t + x + 1
                if t not in diff:
                    diff[t] = 1
                else:
                    diff[t] += 1
            else:
                if maxt not in diff:
                    diff[maxt] = 1
                else:
                    diff[maxt] += 1
                maxt = t + x + 1
            if maxt > MAX_NUM:
                maxt = MAX_NUM
            
            if maxt not in diff:
                diff[maxt] = -1
            else:
                diff[maxt] -= 1
        
        # 堆优先队列
        heap = []
        for i,num in enumerate(queries):
            heappush(heap,(num,i))
            # queries 也要离散化
            if num not in diff:
                diff[num] = 0
        
        # 前缀和获取结果
        res = [n] * len(queries)
        pre = 0
        for i,d in diff.items():      
            pre += d
            while heap and heap[0][0] == i:
                res[heappop(heap)[1]] -= pre
        
        return res

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值