【题解】—— LeetCode一周小结51

🌟欢迎来到 我的博客 —— 探索技术的无限可能!


🌟博客的简介(文章目录)


【题解】—— 每日一道题目栏


上接:【题解】—— LeetCode一周小结50

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

题目链接: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 * 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);
    }
} 

下接:【题解】—— LeetCode一周小结52


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZShiJ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值