力扣第 140 场双周赛

3300. 替换为数位和以后的最小元素

给你一个整数数组 nums 。请你将 nums 中每一个元素都替换为它的各个数位之  。

请你返回替换所有元素以后 nums 中的 最小 元素。

示例 1:

输入:nums = [10,12,13,14]

输出:1

解释:

nums 替换后变为 [1, 3, 4, 5] ,最小元素为 1 。

示例 2:

输入:nums = [1,2,3,4]

输出:1

解释:

nums 替换后变为 [1, 2, 3, 4] ,最小元素为 1 。

示例 3:

输入:nums = [999,19,199]

输出:10

解释:

nums 替换后变为 [27, 10, 19] ,最小元素为 10 。

提示:

  • 1 <= nums.length <= 100
  • 1 <= nums[i] <= 10^4

思路:看代码即可。

代码:

class Solution {
    public int minElement(int[] nums) {
         int minSum = Integer.MAX_VALUE;

        for (int num : nums) {
            int digitSum = getDigitSum(num);
            minSum = Math.min(minSum, digitSum);
        }

        return minSum;
    }
     private static int getDigitSum(int num) {
        int sum = 0;
        while (num > 0) {
            sum += num % 10; // 取出最后一位数字
            num /= 10; // 去掉最后一位数字
        }
        return sum;
    }
}

3301. 高度互不相同的最大塔高和

给你一个数组 maximumHeight ,其中 maximumHeight[i] 表示第 i 座塔可以达到的 最大 高度。

你的任务是给每一座塔分别设置一个高度,使得:

  1. 第 i 座塔的高度是一个正整数,且不超过 maximumHeight[i] 。
  2. 所有塔的高度互不相同。

请你返回设置完所有塔的高度后,可以达到的 最大 总高度。如果没有合法的设置,返回 -1 。

示例 1:

输入:maximumHeight = [2,3,4,3]

输出:10

解释:我们可以将塔的高度设置为:[1, 2, 4, 3] 。

示例 2:

输入:maximumHeight = [15,10]

输出:25

解释:我们可以将塔的高度设置为:[15, 10] 。

示例 3:

输入:maximumHeight = [2,2,1]

输出:-1

解释:无法设置塔的高度为正整数且高度互不相同。

提示:

  • 1 <= maximumHeight.length <= 10^5
  • 1 <= maximumHeight[i] <= 10^9

思路:

从最大的元素开始思考:数组中的最大值 m 不变是最好的。数组中的次大值呢?如果它等于 m,那么它必须变成 m−1,否则不变。
为了方便计算,先把数组从大到小排序,那么 maximumHeight[i] 的实际值为min(maximumHeight[i],maximumHeight[i−1]−1)
如果元素值 ≤0,不符合题目要求,返回 −1。最终答案为 maximumHeight 的元素之和

代码:

class Solution {
    public long maximumTotalSum(int[] maximumHeight) {
        Arrays.sort(maximumHeight); // 从小到大排序,下面倒着遍历
        int n = maximumHeight.length;
        long ans = maximumHeight[n - 1];
        for (int i = n - 2; i >= 0; i--) {
            maximumHeight[i] = Math.min(maximumHeight[i], maximumHeight[i + 1] - 1);
            if (maximumHeight[i] <= 0) {
                return -1;
            }
            ans += maximumHeight[i];
        }
        return ans;
    }
}

3302. 字典序最小的合法序列

给你两个字符串 word1 和 word2 。如果一个字符串 x 修改 至多 一个字符会变成 y ,那么我们称它与 y 几乎相等 。

如果一个下标序列 seq 满足以下条件,我们称它是 合法的 :

  • 下标序列是 升序 
  • 将 word1 中这些下标对应的字符 按顺序 连接,得到一个与 word2 几乎相等 的字符串。

请你返回一个长度为 word2.length 的数组,表示一个 字典序最小的合法下标序列。如果不存在这样的序列,请你返回一个  数组。

注意 ,答案数组必须是字典序最小的下标数组,而 不是 由这些下标连接形成的字符串。

示例 1:

输入:word1 = "vbcca", word2 = "abc"

输出:[0,1,2]

解释:字典序最小的合法下标序列为 [0, 1, 2] :

  • 将 word1[0] 变为 'a' 。
  • word1[1] 已经是 'b' 。
  • word1[2] 已经是 'c' 。

示例 2:

输入:word1 = "bacdc", word2 = "abc"

输出:[1,2,4]

解释:字典序最小的合法下标序列为 [1, 2, 4] :

  • word1[1] 已经是 'a' 。
  • 将 word1[2] 变为 'b' 。
  • word1[4] 已经是 'c' 。

示例 3:

输入:word1 = "aaaaaa", word2 = "aaabc"

输出:[]

解释:没有合法的下标序列。

示例 4:

输入:word1 = "abc", word2 = "ab"

输出:[0,1]

提示:

  • 1 <= word2.length < word1.length <= 3 * 10^5
  • word1 和 word2 只包含小写英文字母。

思路:本题可以修改一个字母,推荐先完成不修改版本 2565. 最少得分子序列(参考灵神的题解)。做完 2565 后,你知道本题也可以用前后缀分解,但难点在于计算字典序最小的下标序列。

为方便描述,下文把 word1记作 s,把 word2记作 t。

定义 suf[i] 为 s[i:] 对应的 t 的最长后缀的开始下标 j,即 t[j:] 是 s[i:] 的子序列。

预处理 suf,然后从左到右遍历 s,分类讨论:

如果 s[i]=t[j],既然能匹配上,那么就立刻匹配,直接把 i 加入答案。(如果不匹配,可能后面就没机会找到子序列了。)
如果 s[i]不等于t[j] 且 suf[i+1]≤j+1,说明修改 s[i] 为 t[j] 后,t[j+1:] 是 s[i+1:] 的子序列。此时一定要修改,如果不修改,那么答案的第 j 个下标就比 i 大了,不是字典序最小的下标序列。
修改后,继续向后匹配,在 s[i]=t[j] 时把 i 加入答案。
循环中,如果发现 j 等于 t 的长度,说明匹配完成,立刻返回答案。

如果循环中没有返回,那么循环结束后返回空数组。

代码:

class Solution {
    public int[] validSequence(String S, String T) {
        char[] s = S.toCharArray();
        char[] t = T.toCharArray();
        int n = s.length;
        int m = t.length;

        int[] suf = new int[n + 1];
        suf[n] = m;
        int j = m - 1;
        for (int i = n - 1; i >= 0; i--) {
            if (j >= 0 && s[i] == t[j]) {
                j--;
            }
            suf[i] = j + 1;
        }

        int[] ans = new int[m];
        boolean changed = false; // 是否修改过
        j = 0;
        for (int i = 0; i < n; i++) {
            if (s[i] == t[j] || !changed && suf[i + 1] <= j + 1) {
                if (s[i] != t[j]) {
                    changed = true;
                }
                ans[j++] = i;
                if (j == m) {
                    return ans;
                }
            }
        }
        return new int[]{};
    }
}

3303. 第一个几乎相等子字符串的下标

给你两个字符串 s 和 pattern 。如果一个字符串 x 修改 至多 一个字符会变成 y ,那么我们称它与 y 几乎相等 。

请你返回 s 中下标 最小 的 子字符串,它与 pattern 几乎相等 。如果不存在,返回 -1 。

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

示例 1:

输入:s = "abcdefg", pattern = "bcdffg"

输出:1

解释:将子字符串 s[1..6] == "bcdefg" 中 s[4] 变为 "f" ,得到 "bcdffg" 。

示例 2:

输入:s = "ababbababa", pattern = "bacaba"

输出:4

解释:将子字符串 s[4..9] == "bababa" 中 s[6] 变为 "c" ,得到 "bacaba" 。

示例 3:

输入:s = "abcd", pattern = "dba"

输出:-1

示例 4:

输入:s = "dde", pattern = "d"

输出:0

提示:

  • 1 <= pattern.length < s.length <= 3 * 10^5
  • s 和 pattern 都只包含小写英文字母。

思路:思路和周赛第三题一样,采用前后缀分解解决。

现在变成了两个问题:

第一个问题:对于每个从 s[i] 开始的字符串 s[i..],计算它能匹配 pattern 多长的前缀。
第二个问题:对于每个以 s[j] 结尾的字符串 s[..j],计算它能匹配 pattern 多长的后缀。
比如示例 1,s=abcdefg, pattern=bcdffg,其中:

s[1..] 可以匹配 pattern 长为 3 的前缀。
s[..6] 可以匹配 pattern 长为 2 的后缀。
那么 3+2=5 等于 pattern 的长度减一,我们可以修改一个字母使得 s[1..6] 与 pattern 相等。

对于第一个问题,我们可以构造字符串 pattern+s,计算其 Z 数组 preZ。那么 s[i..] 与 pattern 前缀可以匹配的最长长度为 preZ[i+m],其中 m 为 pattern 的长度。

对于第二个问题,我们可以构造字符串 rev(pattern)+rev(s),计算其 Z 数组,再反转 Z 数组,得到 sufZ。其中 rev(s) 表示 s 反转后的字符串。那么 s[..j] 与 pattern 后缀可以匹配的最长长度为 sufZ[j]。

设 n 为 s 的长度,m 为 pattern 的长度。

回到原问题,我们枚举 i=0,1,⋯,n−m,那么当前需要匹配的子串为 s[i..i+m−1],对应的 Z 数组元素为 preZ[i+m] 和 sufZ[i+m−1]。

如果preZ[i+m]+sufZ[i+m−1]≥m−1,那么答案为 i。

代码实现时,可以改成枚举 i=m,m+1,⋯,n,这样上面的式子就可以简化为

                                                        preZ[i]+sufZ[i−1]≥m−1
答案为 i−m。

如果没有找到匹配,返回 −1。

代码:

class Solution {
    public int minStartingIndex(String s, String pattern) {
        int[] preZ = calcZ(pattern + s);
        int[] sufZ = calcZ(rev(pattern) + rev(s));
        // 可以不反转 sufZ,下面写 sufZ[sufZ.length - i]
        int n = s.length();
        int m = pattern.length();
        for (int i = m; i <= n; i++) {
            if (preZ[i] + sufZ[sufZ.length - i] >= m - 1) {
                return i - m;
            }
        }
        return -1;
    }

    private int[] calcZ(String S) {
        char[] s = S.toCharArray();
        int n = s.length;
        int[] z = new int[n];
        int boxL = 0;
        int boxR = 0; // z-box 左右边界
        for (int i = 1; i < n; i++) {
            if (i <= boxR) {
                z[i] = Math.min(z[i - boxL], boxR - i + 1);
            }
            while (i + z[i] < n && s[z[i]] == s[i + z[i]]) {
                boxL = i;
                boxR = i + z[i];
                z[i]++;
            }
        }
        return z;
    }

    private String rev(String s) {
        return new StringBuilder(s).reverse().toString();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值