100106. Minimum Sum of Mountain Triplets I

给你一个下标从 0 开始的整数数组 nums 。

如果下标三元组 (i, j, k) 满足下述全部条件,则认为它是一个 山形三元组 :

  • i < j < k
  • nums[i] < nums[j] 且 nums[k] < nums[j]

请你找出 nums 中 元素和最小 的山形三元组,并返回其 元素和 。如果不存在满足条件的三元组,返回 -1 。

测试样例:

输入:nums = [8,6,1,5,3]

输出:9

解释:
三元组 (2, 3, 4) 是一个元素和等于 9 的山形三元组,因为:

  • 2 < 3 < 4
  • nums[2] < nums[3] 且 nums[4] < nums[3]

这个三元组的元素和等于 nums[2] + nums[3] + nums[4] = 9 。可以证明不存在元素和小于 9 的山形三元组。

解答:第一题和第二题一样,我也就只写一题题解了。因为需要寻找三元组,有需要最小山形三元组,所以我们可以想到利用TreeMap来记录数字左右两边的最小值情况。我的习惯是从右到左遍历,这样,当前数字右边可以用一个数字记录最小,右边利用TreeMap动态更新数组排序情况。

class Solution {
    public int minimumSum(int[] nums) {
        TreeMap<Integer, Integer> left = new TreeMap<>();
        int min = Integer.MAX_VALUE;
        for (int i = 0; i < nums.length; ++i) {
            left.put(nums[i], left.getOrDefault(nums[i], 0) + 1);
        }
        int res = Integer.MAX_VALUE;
        for (int i = nums.length - 1; i >= 1; --i) {
            min = Math.min(min, nums[i]);
            int count = left.get(nums[i]);
            if (count == 1) {
                left.remove(nums[i]);
            } else {
                left.put(nums[i], count - 1);
            }

            int leftMin = left.firstKey();
            if (nums[i] > leftMin && nums[i] > min) {
                res = Math.min(res, nums[i] + leftMin + min);
            }
        }
        return res == Integer.MAX_VALUE ? -1 : res;
    }
}

100097. Minimum Number of Groups to Create a Valid Assignment

给你一个长度为 n 下标从 0 开始的整数数组 nums 。

我们想将下标进行分组,使得 [0, n - 1] 内所有下标 i 都 恰好 被分到其中一组。

如果以下条件成立,我们说这个分组方案是合法的:

  • 对于每个组 g ,同一组内所有下标在 nums 中对应的数值都相等。
  • 对于任意两个组 g1 和 g2 ,两个组中 下标数量 的 差值不超过 1 。

请你返回一个整数,表示得到一个合法分组方案的 最少 组数。

测试样例:

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

输出:2

解释:
一个得到 2 个分组的方案如下,中括号内的数字都是下标:

  • 组 1 -> [0,2,4]
  • 组 2 -> [1,3]

所有下标都只属于一个组。

  • 组 1 中,nums[0] == nums[2] == nums[4] ,所有下标对应的数值都相等。
  • 组 2 中,nums[1] == nums[3] ,所有下标对应的数值都相等。
  • 组 1 中下标数目为 3 ,组 2 中下标数目为 2 。
    两者之差不超过 1 。无法得到一个小于 2 组的答案,因为如果只有 1 组,组内所有下标对应的数值都要相等。所以答案为 2 。

解答:这道题目有一个小的暗含条件,1 <= nums.length <= 10^5,那么最多分组就是447个。利用HashMap聚合之后,是可以利用暴力计算的。组的最大规模就是最小频数的出现频次 + 1。然后判断一下每个数是否可以被完成计算即可(isValid函数)。

class Solution {
    public int minGroupsForValidAssignment(int[] nums) {
        HashMap<Integer, Integer> group = new HashMap<>();
        for (int n : nums) {
            group.put(n, group.getOrDefault(n, 0 ) + 1);
        }
        int max = Integer.MAX_VALUE;
        for (Map.Entry<Integer, Integer> kv : group.entrySet()) {
            max = Math.min(max, kv.getValue());
        }
        for (int i = max + 1; i > 1; --i) {
            if (isValid(max, i) != -1) {
                boolean isValid = true;
                int groupNum = 0;
                for (Map.Entry<Integer, Integer> kv : group.entrySet()) {
                    int tmp = isValid(kv.getValue(), i);
                    if (tmp == -1) {
                        isValid = false;
                        break;
                    } else {
                        groupNum += tmp;
                    }
                }
                if (isValid) {
                    return groupNum;
                }
            }
        }
        return nums.length;
    }

    private int isValid(int num, int max) {
        int y = num % max;
        if (y == 0) {
            return num / max;
        } else {
            y = max - y;
            if ((max - 1) * y > num) {
                return -1;
            } else {
                return y + (num - (max - 1) * y) / max;
            }
        }
    }
}

6920. Minimum Changes to Make K Semi-palindromes

给你一个字符串 s 和一个整数 k ,请你将 s 分成 k 个 子字符串 ,使得每个 子字符串 变成 半回文串 需要修改的字符数目最少。

请你返回一个整数,表示需要修改的 最少 字符数目。

注意:

  • 如果一个字符串从左往右和从右往左读是一样的,那么它是一个 回文串 。
  • 如果长度为 len 的字符串存在一个满足 1 <= d < len 的正整数 d ,len % d == 0 成立且所有对 d 做除法余数相同的下标对应的字符连起来得到的字符串都是 回文串 ,那么我们说这个字符串是 半回文串 。比方说 "aa" ,"aba" ,"adbgad" 和 "abab" 都是 半回文串 ,而 "a" ,"ab" 和 "abca" 不是。
  • 子字符串 指的是一个字符串中一段连续的字符序列。

测试样例

输入:s = "abcac", k = 2

输出:1

解释:
我们可以将 s 分成子字符串 "ab" 和 "cac" 。子字符串 "cac" 已经是半回文串。如果我们将 "ab" 变成 "aa" ,它也会变成一个 d = 1 的半回文串。该方案是将 s 分成 2 个子字符串的前提下,得到 2 个半回文子字符串需要的最少修改次数。所以答案为 1 。

解答:这道题目看着唬人,其实难度不大,基本每一步都能暴力。首先我们先记录一个二维数组,作用时计算i->j位,变成半回文串所需要的最小操作数(有len % d == 0这个条件,所以实际会进行计算的分支不多,可以暴力)。然后利用动态规划(我用了记忆搜索的写法)计算当前数组变成k个字串的最小操作数。

class Solution {
    private String s;
    private int[][] mem;
    private Integer[][] dp;

    public int minimumChanges(String s, int k) {
        int len = s.length();
        this.s = s;
        mem = new int[len][len];
        for (int i = 0; i < s.length(); ++i) {
            for (int j = i; j < s.length(); ++j) {
                mem[i][j] = minChange(i, j);
            }
        }
        dp = new Integer[len][k];
        return helper(0, k - 1);
    }

    private int minChange(int st, int ed) {
        if (st == ed) return -1;
        int len = ed - st + 1;
        int res = Integer.MAX_VALUE;
        for (int i = 1; i < len; ++i) {
            if (len % i == 0) {
                List<Character>[] lists = new List[i];
                for (int j = 0; j < i; ++j) {
                    lists[j] = new ArrayList<>();
                }
                for (int j = st; j <= ed; ++j) {
                    lists[(j - st) % i].add(s.charAt(j));
                }
                int cur = 0;
                for (List<Character> list : lists) {
                    cur += minChangeOneList(list);
                }
                res = Math.min(cur, res);
            }
        }
        return res;
    }

    private int minChangeOneList(List<Character> list) {
        int st = 0, ed = list.size() - 1;
        int res = 0;
        while (st < ed) {
            if (list.get(st) != list.get(ed)) {
                ++res;
            }
            ++st;
            --ed;
        }
        return res;
    }

    private int helper(int pos, int k) {
        if (k == 0) {
            if ((s.length() - pos) < 2 * (k + 1)) return -1;
            return mem[pos][s.length() - 1];
        } else if ((s.length() - pos) < 2 * (k + 1)) {
            return -1;
        } else if (dp[pos][k] == null) {
            dp[pos][k] = Integer.MAX_VALUE;
            for (int i = pos + 1; i < s.length(); ++i) {
                int tmp = helper(i + 1, k - 1);
                if (tmp != -1) {
                    dp[pos][k] = Math.min(dp[pos][k], tmp + mem[pos][i]);
                }
            }
            if (dp[pos][k] == Integer.MAX_VALUE) {
                dp[pos][k] = -1;
            }
        }
        return dp[pos][k];
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *