1387.将整数按权重排序

目标

我们将整数 x 的 权重 定义为按照下述规则将 x 变成 1 所需要的步数:

  • 如果 x 是偶数,那么 x = x / 2
  • 如果 x 是奇数,那么 x = 3 * x + 1

比方说,x=3 的权重为 7 。因为 3 需要 7 步变成 1 (3 --> 10 --> 5 --> 16 --> 8 --> 4 --> 2 --> 1)。

给你三个整数 lo, hi 和 k 。你的任务是将区间 [lo, hi] 之间的整数按照它们的权重 升序排序 ,如果大于等于 2 个整数有 相同 的权重,那么按照数字自身的数值 升序排序 。

请你返回区间 [lo, hi] 之间的整数按权重排序后的第 k 个数。

注意,题目保证对于任意整数 x (lo <= x <= hi) ,它变成 1 所需要的步数是一个 32 位有符号整数。

示例 1:

输入:lo = 12, hi = 15, k = 2
输出:13
解释:12 的权重为 9(12 --> 6 --> 3 --> 10 --> 5 --> 16 --> 8 --> 4 --> 2 --> 1)
13 的权重为 9
14 的权重为 17
15 的权重为 17
区间内的数按权重排序以后的结果为 [12,13,14,15] 。对于 k = 2 ,答案是第二个整数也就是 13 。
注意,12 和 13 有相同的权重,所以我们按照它们本身升序排序。14 和 15 同理。

示例 2:

输入:lo = 7, hi = 11, k = 4
输出:7
解释:区间内整数 [7, 8, 9, 10, 11] 对应的权重为 [16, 3, 19, 6, 14] 。
按权重排序后得到的结果为 [8, 10, 11, 7, 9] 。
排序后数组中第 4 个数字为 7 。

说明:

1 <= lo <= hi <= 1000
1 <= k <= hi - lo + 1

思路

定义整数 x 的权重为 将其变为 1 的操作次数,根据整数的奇偶性,可以执行不同的操作:

  • x 为偶数,x -> x / 2
  • x 为奇数,x -> 3 * x + 1

返回区间 [lo, hi] 之间的整数按权重排序后的第 k 个数。

根据题意模拟计算出每个数字的权重,将它和数字一起保存起来,然后按照权重、数值排序即可。

可以预处理 1 ~ 1000 内的所有权重,保存中间结果减少重复计算。

看到题目时我们都会有这样的疑问,如何证明 x 最终都会回到 1?有网友提到题目中的操作与考拉兹猜想(Collatz conjecture)的操作一样,由于操作过程与冰雹的形成和下落过程相似,因此也叫冰雹猜想。

代码


/**
 * @date 2024-12-22 16:20
 */
public class GetKth1387 {

    public int getKth(int lo, int hi, int k) {
        int n = hi - lo + 1;
        int[][] w = new int[n][2];
        int c = 0;
        for (int i = lo; i <= hi; i++) {
            w[c++] = new int[]{getWeight(i), i};
        }
        Arrays.sort(w, (a, b) -> {
            int compare = a[0] - b[0];
            if (compare != 0) {
                return compare;
            }
            return a[1] - b[1];
        });
        return w[k - 1][1];
    }

    public int getWeight(int x) {
        int cnt = 0;
        while (x > 1) {
            if (x % 2 == 0) {
                x >>= 1;
            } else {
                x = 3 * x + 1;
            }
            cnt++;
        }
        return cnt;
    }
}

性能

2545.根据第K场考试的分数排序

目标

班里有 m 位学生,共计划组织 n 场考试。给你一个下标从 0 开始、大小为 m x n 的整数矩阵 score ,其中每一行对应一位学生,而 score[i][j] 表示第 i 位学生在第 j 场考试取得的分数。矩阵 score 包含的整数 互不相同 。

另给你一个整数 k 。请你按第 k 场考试分数从高到低完成对这些学生(矩阵中的行)的排序。

返回排序后的矩阵。

示例 1:

输入:score = [[10,6,9,1],[7,5,11,2],[4,8,3,15]], k = 2
输出:[[7,5,11,2],[10,6,9,1],[4,8,3,15]]
解释:在上图中,S 表示学生,E 表示考试。
- 下标为 1 的学生在第 2 场考试取得的分数为 11 ,这是考试的最高分,所以 TA 需要排在第一。
- 下标为 0 的学生在第 2 场考试取得的分数为 9 ,这是考试的第二高分,所以 TA 需要排在第二。
- 下标为 2 的学生在第 2 场考试取得的分数为 3 ,这是考试的最低分,所以 TA 需要排在第三。

示例 2:

输入:score = [[3,4],[5,6]], k = 0
输出:[[5,6],[3,4]]
解释:在上图中,S 表示学生,E 表示考试。
- 下标为 1 的学生在第 0 场考试取得的分数为 5 ,这是考试的最高分,所以 TA 需要排在第一。
- 下标为 0 的学生在第 0 场考试取得的分数为 3 ,这是考试的最低分,所以 TA 需要排在第二。

说明:

  • m == score.length
  • n == score[i].length
  • 1 <= m, n <= 250
  • 1 <= score[i][j] <= 10^5
  • score 由 不同 的整数组成
  • 0 <= k < n

思路

有一个二维矩阵 score[i][j],根据第 k 列的值进行排序,返回排序后的数组。

直接调用 API 就是一个简单题,本题应该是考察手写排序吧。

// todo

代码


/**
 * @date 2024-12-21 17:31
 */
public class SortTheStudents2545 {

    public int[][] sortTheStudents(int[][] score, int k) {
        Arrays.sort(score, (a, b) -> b[k] - a[k]);
        return score;
    }
}

性能

3138.同位字符串连接的最小长度

目标

给你一个字符串 s ,它由某个字符串 t 和若干 t 的 同位字符串 连接而成。

请你返回字符串 t 的 最小 可能长度。

同位字符串 指的是重新排列一个单词得到的另外一个字符串,原来字符串中的每个字符在新字符串中都恰好只使用一次。

示例 1:

输入:s = "abba"
输出:2
解释:
一个可能的字符串 t 为 "ba" 。

示例 2:

输入:s = "cdef"
输出:4
解释:
一个可能的字符串 t 为 "cdef" ,注意 t 可能等于 s 。

说明:

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

思路

字符串 s 由某个字符串 t 以及若干(可以为0) t 的同位字符串 连接 而成,返回字符串 t 最小的可能长度。同位字符串指构成字符串的字符分布完全相同,换句话说就是不同字符的种类与数量完全相同。

特别注意该题与字串的顺序有关,比如 aabb 并不能由 ab 拼接而来,它的同位字符串是 abba,只能构成 abba abab baab baba

注意到子串的长度 length 一定能够被 s.length 整除。将字符串截成 k 个长度为 length 的子字符串,通过计算这些子字符串的字母个数,判断是否是同位字符串,从小到大遍历因数 length,取最小的即可。

代码


/**
 * @date 2024-12-20 9:08
 */
public class MinAnagramLength3138 {

    public int minAnagramLength_v1(String s) {
        int n = s.length();
        int[] cnt = new int[26];
        Map<Integer, int[]> possibleLength = new LinkedHashMap<>();
        for (int i = 0; i < n; i++) {
            int c = s.charAt(i) - 'a';
            cnt[c]++;
            int length = i + 1;
            if (n % length == 0) {
                int[] composition = new int[26];
                System.arraycopy(cnt, 0, composition, 0, 26);
                possibleLength.put(length, composition);
            }
        }
        char[] chars = s.toCharArray();
        for (Map.Entry<Integer, int[]> entry : possibleLength.entrySet()) {
            int length = entry.getKey();
            if (length == n) {
                return n;
            }
            int[] composition = entry.getValue();
            int loop = n / length;
            boolean find = true;
            here:
            for (int i = 0; i < loop; i++) {
                int[] tmp = new int[26];
                System.arraycopy(composition, 0, tmp, 0, 26);
                for (int j = 0; j < length; j++) {
                    int c = chars[i * length + j] - 'a';
                    tmp[c]--;
                    if (tmp[c] < 0) {
                        find = false;
                        break here;
                    }
                }
            }
            if (find) {
                return length;
            }
        }
        return n;
    }

}

性能

3285.找到稳定山的下标

目标

有 n 座山排成一列,每座山都有一个高度。给你一个整数数组 height ,其中 height[i] 表示第 i 座山的高度,再给你一个整数 threshold 。

对于下标不为 0 的一座山,如果它左侧相邻的山的高度 严格大于 threshold ,那么我们称它是 稳定 的。我们定义下标为 0 的山 不是 稳定的。

请你返回一个数组,包含所有 稳定 山的下标,你可以以 任意 顺序返回下标数组。

示例 1:

输入:height = [1,2,3,4,5], threshold = 2
输出:[3,4]
解释:
下标为 3 的山是稳定的,因为 height[2] == 3 大于 threshold == 2 。
下标为 4 的山是稳定的,因为 height[3] == 4 大于 threshold == 2.

示例 2:

输入:height = [10,1,10,1,10], threshold = 3
输出:[1,3]

示例 3:

输入:height = [10,1,10,1,10], threshold = 10
输出:[]

说明:

  • 2 <= n == height.length <= 100
  • 1 <= height[i] <= 100
  • 1 <= threshold <= 100

思路

返回数组的稳定下标,如果一个元素的左侧相邻元素严格大于 threshold 称当前元素是稳定的。

代码


/**
 * @date 2024-12-19 21:50
 */
public class StableMountains3285 {

    public List<Integer> stableMountains(int[] height, int threshold) {
        List<Integer> res = new ArrayList<>();
        int prev = height[0];
        int n = height.length;
        for (int i = 1; i < n; i++) {
            if (prev > threshold) {
                res.add(i);
            }
            prev = height[i];
        }
        return res;
    }
}

性能

3292.形成目标字符串需要的最少字符串数II

目标

给你一个字符串数组 words 和一个字符串 target。

如果字符串 x 是 words 中 任意 字符串的 前缀,则认为 x 是一个 有效 字符串。

现计划通过 连接 有效字符串形成 target ,请你计算并返回需要连接的 最少 字符串数量。如果无法通过这种方式形成 target,则返回 -1。

示例 1:

输入: words = ["abc","aaaaa","bcdef"], target = "aabcdabc"
输出: 3
解释:
target 字符串可以通过连接以下有效字符串形成:
words[1] 的长度为 2 的前缀,即 "aa"。
words[2] 的长度为 3 的前缀,即 "bcd"。
words[0] 的长度为 3 的前缀,即 "abc"。

示例 2:

输入: words = ["abababab","ab"], target = "ababaababa"
输出: 2
解释:
target 字符串可以通过连接以下有效字符串形成:
words[0] 的长度为 5 的前缀,即 "ababa"。
words[0] 的长度为 5 的前缀,即 "ababa"。

示例 3:

输入: words = ["abcdef"], target = "xyz"
输出: -1

说明:

  • 1 <= words.length <= 100
  • 1 <= words[i].length <= 5 * 10^4
  • 输入确保 sum(words[i].length) <= 10^5.
  • words[i] 只包含小写英文字母。
  • 1 <= target.length <= 5 * 10^4
  • target 只包含小写英文字母。

思路

// todo

代码

性能

3291.形成目标字符串需要的最少字符串数I

目标

给你一个字符串数组 words 和一个字符串 target。

如果字符串 x 是 words 中 任意 字符串的 前缀,则认为 x 是一个 有效 字符串。

现计划通过 连接 有效字符串形成 target ,请你计算并返回需要连接的 最少 字符串数量。如果无法通过这种方式形成 target,则返回 -1。

示例 1:

输入: words = ["abc","aaaaa","bcdef"], target = "aabcdabc"
输出: 3
解释:
target 字符串可以通过连接以下有效字符串形成:
words[1] 的长度为 2 的前缀,即 "aa"。
words[2] 的长度为 3 的前缀,即 "bcd"。
words[0] 的长度为 3 的前缀,即 "abc"。

示例 2:

输入: words = ["abababab","ab"], target = "ababaababa"
输出: 2
解释:
target 字符串可以通过连接以下有效字符串形成:
words[0] 的长度为 5 的前缀,即 "ababa"。
words[0] 的长度为 5 的前缀,即 "ababa"。

示例 3:

输入: words = ["abcdef"], target = "xyz"
输出: -1

说明:

  • 1 <= words.length <= 100
  • 1 <= words[i].length <= 5 * 10^3
  • 输入确保 sum(words[i].length) <= 10^5。
  • words[i] 只包含小写英文字母。
  • 1 <= target.length <= 5 * 10^3
  • target 只包含小写英文字母。

思路

有一个字符串数组 words 和目标字符串 target,请你使用最少的字符串前缀组成 target,返回需要的字符串数量,如果无法组成 target 返回 -1

注意前缀是允许重复使用的,状态个数为 target.length ^ 2,深度为 100,直接使用记忆化搜索会超时。

使用字典树加动态规划可以勉强通过,但是明天的通过不了。

//todo KMP 算法 Z 函数 / 字符串哈希+二分 / AC 自动机

代码


/**
 * @date 2024-12-17 8:59
 */
public class MinValidStrings3291 {

    static public class Trie {
        public boolean isLeaf;
        public Trie[] children;

        public Trie() {
            this.children = new Trie[26];
        }

        public Trie build(String[] dict) {
            Trie root = this;
            for (String word : dict) {
                root = this;
                char[] chars = word.toCharArray();
                for (int i = 0; i < chars.length; i++) {
                    int c = chars[i] - 'a';
                    if (root.children[c] == null) {
                        root.children[c] = new Trie();
                    }
                    root = root.children[c];
                }
                root.isLeaf = true;
            }
            return root;
        }

        public int exists(char[] target, int start) {
            int n = target.length;
            int length = 0;
            Trie root = this;
            int c = target[start] - 'a';
            while (root.children[c] != null) {
                root = root.children[c];
                length++;
                start++;
                if (start == n) {
                    break;
                }
                c = target[start] - 'a';
            }
            return length;
        }

    }

    public int minValidStrings_v1(String[] words, String target) {
        Trie root = new Trie();
        root.build(words);
        char[] chars = target.toCharArray();
        int n = chars.length;
        int[] dp = new int[n];
        Arrays.fill(dp, Integer.MAX_VALUE);
        int length = root.exists(chars, 0);
        for (int i = 0; i < length; i++) {
            dp[i] = 1;
        }

        for (int i = 1; i < n; i++) {
            if (dp[i - 1] == Integer.MAX_VALUE) {
                return -1;
            }
            length = root.exists(chars, i);
            for (int j = i; j < i + length; j++) {
                dp[j] = Math.min(dp[j], dp[i - 1] + 1);
            }
        }

        return dp[n - 1] == Integer.MAX_VALUE ? -1 : dp[n - 1];
    }

}

性能

1847.最近的房间

目标

一个酒店里有 n 个房间,这些房间用二维整数数组 rooms 表示,其中 rooms[i] = [roomIdi, sizei] 表示有一个房间号为 roomIdi 的房间且它的面积为 sizei 。每一个房间号 roomIdi 保证是 独一无二 的。

同时给你 k 个查询,用二维数组 queries 表示,其中 queries[j] = [preferredj, minSizej] 。第 j 个查询的答案是满足如下条件的房间 id :

  • 房间的面积 至少 为 minSizej ,且
  • abs(id - preferredj) 的值 最小 ,其中 abs(x) 是 x 的绝对值。

如果差的绝对值有 相等 的,选择 最小 的 id 。如果 没有满足条件的房间 ,答案为 -1 。

请你返回长度为 k 的数组 answer ,其中 answer[j] 为第 j 个查询的结果。

示例 1:

输入:rooms = [[2,2],[1,2],[3,2]], queries = [[3,1],[3,3],[5,2]]
输出:[3,-1,3]
解释:查询的答案如下:
查询 [3,1] :房间 3 的面积为 2 ,大于等于 1 ,且号码是最接近 3 的,为 abs(3 - 3) = 0 ,所以答案为 3 。
查询 [3,3] :没有房间的面积至少为 3 ,所以答案为 -1 。
查询 [5,2] :房间 3 的面积为 2 ,大于等于 2 ,且号码是最接近 5 的,为 abs(3 - 5) = 2 ,所以答案为 3 。

示例 2:

输入:rooms = [[1,4],[2,3],[3,5],[4,1],[5,2]], queries = [[2,3],[2,4],[2,5]]
输出:[2,1,3]
解释:查询的答案如下:
查询 [2,3] :房间 2 的面积为 3 ,大于等于 3 ,且号码是最接近的,为 abs(2 - 2) = 0 ,所以答案为 2 。
查询 [2,4] :房间 1 和 3 的面积都至少为 4 ,答案为 1 因为它房间编号更小。
查询 [2,5] :房间 3 是唯一面积大于等于 5 的,所以答案为 3 。

说明:

  • n == rooms.length
  • 1 <= n <= 10^5
  • k == queries.length
  • 1 <= k <= 10^4
  • 1 <= roomIdi, preferredj <= 10^7
  • 1 <= sizei, minSizej <= 10^7

思路

有一个数组 roomsrooms[i][0] 表示第 i 个房间编号,房间编号不重复,rooms[i][1] 表示第 i 个房间大小。有一个查询数组 queriesqueries[j][0] 表示第 j 个查询期望的房间编号queries[j][1] 表示第 j 个查询最小的房间大小。返回查询数组对应的结果数组,查询结果为房间编号,该房间的面积至少为 queries[j][1],且房间编号与 queries[j][0] 的距离最小,如果存在距离相等的情况,取房间编号最小的。

首先按房间大小排序,大小相同的按编号排序。对于每个查询首先二分查找出第一个大于 queries[j][1] 的房间在数组中的位置,接下来需要从该位置往后计算距离 queries[j][0] 最近的房间编号。

// todo 官网题解 Bentley Ottmann, Sparse Table 倍增 RMQ,Range Maximum/Minimum Query

代码


/**
 * @date 2024-12-16 16:23
 */
public class ClosestRoom1847 {

    public int[] closestRoom(int[][] rooms, int[][] queries) {
        Arrays.sort(rooms, (a, b) -> {
            int compare = a[1] - b[1];
            if (compare != 0) {
                return compare;
            }
            return a[0] - b[0];
        });
        int n = rooms.length;
        int k = queries.length;
        int[] res = new int[k];
        int i = 0;
        for (int[] query : queries) {
            int minAreaRoomIndex = lowerBound(rooms, 0, n - 1, query[1]);
            if (minAreaRoomIndex == n) {
                res[i++] = -1;
                continue;
            }
            int dist = Integer.MAX_VALUE;
            int roomId = Integer.MAX_VALUE;
            for (int j = minAreaRoomIndex; j < n; j++) {
                int tmp = Math.abs(query[0] - rooms[j][0]);
                if (tmp < dist) {
                    dist = tmp;
                    roomId = rooms[j][0];
                } else if (tmp == dist) {
                    roomId = Math.min(rooms[j][0], roomId);
                }
            }
            res[i++] = roomId;
        }

        return res;
    }

    public int lowerBound(int[][] rooms, int l, int r, int target) {
        int m = l + ((r - l) >> 1);
        while (l <= r) {
            if (rooms[m][1] >= target) {
                r = m - 1;
            } else {
                l = m + 1;
            }
            m = l + ((r - l) >> 1);
        }
        return l;
    }

}

性能

1338.数组大小减半

目标

给你一个整数数组 arr。你可以从中选出一个整数集合,并删除这些整数在数组中的每次出现。

返回 至少 能删除数组中的一半整数的整数集合的最小大小。

示例 1:

输入:arr = [3,3,3,3,5,5,5,2,2,7]
输出:2
解释:选择 {3,7} 使得结果数组为 [5,5,5,2,2]、长度为 5(原数组长度的一半)。
大小为 2 的可行集合有 {3,5},{3,2},{5,2}。
选择 {2,7} 是不可行的,它的结果数组为 [3,3,3,3,5,5,5],新数组长度大于原数组的二分之一。

示例 2:

输入:arr = [7,7,7,7,7,7]
输出:1
解释:我们只能选择集合 {7},结果数组为空。

说明:

  • 1 <= arr.length <= 10^5
  • arr.length 为偶数
  • 1 <= arr[i] <= 10^5

思路

从整数数组中选出一个元素集合,使该集合中元素在原数组中的出现次数超过原数组长度的一半,求集合大小的最小值。

统计每个元素的出现次数,将出现次数从大到小排序,然后开始选元素直到满足题目条件。

代码


/**
 * @date 2024-12-15 0:17
 */
public class MinSetSize1338 {

    public int minSetSize(int[] arr) {
        int n = arr.length;
        int[] cnt = new int[100001];
        for (int i : arr) {
            cnt[i]++;
        }
        PriorityQueue<Integer> q = new PriorityQueue<>((a, b) -> b - a);
        for (int i : cnt) {
            if (i > 0) {
                q.offer(i);
            }
        }
        int res = 0;
        int l = 0;
        while (!q.isEmpty()) {
            l += q.poll();
            res++;
            if (l >= n / 2) {
                break;
            }
        }
        return res;
    }
}

性能

3266.K次乘运算后的最终数组II

目标

给你一个整数数组 nums ,一个整数 k 和一个整数 multiplier 。

你需要对 nums 执行 k 次操作,每次操作中:

  • 找到 nums 中的 最小 值 x ,如果存在多个最小值,选择最 前面 的一个。
  • 将 x 替换为 x * multiplier 。

k 次操作以后,你需要将 nums 中每一个数值对 10^9 + 7 取余。

请你返回执行完 k 次乘运算以及取余运算之后,最终的 nums 数组。

示例 1:

输入:nums = [2,1,3,5,6], k = 5, multiplier = 2
输出:[8,4,6,5,6]
解释:
操作 结果
1 次操作后 [2, 2, 3, 5, 6]
2 次操作后 [4, 2, 3, 5, 6]
3 次操作后 [4, 4, 3, 5, 6]
4 次操作后 [4, 4, 6, 5, 6]
5 次操作后 [8, 4, 6, 5, 6]
取余操作后 [8, 4, 6, 5, 6]

示例 2:

输入:nums = [100000,2000], k = 2, multiplier = 1000000
输出:[999999307,999999993]
解释:
操作 结果
1 次操作后 [100000, 2000000000]
2 次操作后 [100000000000, 2000000000]
取余操作后 [999999307, 999999993]

说明:

  • 1 <= nums.length <= 10^4
  • 1 <= nums[i] <= 10^9
  • 1 <= k <= 10^9
  • 1 <= multiplier <= 10^6

思路

有一个数组 nums,我们需要执行 k 次操作,每次操作选择数组中最小元素 min,并将它的值替换为 min * multiplier,返回最终的数组。数据范围比 3264.K次乘运算后的最终数组I 大,multiplier 也大,会溢出,需要进行取余运算。

首先 k 最大 10^9,还沿用昨天模拟的解法会超时。更重要的是,由于乘积很大,我们只能在队列中保存取余后的数据,如果还按找之前模拟来取最小元素就不对了。

我们发现,当执行一些次操作之后,所有元素都会被乘以 multiplier,当 k / n 比较大时,我们可以使用快速幂先计算出 multiplierk/n 幂,然后再与元素相乘。

关键在于何时开始使用上面的思路来计算,考虑 1 2 4 8 16multiplier2,k 为 20

2   2   4   8   16
4   2   4   8   16
4   4   4   8   16
8   4   4   8   16
8   8   4   8   16
8   8   8   8   16
16  8   8   8   16
16  16  8   8   16
16  16  16  8   16
16  16  16  16  16

可以发现 当前数组 最小值 乘以 multiplier 大于 原数组 元素的 最大值 时,后面再乘以 multiplier 就是每一个元素执行一次了。

因此我们需要先使用堆模拟前面几次操作,直到满足上面的条件。注意:堆中数据不能取模,满足条件之前堆中数据使用 long 型不会溢出。

代码


/**
 * @date 2024-12-14 10:31
 */
public class GetFinalState3266 {

    public int[] getFinalState(int[] nums, int k, int multiplier) {
        if (multiplier == 1) {
            return nums;
        }
        int mod = 1000000007;
        int n = nums.length;
        long[] mul = new long[n];
        for (int i = 0; i < n; i++) {
            mul[i] = nums[i];
        }
        PriorityQueue<Integer> q = new PriorityQueue<>((a, b) -> {
            long compare = mul[a] - mul[b];
            if (compare != 0) {
                return (int) compare;
            }
            return a - b;
        });
        long max = 0;
        for (int i = 0; i < n; i++) {
            q.offer(i);
            max = Math.max(max, nums[i]);
        }
        int i = 0;
        for (; i < k; i++) {
            if (mul[q.peek()] * (long) multiplier > max) {
                break;
            }
            Integer index = q.poll();
            mul[index] = mul[index] * multiplier;
            q.offer(index);
        }
        int remainder = k - i;
        if (remainder >= n) {
            long pow = pow(multiplier, remainder / n);
            for (int j = 0; j < n; j++) {
                Integer index = q.poll();
                int rem = remainder % n;
                mul[index] = (int) ((mul[index] * pow % mod * (j < rem ? (long) multiplier : 1)) % mod);
            }
        } else {
            for (int j = 0; j < remainder; j++) {
                Integer index = q.poll();
                mul[index] = (int) ((mul[index] * (long) multiplier) % mod);
                q.offer(index);
            }
        }
        for (int j = 0; j < n; j++) {
            nums[j] = (int) mul[j];
        }
        return nums;
    }

    public long pow(long base, int exponent) {
        long res = 1;
        int mod = 1000000007;
        while (exponent > 0) {
            if ((exponent & 1) == 1) {
                res = res * base % mod;
            }
            base = base * base % mod;
            exponent >>= 1;
        }
        return res;
    }

}

性能

3264.K次乘运算后的最终数组I

目标

给你一个整数数组 nums ,一个整数 k 和一个整数 multiplier 。

你需要对 nums 执行 k 次操作,每次操作中:

  • 找到 nums 中的 最小 值 x ,如果存在多个最小值,选择最 前面 的一个。
  • 将 x 替换为 x * multiplier 。

请你返回执行完 k 次乘运算之后,最终的 nums 数组。

示例 1:

输入:nums = [2,1,3,5,6], k = 5, multiplier = 2
输出:[8,4,6,5,6]
解释:
操作 结果
1 次操作后 [2, 2, 3, 5, 6]
2 次操作后 [4, 2, 3, 5, 6]
3 次操作后 [4, 4, 3, 5, 6]
4 次操作后 [4, 4, 6, 5, 6]
5 次操作后 [8, 4, 6, 5, 6]

示例 2:

输入:nums = [1,2], k = 3, multiplier = 4
输出:[16,8]
解释:
操作 结果
1 次操作后 [4, 2]
2 次操作后 [4, 8]
3 次操作后 [16, 8]

说明:

  • 1 <= nums.length <= 100
  • 1 <= nums[i] <= 100
  • 1 <= k <= 10
  • 1 <= multiplier <= 5

思路

有一个数组 nums,我们需要执行 k 次操作,每次操作选择数组中最小元素 min,并将它的值替换为 min * multiplier,返回最终的数组。

使用最小堆,堆中元素为 [value, index],获取堆顶元素,将其值乘以 multiplier 再放回堆中,操作完 k 次之后,遍历堆中元素,根据 index 重写 nums 即可。需要注意处理值相等的情况,堆排序不稳定。

代码


/**
 * @date 2024-12-13 2:13
 */
public class GetFinalState3264 {

    public int[] getFinalState(int[] nums, int k, int multiplier) {
        int n = nums.length;
        // 注意这里需要处理相等的情况,堆排序是不稳定的
        PriorityQueue<Integer> q = new PriorityQueue<>((a, b) -> {
            int compare = nums[a] - nums[b];
            if (compare != 0){
                return compare;
            }
            return a - b;
        });
        for (int i = 0; i < n; i++) {
            q.offer(i);
        }
        for (int i = 0; i < k; i++) {
            int index = q.poll();
            nums[index] *= multiplier;
            q.offer(index);
        }
        return nums;
    }
}

性能