🌟欢迎来到 我的博客 —— 探索技术的无限可能!
16.最近的房间
题目链接: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 <= 105
k == queries.length
1 <= k <= 104
1 <= roomIdi, preferredj <= 107
1 <= sizei, minSizej <= 107
题解:
方法:离线+有序集合+双指针
class Solution {
public int[] closestRoom(int[][] rooms, int[][] queries) {
// 按照 size 从大到小排序
Arrays.sort(rooms, (a, b) -> (b[1] - a[1]));
int q = queries.length;
Integer[] queryIds = new Integer[q];
Arrays.setAll(queryIds, i -> i);
// 按照 minSize 从大到小排序
Arrays.sort(queryIds, (i, j) -> queries[j][1] - queries[i][1]);
int[] ans = new int[q];
Arrays.fill(ans, -1);
TreeSet<Integer> roomIds = new TreeSet<>();
int j = 0;
for (int i : queryIds) {
int preferredId = queries[i][0];
int minSize = queries[i][1];
while (j < rooms.length && rooms[j][1] >= minSize) {
roomIds.add(rooms[j][0]);
j++;
}
int diff = Integer.MAX_VALUE;
Integer floor = roomIds.floor(preferredId);
if (floor != null) {
diff = preferredId - floor; // 左边的差
ans[i] = floor;
}
Integer ceiling = roomIds.ceiling(preferredId);
if (ceiling != null && ceiling - preferredId < diff) { // 右边的差更小
ans[i] = ceiling;
}
}
return ans;
}
}
17.形成目标字符串需要的最少字符串数 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 * 103
输入确保 sum(words[i].length) <= 105。
words[i] 只包含小写英文字母。
1 <= target.length <= 5 * 103
target 只包含小写英文字母。
题解:
方法:字典树 + 记忆化搜索
class Trie {
Trie[] children = new Trie[26];
void insert(String w) {
Trie node = this;
for (int i = 0; i < w.length(); ++i) {
int j = w.charAt(i) - 'a';
if (node.children[j] == null) {
node.children[j] = new Trie();
}
node = node.children[j];
}
}
}
class Solution {
private Integer[] f;
private char[] s;
private Trie trie;
private final int inf = 1 << 30;
public int minValidStrings(String[] words, String target) {
trie = new Trie();
for (String w : words) {
trie.insert(w);
}
s = target.toCharArray();
f = new Integer[s.length];
int ans = dfs(0);
return ans < inf ? ans : -1;
}
private int dfs(int i) {
if (i >= s.length) {
return 0;
}
if (f[i] != null) {
return f[i];
}
Trie node = trie;
f[i] = inf;
for (int j = i; j < s.length; ++j) {
int k = s[j] - 'a';
if (node.children[k] == null) {
break;
}
f[i] = Math.min(f[i], 1 + dfs(j + 1));
node = node.children[k];
}
return f[i];
}
}
18.形成目标字符串需要的最少字符串数 II
题目链接: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 * 104
输入确保 sum(words[i].length) <= 105.
words[i] 只包含小写英文字母。
1 <= target.length <= 5 * 104
target 只包含小写英文字母。
题解:
方法:字符串哈希 + 二分查找 + 贪心
class Hashing {
private final long[] p;
private final long[] h;
private final long mod;
public Hashing(String word, long base, int mod) {
int n = word.length();
p = new long[n + 1];
h = new long[n + 1];
p[0] = 1;
this.mod = mod;
for (int i = 1; i <= n; i++) {
p[i] = p[i - 1] * base % mod;
h[i] = (h[i - 1] * base + word.charAt(i - 1)) % mod;
}
}
public long query(int l, int r) {
return (h[r] - h[l - 1] * p[r - l + 1] % mod + mod) % mod;
}
}
class Solution {
private Hashing hashing;
private Set<Long>[] s;
public int minValidStrings(String[] words, String target) {
int base = 13331, mod = 998244353;
hashing = new Hashing(target, base, mod);
int m = Arrays.stream(words).mapToInt(String::length).max().orElse(0);
s = new Set[m + 1];
Arrays.setAll(s, k -> new HashSet<>());
for (String w : words) {
long h = 0;
for (int j = 0; j < w.length(); j++) {
h = (h * base + w.charAt(j)) % mod;
s[j + 1].add(h);
}
}
int ans = 0;
int last = 0;
int mx = 0;
int n = target.length();
for (int i = 0; i < n; i++) {
int dist = f(i, n, m);
mx = Math.max(mx, i + dist);
if (i == last) {
if (i == mx) {
return -1;
}
last = mx;
ans++;
}
}
return ans;
}
private int f(int i, int n, int m) {
int l = 0, r = Math.min(n - i, m);
while (l < r) {
int mid = (l + r + 1) >> 1;
long sub = hashing.query(i + 1, i + mid);
if (s[mid].contains(sub)) {
l = mid;
} else {
r = mid - 1;
}
}
return l;
}
}
19.找到稳定山的下标
题目链接: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
题解:
方法:遍历
class Solution {
public List<Integer> stableMountains(int[] height, int threshold) {
List<Integer> ans = new ArrayList<>();
for (int i = 0; i < height.length - 1; i++) {
if (height[i] > threshold) {
ans.add(i + 1);
}
}
return ans;
}
}
20.同位字符串连接的最小长度
题目链接:3138. 同位字符串连接的最小长度
给你一个字符串 s ,它由某个字符串 t 和若干 t 的 同位字符串 连接而成。
请你返回字符串 t 的 最小 可能长度。
同位字符串 指的是重新排列一个字符串的字母得到的另外一个字符串。例如,“aab”,“aba” 和 “baa” 是 “aab” 的同位字符串。
示例 1:
输入:s = “abba”
输出:2
解释:
一个可能的字符串 t 为 “ba” 。
示例 2:
输入:s = “cdef”
输出:4
解释:
一个可能的字符串 t 为 “cdef” ,注意 t 可能等于 s 。
提示:
1 <= s.length <= 105
s 只包含小写英文字母。
题解:
方法:枚举长度
class Solution {
public int minAnagramLength(String S) {
char[] s = S.toCharArray();
int n = s.length;
next:
for (int k = 1; k <= n / 2; k++) {
if (n % k > 0) {
continue;
}
int[] cnt0 = new int[26];
for (int j = 0; j < k; j++) {
cnt0[s[j] - 'a']++;
}
for (int i = k * 2; i <= n; i += k) {
int[] cnt = new int[26];
for (int j = i - k; j < i; j++) {
cnt[s[j] - 'a']++;
}
if (!Arrays.equals(cnt, cnt0)) {
continue next;
}
}
return k;
}
return n;
}
}
方法:枚举次数 + GCD 优化
class Solution {
public int minAnagramLength(String S) {
char[] s = S.toCharArray();
int n = s.length;
int[] cntAll = new int[26];
for (char c : s) {
cntAll[c - 'a']++;
}
int g = 0;
for (int c : cntAll) {
g = gcd(g, c);
}
next:
for (int times = g; times > 1; times--) {
if (g % times > 0) {
continue;
}
int k = n / times;
int[] cnt0 = new int[26];
for (int j = 0; j < k; j++) {
cnt0[s[j] - 'a']++;
}
for (int i = k * 2; i <= n; i += k) {
int[] cnt = new int[26];
for (int j = i - k; j < i; j++) {
cnt[s[j] - 'a']++;
}
if (!Arrays.equals(cnt, cnt0)) {
continue next;
}
}
return k;
}
return n;
}
private int gcd(int a, int b) {
while (a != 0) {
int tmp = a;
a = b % a;
b = tmp;
}
return b;
}
}
21.根据第 K 场考试的分数排序
题目链接: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] <= 105
score 由 不同 的整数组成
0 <= k < n
题解:
方法:排序
class Solution {
public int[][] sortTheStudents(int[][] score, int k) {
Arrays.sort(score, (a, b) -> b[k] - a[k]);
return score;
}
}
22.将整数按权重排序
题目链接: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
题解:
方法:冰雹猜想,记忆化搜索
class Solution {
private static final Map<Integer, Integer> memo = new HashMap<>();
public int getKth(int lo, int hi, int k) {
Integer[] nums = new Integer[hi - lo + 1];
Arrays.setAll(nums, i -> i + lo);
Arrays.sort(nums, (x, y) -> dfs(x) - dfs(y)); // 稳定排序
return nums[k - 1];
}
private int dfs(int i) {
if (i == 1) {
return 0;
}
if (memo.containsKey(i)) { // 之前计算过
return memo.get(i);
}
if (i % 2 == 1) {
memo.put(i, dfs((i * 3 + 1) / 2) + 2);
} else {
memo.put(i, dfs(i / 2) + 1);
}
return memo.get(i);
}
}