动态规划不可怕,思路不清想到炸

LeetCode 第 207 场周赛

Posted by lyle on September 20, 2020

12 分,300+ 名

题目

  1. 重新排列单词间的空格
  2. 拆分字符串使唯一子字符串的数目最大
  3. 矩阵的最大非负积
  4. 连通两组点的最小成本

T1. 重新排列单词间的空格

题目

给你一个字符串 text ,该字符串由若干被空格包围的单词组成。每个单词由一个或者多个小写英文字母组成,并且两个单词之间至少存在一个空格。题目测试用例保证 text 至少包含一个单词

请你重新排列空格,使每对相邻单词之间的空格数目都 相等 ,并尽可能 最大化 该数目。如果不能重新平均分配所有空格,请 将多余的空格放置在字符串末尾 ,这也意味着返回的字符串应当与原 text 字符串的长度相等。

返回 重新排列空格后的字符串

示例 1:

输入:text = "  this   is  a sentence "
输出:"this   is   a   sentence"
解释:总共有 9 个空格和 4 个单词。可以将 9 个空格平均分配到相邻单词之间,相邻单词间空格数为:9 / (4-1) = 3 个。

示例 2:

输入:text = " practice   makes   perfect"
输出:"practice   makes   perfect "
解释:总共有 7 个空格和 3 个单词。7 / (3-1) = 3 个空格加上 1 个多余的空格。多余的空格需要放在字符串的末尾。

示例 3:

输入:text = "hello   world"
输出:"hello   world"

示例 4:

输入:text = "  walks  udp package   into  bar a"
输出:"walks  udp  package  into  bar  a "

示例 5:

输入:text = "a"
输出:"a"

提示:

  • 1 <= text.length <= 100
  • text 由小写英文字母和 ' ' 组成
  • text 中至少包含一个单词

题解思路

  • 提前算好间隔的空格数 each

参考代码

// bf
public String reorderSpaces(String text) {
    int blank = 0;
    for (char c : text.toCharArray())
        if (c == ' ') blank++;

    String[] arr = text.trim().split("\\s+");
    int word = arr.length;
    if (word == 1) {
        StringBuilder builder = new StringBuilder();
        builder.append(arr[0]);
        while (blank-- > 0)
            builder.append(" ");
        return builder.toString();
    }

    int each = blank / (word - 1);
    StringBuilder empty = new StringBuilder();
    for (int i = 0; i < each; i++)
        empty.append(" ");

    int rest = blank % (word - 1);
    StringBuilder restEmpty = new StringBuilder();
    for (int i = 0; i < rest; i++)
        restEmpty.append(" ");

    StringBuilder builder = new StringBuilder();
    for (int i = 0; i < word; i++) {
        builder.append(arr[i]);
        if (i != word - 1) builder.append(empty);
    }
    builder.append(restEmpty);
    return builder.toString();
}

复杂度分析

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

T2. 拆分字符串使唯一子字符串的数目最大

题目

给你一个字符串 s ,请你拆分该字符串,并返回拆分后唯一子字符串的最大数目。

字符串 s 拆分后可以得到若干 非空子字符串 ,这些子字符串连接后应当能够还原为原字符串。但是拆分出来的每个子字符串都必须是 唯一的

注意:子字符串 是字符串中的一个连续字符序列。

示例 1:

输入:s = "ababccc"
输出:5
解释:一种最大拆分方法为 ['a', 'b', 'ab', 'c', 'cc'] 。像 ['a', 'b', 'a', 'b', 'c', 'cc'] 这样拆分不满足题目要求,因为其中的 'a' 和 'b' 都出现了不止一次。

示例 2:

输入:s = "aba"
输出:2
解释:一种最大拆分方法为 ['a', 'ba'] 。

示例 3:

输入:s = "aa"
输出:1
解释:无法进一步拆分字符串。

提示:

  • 1 <= s.length <= 16

  • s 仅包含小写英文字母

题解思路

  • 空间不大,回溯吧
  • HashSet 记录是否出现过
    • 已有,必须等待后面的字母
    • 未有
      • 直接进 set
      • 不进,等待后面的字母
  • 全局 max 求最大

参考代码

// bt
private int max;

public int maxUniqueSplit(String s) {
    max = 0;
    dfs(s.toCharArray(), 0, "", new HashSet<>());
    return max;
}

private void dfs(char[] arr, int i, String curr, Set<String> set) {
    if (i == arr.length) {
        max = Math.max(max, set.size());
        return;
    }

    char c = arr[i];
    String comb = curr + c;
    if (set.contains(comb)) {
        // 已有,必须再等
        dfs(arr, i + 1, comb, set);
    } else {
        // 未有
        // 选择不等了直接进
        set.add(comb);
        dfs(arr, i + 1, "", set);
        set.remove(comb);

        // 选择继续等待
        dfs(arr, i + 1, comb, set);
    }
}

复杂度分析

  • 时间复杂度:2^n
    • T(n) = 2T(n - 1) + O(1)
  • 空间复杂度:O(n)

T3. 矩阵的最大非负积

题目

给你一个大小为 rows x cols 的矩阵 grid 。最初,你位于左上角 (0, 0) ,每一步,你可以在矩阵中 向右向下 移动。

在从左上角 (0, 0) 开始到右下角 (rows - 1, cols - 1) 结束的所有路径中,找出具有 最大非负积 的路径。路径的积是沿路径访问的单元格中所有整数的乘积。

返回 最大非负积 109 + 7 取余 的结果。如果最大积为负数,则返回 -1

注意,取余是在得到最大积之后执行的。

示例 1:

输入:grid = [[-1,-2,-3],
             [-2,-3,-3],
             [-3,-3,-2]]
输出:-1
解释:从 (0, 0) 到 (2, 2) 的路径中无法得到非负积,所以返回 -1

示例 2:

输入:grid = [[1,-2,1],
             [1,-2,1],
             [3,-4,1]]
输出:8
解释:最大非负积对应的路径已经用粗体标出 (1 * 1 * -2 * -4 * 1 = 8)

示例 3:

输入:grid = [[1, 3],
             [0,-4]]
输出:0
解释:最大非负积对应的路径已经用粗体标出 (1 * 0 * -4 = 0)

示例 4:

输入:grid = [[ 1, 4,4,0],
             [-2, 0,0,1],
             [ 1,-1,1,1]]
输出:2
解释:最大非负积对应的路径已经用粗体标出 (1 * -2 * 1 * -1 * 1 * 1 = 2)

提示:

  • 1 <= rows, cols <= 15
  • -4 <= grid[i][j] <= 4

题解思路

  • 显然 DP
  • dp[i][j][0|1] 表示走到 g[i][j] 时的最小值 dp[i][j][0]、最大值 dp[i][j][1]

参考代码

// dp
private static final int MOD = (int) 1e9 + 7;

public int maxProductPath(int[][] g) {
    int m = g.length, n = g[0].length;
    long[][][] dp = new long[m][n][2]; // 2: {min, max}
    dp[0][0][0] = dp[0][0][1] = (long) g[0][0];

    for (int i = 1; i < m; i++)
        dp[i][0][0] = dp[i][0][1] = dp[i - 1][0][1] * g[i][0];
    for (int j = 1; j < n; j++)
        dp[0][j][0] = dp[0][j][1] = dp[0][j - 1][1] * g[0][j];

    for (int i = 1; i < m; i++) {
        for (int j = 1; j < n; j++) {
            long u1 = dp[i - 1][j][0] * g[i][j];
            long u2 = dp[i - 1][j][1] * g[i][j];
            long l1 = dp[i][j - 1][0] * g[i][j];
            long l2 = dp[i][j - 1][1] * g[i][j];
            dp[i][j][0] = Math.min(u1, Math.min(u2, Math.min(l1, l2)));
            dp[i][j][1] = Math.max(u1, Math.max(u2, Math.max(l1, l2)));
        }
    }
    int res = (int) (dp[m - 1][n - 1][1] % MOD);
    return res < 0 ? -1 : res;
}

复杂度分析

  • 时间复杂度:O(mn)
  • 空间复杂度:O(mn)

T4. 连通两组点的最小成本

题目

给你两组点,其中第一组中有 size1 个点,第二组中有 size2 个点,且 size1 >= size2

任意两点间的连接成本 cost 由大小为 size1 x size2 矩阵给出,其中 cost[i][j] 是第一组中的点 i 和第二组中的点 j 的连接成本。如果两个组中的每个点都与另一组中的一个或多个点连接,则称这两组点是连通的。换言之,第一组中的每个点必须至少与第二组中的一个点连接,且第二组中的每个点必须至少与第一组中的一个点连接。

返回连通两组点所需的最小成本。

示例 1:

输入:cost = [[15, 96], [36, 2]]
输出:17
解释:连通两组点的最佳方法是:
1--A
2--B
总成本为 17 。

示例 2:

输入:cost = [[1, 3, 5], [4, 1, 1], [1, 5, 3]]
输出:4
解释:连通两组点的最佳方法是:
1--A
2--B
2--C
3--A
最小成本为 4 。
请注意,虽然有多个点连接到第一组中的点 2 和第二组中的点 A ,但由于题目并不限制连接点的数目,所以只需要关心最低总成本。

示例 3:

输入:cost = [[2, 5, 1], [3, 4, 7], [8, 1, 2], [6, 2, 4], [3, 8, 8]]
输出:10

提示:

  • size1 == cost.length
  • size2 == cost[i].length
  • 1 <= size1, size2 <= 12
  • size1 >= size2
  • 0 <= cost[i][j] <= 100

题解思路

  • 花花酱 LeetCode 1595. Minimum Cost to Connect Two Groups of Points
  • dp[i][k] 表示左侧已完成共 i 个点(不是第 i),右侧连接情况为 k(位运算表示)
  • 对右侧第 j 个点,dp[i][k | (1 << j)] 可来自
    • dp[i][k] 左侧已连的,对应右侧未连的 j
    • dp[i - 1][k] 左侧未连的,对应右侧未连的 j
    • 至于左侧已连/未连是谁,枚举 i 即可

参考代码

public int connectTwoGroups(List<List<Integer>> cost) {
    int l = cost.size(), r = cost.get(0).size();

    int[][] dp = new int[l + 1][1 << r];
    for (int[] d : dp) Arrays.fill(d, (int) 1e9);
    dp[0][0] = 0;

    for (int i = 0; i < l; i++)
        for (int j = 0; j < r; j++)
            for (int k = 0; k < (1 << r); k++)
                dp[i + 1][k | (1 << j)] = 
                    Math.min(
                        dp[i + 1][k | (1 << j)],
                        Math.min(
                            dp[i][k] + cost.get(i).get(j)
                            dp[i + 1][k] + cost.get(i).get(j),
                        )
                    );
    return dp[l][(1 << r) - 1];
}

复杂度分析

  • 时间复杂度:O(mn * 2^n)
  • 空间复杂度:O(m * 2^n)