周赛377

周赛377

2974. 最小数字游戏

简单

你有一个下标从 0 开始、长度为 偶数 的整数数组 nums ,同时还有一个空数组 arr 。Alice 和 Bob 决定玩一个游戏,游戏中每一轮 Alice 和 Bob 都会各自执行一次操作。游戏规则如下:

  • 每一轮,Alice 先从 nums 中移除一个 最小 元素,然后 Bob 执行同样的操作。
  • 接着,Bob 会将移除的元素添加到数组 arr 中,然后 Alice 也执行同样的操作。
  • 游戏持续进行,直到 nums 变为空。

返回结果数组 arr

示例 1:

输入:nums = [5,4,2,3]
输出:[3,2,5,4]
解释:第一轮,Alice 先移除 2 ,然后 Bob 移除 3 。然后 Bob 先将 3 添加到 arr 中,接着 Alice 再将 2 添加到 arr 中。于是 arr = [3,2] 。
第二轮开始时,nums = [5,4] 。Alice 先移除 4 ,然后 Bob 移除 5 。接着他们都将元素添加到 arr 中,arr 变为 [3,2,5,4] 。

示例 2:

输入:nums = [2,5]
输出:[5,2]
解释:第一轮,Alice 先移除 2 ,然后 Bob 移除 5 。然后 Bob 先将 5 添加到 arr 中,接着 Alice 再将 2 添加到 arr 中。于是 arr = [5,2] 。

提示:

  • 1 <= nums.length <= 100
  • 1 <= nums[i] <= 100
  • nums.length % 2 == 0

模拟

class Solution {
    public int[] numberGame(int[] nums) {
        Arrays.sort(nums);
        int n = nums.length;
        int[] res = new int[n];
        for(int i = 0; i < n; i += 2){
            res[i] = nums[i+1];
            res[i+1] = nums[i]; 
        }
        return res;
    }
}

2975. 移除栅栏得到的正方形田地的最大面积

中等

有一个大型的 (m - 1) x (n - 1) 矩形田地,其两个对角分别是 (1, 1)(m, n) ,田地内部有一些水平栅栏和垂直栅栏,分别由数组 hFencesvFences 给出。

水平栅栏为坐标 (hFences[i], 1)(hFences[i], n),垂直栅栏为坐标 (1, vFences[i])(m, vFences[i])

返回通过 移除 一些栅栏(可能不移除)所能形成的最大面积的 正方形 田地的面积,或者如果无法形成正方形田地则返回 -1

由于答案可能很大,所以请返回结果对 109 + 7 取余 后的值。

**注意:**田地外围两个水平栅栏(坐标 (1, 1)(1, n) 和坐标 (m, 1)(m, n) )以及两个垂直栅栏(坐标 (1, 1)(m, 1) 和坐标 (1, n)(m, n) )所包围。这些栅栏 不能 被移除。

示例 1:

img

输入:m = 4, n = 3, hFences = [2,3], vFences = [2]
输出:4
解释:移除位于 2 的水平栅栏和位于 2 的垂直栅栏将得到一个面积为 4 的正方形田地。

示例 2:

img

输入:m = 6, n = 7, hFences = [2], vFences = [4]
输出:-1
解释:可以证明无法通过移除栅栏形成正方形田地。

提示:

  • 3 <= m, n <= 109
  • 1 <= hFences.length, vFences.length <= 600
  • 1 < hFences[i] < m
  • 1 < vFences[i] < n
  • hFencesvFences 中的元素是唯一的。

贪心

class Solution {
    /**
    水平栅栏和垂直栅栏分开计算。
        对于水平栅栏,计算出任意两个栅栏之间的距离,存到一个哈希表 h 中
        对于垂直栅栏,计算出任意两个栅栏之间的距离,存到一个哈希表 v 中
    答案就是 h 和 v 中的最大值(相等的)的平方
     */
    public int maximizeSquareArea(int m, int n, int[] hFences, int[] vFences) {
        List<Integer> hlist = new ArrayList<>();
        for(int x : hFences) hlist.add(x);
        hlist.add(1);
        hlist.add(m);
        List<Integer> vlist = new ArrayList<>();
        for(int x : vFences) vlist.add(x);
        vlist.add(1);
        vlist.add(n);
        Collections.sort(hlist);
        Collections.sort(vlist);
        Map<Integer, Integer> h = new HashMap<>();
        Map<Integer, Integer> v = new HashMap<>();
        for(int i = 0; i < hlist.size(); i++)
            for(int j = i+1; j < hlist.size(); j++)
                h.merge(hlist.get(j) - hlist.get(i), 1, Integer::sum);
        int res = 0;
        for(int i = 0; i < vlist.size(); i++){
            for(int j = i+1; j < vlist.size(); j++){
                int len = vlist.get(j) - vlist.get(i);
                if(res < len && h.containsKey(len)) res = len;
            }
        }
        return res == 0 ? -1 : (int)(((long) res * res) % (int)(1e9+7));
    }
}

2976. 转换字符串的最小成本 I

中等

给你两个下标从 0 开始的字符串 sourcetarget ,它们的长度均为 n 并且由 小写 英文字母组成。

另给你两个下标从 0 开始的字符数组 originalchanged ,以及一个整数数组 cost ,其中 cost[i] 代表将字符 original[i] 更改为字符 changed[i] 的成本。

你从字符串 source 开始。在一次操作中,如果 存在 任意 下标 j 满足 cost[j] == zoriginal[j] == x 以及 changed[j] == y 。你就可以选择字符串中的一个字符 x 并以 z 的成本将其更改为字符 y

返回将字符串 source 转换为字符串 target 所需的 最小 成本。如果不可能完成转换,则返回 -1

注意,可能存在下标 ij 使得 original[j] == original[i]changed[j] == changed[i]

示例 1:

输入:source = "abcd", target = "acbe", original = ["a","b","c","c","e","d"], changed = ["b","c","b","e","b","e"], cost = [2,5,5,1,2,20]
输出:28
解释:将字符串 "abcd" 转换为字符串 "acbe" :
- 更改下标 1 处的值 'b' 为 'c' ,成本为 5 。
- 更改下标 2 处的值 'c' 为 'e' ,成本为 1 。
- 更改下标 2 处的值 'e' 为 'b' ,成本为 2 。
- 更改下标 3 处的值 'd' 为 'e' ,成本为 20 。
产生的总成本是 5 + 1 + 2 + 20 = 28 。
可以证明这是可能的最小成本。

示例 2:

输入:source = "aaaa", target = "bbbb", original = ["a","c"], changed = ["c","b"], cost = [1,2]
输出:12
解释:要将字符 'a' 更改为 'b':
- 将字符 'a' 更改为 'c',成本为 1 
- 将字符 'c' 更改为 'b',成本为 2 
产生的总成本是 1 + 2 = 3。
将所有 'a' 更改为 'b',产生的总成本是 3 * 4 = 12 。

示例 3:

输入:source = "abcd", target = "abce", original = ["a"], changed = ["e"], cost = [10000]
输出:-1
解释:无法将 source 字符串转换为 target 字符串,因为下标 3 处的值无法从 'd' 更改为 'e' 。

提示:

  • 1 <= source.length == target.length <= 105
  • sourcetarget 均由小写英文字母组成
  • 1 <= cost.length== original.length == changed.length <= 2000
  • original[i]changed[i] 是小写英文字母
  • 1 <= cost[i] <= 106
  • original[i] != changed[i]

Floyd算法

class Solution {
    private static final int INF = Integer.MAX_VALUE / 2;
    public long minimumCost(String source, String target, char[] original, char[] changed, int[] cost) {
        int n = original.length;
        int[][] g = new int[26][26];
        // 初始化邻接表
        for(int i = 0; i < 26; i++){
            Arrays.fill(g[i], INF);
            g[i][i] = 0;
        }
        for(int i = 0; i < original.length; i++){
            int x = original[i] - 'a', y = changed[i] - 'a', cs = cost[i];
            g[x][y] = Math.min(g[x][y], cs);
        }
        // Floyd算法
        for(int k = 0; k < 26; k++){
            for(int i = 0; i < 26; i++){
                for(int j = 0; j < 26; j++){
                    g[i][j] = Math.min(g[i][j], g[i][k] + g[k][j]);
                }
            }
        }

        long res = 0;
        for(int i = 0; i < source.length(); i++){
            int x = source.charAt(i) - 'a', y = target.charAt(i) - 'a';
            if(x == y) continue;
            if(g[x][y] == INF) return -1;
            res += g[x][y];
        }
        return res;
    }
}

2977. 转换字符串的最小成本 II

困难

给你两个下标从 0 开始的字符串 sourcetarget ,它们的长度均为 n 并且由 小写 英文字母组成。

另给你两个下标从 0 开始的字符串数组 originalchanged ,以及一个整数数组 cost ,其中 cost[i] 代表将字符串 original[i] 更改为字符串 changed[i] 的成本。

你从字符串 source 开始。在一次操作中,如果 存在 任意 下标 j 满足 cost[j] == zoriginal[j] == x 以及 changed[j] == y ,你就可以选择字符串中的 子串 x 并以 z 的成本将其更改为 y 。 你可以执行 任意数量 的操作,但是任两次操作必须满足 以下两个 条件 之一

  • 在两次操作中选择的子串分别是 source[a..b]source[c..d] ,满足 b < c d < a 。换句话说,两次操作中选择的下标 不相交
  • 在两次操作中选择的子串分别是 source[a..b]source[c..d] ,满足 a == c b == d 。换句话说,两次操作中选择的下标 相同

返回将字符串 source 转换为字符串 target 所需的 最小 成本。如果不可能完成转换,则返回 -1

注意,可能存在下标 ij 使得 original[j] == original[i]changed[j] == changed[i]

示例 1:

输入:source = "abcd", target = "acbe", original = ["a","b","c","c","e","d"], changed = ["b","c","b","e","b","e"], cost = [2,5,5,1,2,20]
输出:28
解释:将 "abcd" 转换为 "acbe",执行以下操作:
- 将子串 source[1..1] 从 "b" 改为 "c" ,成本为 5 。
- 将子串 source[2..2] 从 "c" 改为 "e" ,成本为 1 。
- 将子串 source[2..2] 从 "e" 改为 "b" ,成本为 2 。
- 将子串 source[3..3] 从 "d" 改为 "e" ,成本为 20 。
产生的总成本是 5 + 1 + 2 + 20 = 28 。 
可以证明这是可能的最小成本。

示例 2:

输入:source = "abcdefgh", target = "acdeeghh", original = ["bcd","fgh","thh"], changed = ["cde","thh","ghh"], cost = [1,3,5]
输出:9
解释:将 "abcdefgh" 转换为 "acdeeghh",执行以下操作:
- 将子串 source[1..3] 从 "bcd" 改为 "cde" ,成本为 1 。
- 将子串 source[5..7] 从 "fgh" 改为 "thh" ,成本为 3 。可以执行此操作,因为下标 [5,7] 与第一次操作选中的下标不相交。
- 将子串 source[5..7] 从 "thh" 改为 "ghh" ,成本为 5 。可以执行此操作,因为下标 [5,7] 与第一次操作选中的下标不相交,且与第二次操作选中的下标相同。
产生的总成本是 1 + 3 + 5 = 9 。
可以证明这是可能的最小成本。

示例 3:

输入:source = "abcdefgh", target = "addddddd", original = ["bcd","defgh"], changed = ["ddd","ddddd"], cost = [100,1578]
输出:-1
解释:无法将 "abcdefgh" 转换为 "addddddd" 。
如果选择子串 source[1..3] 执行第一次操作,以将 "abcdefgh" 改为 "adddefgh" ,你无法选择子串 source[3..7] 执行第二次操作,因为两次操作有一个共用下标 3 。
如果选择子串 source[3..7] 执行第一次操作,以将 "abcdefgh" 改为 "abcddddd" ,你无法选择子串 source[1..3] 执行第二次操作,因为两次操作有一个共用下标 3 。

提示:

  • 1 <= source.length == target.length <= 1000
  • sourcetarget 均由小写英文字母组成
  • 1 <= cost.length == original.length == changed.length <= 100
  • 1 <= original[i].length == changed[i].length <= source.length
  • original[i]changed[i] 均由小写英文字母组成
  • original[i] != changed[i]
  • 1 <= cost[i] <= 106

Floyd算法 + DP

https://leetcode.cn/problems/minimum-cost-to-convert-string-ii/solutions/2577877/zi-dian-shu-floyddp-by-endlesscheng-oi2r/

class Solution {
    /**
    changed.length <= 100,可以把每个字符串变成一个整数编号(整体),这一步可以通过字典树完成
    建图,从 o[i] 到 c[i] 连边,边权 cost[i]
    用Floyd算法求图中任意两点最短路,即 dis[i][j] 表示编号为i的字串变成编号为j的字串的最小成本

    定义 dfs(i) 表示从 source[i] 开始向后修改的最小成本
    转移 若s[i] == t[i],可以不修改 dfs(i) = dfs(i+1)
        也可以从s[i]开始向后修改,枚举所有字串编号
        利用字典树快速判断s和t的下标从i到j的字串是否在original和changed中
            如果在就用 dis[x][y] + dfs(j+1) 更新dfs(i)的最小值
    递归边界 dfs(n) = 0
    递归入口 dfs(0)
     */
    private int[][] dis;
    private char[] s, t;
    private long[] memo;
    public long minimumCost(String source, String target, String[] original, String[] changed, int[] cost) {
        int m = cost.length;
        
        dis = new int[m*2][m*2];
        for(int i = 0; i < dis.length; i++){
            Arrays.fill(dis[i], Integer.MAX_VALUE / 2);
            dis[i][i] = 0;
        }
        for(int i = 0; i < cost.length; i++){
            int x = insert(original[i]);
            int y = insert(changed[i]);
            dis[x][y] = Math.min(dis[x][y], cost[i]);
        }
        // floyd统计任意两点最短路
        for(int k = 0; k < sid; k++){
            for(int i = 0; i < sid; i++){
                if(dis[i][k] == Integer.MAX_VALUE / 2)
                    continue;
                for(int j = 0; j < sid; j++){
                    dis[i][j] = Math.min(dis[i][j], dis[i][k] + dis[k][j]);
                }
            }
        }
        // DP求修改成本,枚举以第i位为字符串起点的字串换还是不换
        s = source.toCharArray();
        t = target.toCharArray();
        memo = new long[s.length];
        Arrays.fill(memo, -1);
        long ans = dfs(0);
        return ans < Long.MAX_VALUE / 2 ? ans : -1;
    }

    public long dfs(int i){
        if(i >= s.length){
            return 0;
        }
        if(memo[i] != -1) return memo[i];
        long res = Long.MAX_VALUE / 2;
        // 当 s[i] = t[i],此时可以不修改
        if(s[i] == t[i]){
            res = dfs(i+1);
        }
        // 以 s[i] 为字符串第一个字符,枚举转移
        TrieNode p = root, q = root;
        for(int j = i; j < s.length; j++){
            p = p.child[s[j] - 'a'];
            q = q.child[t[j] - 'a'];
            if(p == null || q == null)
                break;
            if(p.sid < 0 || q.sid < 0){
                continue;
            }
            // 修改从 i 到 j 的这一段
            int d = dis[p.sid][q.sid];
            if(d < Integer.MAX_VALUE / 2){
                res = Math.min(res, d + dfs(j + 1));
            }
        }
        return memo[i] = res;
    }

    int sid = 0; // 编号总数

    class TrieNode{//字典树的结点数据结构
		boolean end;//是否是单词末尾的标识
		int pass; // 经过这个结点的次数(根据需要设置这个变量)
		TrieNode[] child; //26个小写字母的拖尾

        int sid; // 字符串编号
		public TrieNode(){
			end = false;
			pass = 0;
            sid = -1;
			child = new TrieNode[26];
		}
	}

	TrieNode root = new TrieNode();

    public int insert(String s) {
        TrieNode p = root;
        for(int i = 0; i < s.length(); i++) {
            int u = s.charAt(i) - 'a';
			//若当前结点下没有找到要的字母,则新开结点继续插入
            if (p.child[u] == null) p.child[u] = new TrieNode();
            p = p.child[u]; 
            p.pass++;
        }
        p.end = true;
        if(p.sid < 0)
            p.sid = sid++;
        return p.sid;
    }
}
  • 26
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值