阅读视图

发现新文章,点击刷新页面。

3742. 网格中得分最大的路径

解法

思路和算法

由于每次只能向右或者向下移动,对于每个值为 $0$ 的单元格花费 $0$,对于每个值为 $1$ 的单元格花费 $1$,因此对于网格中的每个单元格,到达该单元格的路径的最大分数需要通过到达相邻单元格的路径的最大分数与花费计算得到。可以使用动态规划计算最大分数。

网格 $\textit{grid}$ 的网格 $\textit{coins}$ 的大小是 $m \times n$。创建 $m \times n \times (k + 1)$ 的三维数组 $\textit{dp}$,其中 $\textit{dp}[i][j][c]$ 表示从单元格 $(0, 0)$ 到达单元格 $(i, j)$ 且花费不超过 $c$ 的最大分数,对于不可能的状态使用 $-\infty$ 表示。由于花费一定非负,因此为方便处理,规定当 $c < 0$ 时 $\textit{dp}[i][j][c] = -\infty$,表示不可能的状态。

当 $i = 0$ 且 $j = 0$ 时,路径上只有 $(0, 0)$ 一个位置,根据 $\textit{grid}[0][0] = 0$ 可以得到分数是 $0$ 且花费是 $0$,因此动态规划的边界情况是:对于任意 $0 \le c \le k$,$\textit{dp}[0][0][c] = 0$。

当 $i > 0$ 或 $j > 0$ 时,计算 $\textit{dp}[i][j][c]$ 需要考虑到达相邻单元格的路径的最大分数与花费。定义示性函数 $\mathbb{I}(b)$,当 $b = \text{true}$ 时 $\mathbb{I}(b) = 1$,当 $b = \text{false}$ 时 $\mathbb{I}(b) = 0$。

  • 当 $i = 0$ 且 $j > 0$ 时,只能从 $(i, j - 1)$ 向右移动到 $(i, j)$,因此 $\textit{dp}[i][j][c] = \textit{dp}[i][j - 1][c - \mathbb{I}(\textit{grid}[0][j] \ne 0)] + \textit{grid}[i][j]$。

  • 当 $i > 0$ 且 $j = 0$ 时,只能从 $(i - 1, j)$ 向下移动到 $(i, j)$,因此 $\textit{dp}[i][j][c] = \textit{dp}[i - 1][j][c - \mathbb{I}(\textit{grid}[i][0] \ne 0)] + \textit{grid}[i][j]$。

  • 当 $i > 0$ 且 $j > 0$ 时,可以从 $(i - 1, j)$ 向下移动到 $(i, j)$ 或从 $(i, j - 1)$ 向右移动到 $(i, j)$,到达 $(i, j)$ 的路径的最大分数为其中的最大值,因此 $\textit{dp}[i][j][c] = \max(\textit{dp}[i - 1][j][c - \mathbb{I}(\textit{grid}[i][j] \ne 0)], \textit{dp}[i][j - 1][c - \mathbb{I}(\textit{grid}[i][j] \ne 0)]) + \textit{grid}[i][j])$。

根据上述分析,当 $i > 0$ 或 $j > 0$ 时,动态规划的状态转移方程如下。

$$
\textit{dp}[i][j][c] = \begin{cases}
\textit{dp}[i][j - 1][c - \mathbb{I}(\textit{grid}[0][j] \ne 0)] + \textit{grid}[i][j], & i = 0 \wedge j > 0 \
\textit{dp}[i - 1][j][c - \mathbb{I}(\textit{grid}[i][0] \ne 0)] + \textit{grid}[i][j], & i > 0 \wedge j = 0 \
\max(\textit{dp}[i - 1][j][c - \mathbb{I}(\textit{grid}[i][j] \ne 0)], \textit{dp}[i][j - 1][c - \mathbb{I}(\textit{grid}[i][j] \ne 0)]) + \textit{grid}[i][j]), & i > 0 \wedge j > 0
\end{cases}
$$

根据动态规划的状态转移方程,计算 $\textit{dp}[i][j]$ 的顺序可以是以下两种。

  1. 从小到大遍历每个 $i$,对于每个 $i$ 从小到大遍历每个 $j$。该顺序为按行遍历。

  2. 从小到大遍历每个 $j$,对于每个 $j$ 从小到大遍历每个 $i$。该顺序为按列遍历。

计算得到 $\textit{dp}[m - 1][n - 1][k]$ 即为从左上角到右下角的总花费不超过 $k$ 的最大分数。

上述做法的时间复杂度和空间复杂度都是 $O(mnk)$。

实现方面可以优化空间,按行遍历和按列遍历的优化空间做法分别如下。

  1. 按行遍历时,由于 $\textit{dp}[i][]$ 只取决于 $\textit{dp}[i - 1][]$,和更早的状态无关,因此可以使用滚动数组的思想,只保留前一行的状态,将空间复杂度降到 $O(n)$。

  2. 按列遍历时,由于 $\textit{dp}[][j]$ 只取决于 $\textit{dp}[][j - 1]$,和更早的状态无关,因此可以使用滚动数组的思想,只保留前一列的状态,将空间复杂度降到 $O(m)$。

使用优化空间做法时,对于每个单元格 $(i, j)$ 计算状态值时应从大到小遍历每个 $c$。

当 $m \ge n$ 时可以使用按行遍历,当 $m < n$ 时可以使用按列遍历,将空间复杂度降到 $O(\min(m, n) \times k)$。

代码

下面的代码为不优化空间的实现。

###Java

class Solution {
    public int maxPathScore(int[][] grid, int k) {
        int m = grid.length, n = grid[0].length;
        int[][][] dp = new int[m][n][k + 1];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                Arrays.fill(dp[i][j], Integer.MIN_VALUE);
            }
        }
        Arrays.fill(dp[0][0], 0);
        for (int j = 1; j < n; j++) {
            int costIncrease = grid[0][j] != 0 ? 1 : 0;
            for (int c = costIncrease; c <= k; c++) {
                dp[0][j][c] = dp[0][j - 1][c - costIncrease] + grid[0][j];
            }
        }
        for (int i = 1; i < m; i++) {
            int costIncrease = grid[i][0] != 0 ? 1 : 0;
            for (int c = costIncrease; c <= k; c++) {
                dp[i][0][c] = dp[i - 1][0][c - costIncrease] + grid[i][0];
            }
        }
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                int costIncrease = grid[i][j] != 0 ? 1 : 0;
                for (int c = costIncrease; c <= k; c++) {
                    dp[i][j][c] = Math.max(dp[i - 1][j][c - costIncrease], dp[i][j - 1][c - costIncrease]) + grid[i][j];
                }
            }
        }
        return dp[m - 1][n - 1][k] >= 0 ? dp[m - 1][n - 1][k] : -1;
    }
}

###C#

public class Solution {
    public int MaxPathScore(int[][] grid, int k) {
        int m = grid.Length, n = grid[0].Length;
        int[][][] dp = new int[m][][];
        for (int i = 0; i < m; i++) {
            dp[i] = new int[n][];
            for (int j = 0; j < n; j++) {
                dp[i][j] = new int[k + 1];
                Array.Fill(dp[i][j], int.MinValue);
            }
        }
        Array.Fill(dp[0][0], 0);
        for (int j = 1; j < n; j++) {
            int costIncrease = grid[0][j] != 0 ? 1 : 0;
            for (int c = costIncrease; c <= k; c++) {
                dp[0][j][c] = dp[0][j - 1][c - costIncrease] + grid[0][j];
            }
        }
        for (int i = 1; i < m; i++) {
            int costIncrease = grid[i][0] != 0 ? 1 : 0;
            for (int c = costIncrease; c <= k; c++) {
                dp[i][0][c] = dp[i - 1][0][c - costIncrease] + grid[i][0];
            }
        }
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                int costIncrease = grid[i][j] != 0 ? 1 : 0;
                for (int c = costIncrease; c <= k; c++) {
                    dp[i][j][c] = Math.Max(dp[i - 1][j][c - costIncrease], dp[i][j - 1][c - costIncrease]) + grid[i][j];
                }
            }
        }
        return dp[m - 1][n - 1][k] >= 0 ? dp[m - 1][n - 1][k] : -1;
    }
}

下面的代码为优化空间的实现。

###Java

class Solution {
    public int maxPathScore(int[][] grid, int k) {
        return grid.length >= grid[0].length ? maxPathScoreHorizontal(grid, k) : maxPathScoreVertical(grid, k);
    }

    public int maxPathScoreHorizontal(int[][] grid, int k) {
        int m = grid.length, n = grid[0].length;
        int[][] dp = new int[n][k + 1];
        for (int j = 0; j < n; j++) {
            Arrays.fill(dp[j], Integer.MIN_VALUE);
        }
        Arrays.fill(dp[0], 0);
        for (int j = 1; j < n; j++) {
            int costIncrease = grid[0][j] != 0 ? 1 : 0;
            for (int c = k; c >= 0; c--) {
                dp[j][c] = c >= costIncrease ? dp[j - 1][c - costIncrease] + grid[0][j] : Integer.MIN_VALUE;
            }
        }
        for (int i = 1; i < m; i++) {
            int costIncrease0 = grid[i][0] != 0 ? 1 : 0;
            for (int c = k; c >= 0; c--) {
                dp[0][c] = c >= costIncrease0 ? dp[0][c - costIncrease0] + grid[i][0] : Integer.MIN_VALUE;
            }
            for (int j = 1; j < n; j++) {
                int costIncrease = grid[i][j] != 0 ? 1 : 0;
                for (int c = k; c >= 0; c--) {
                    dp[j][c] = c >= costIncrease ? Math.max(dp[j][c - costIncrease], dp[j - 1][c - costIncrease]) + grid[i][j] : Integer.MIN_VALUE;
                }
            }
        }
        return dp[n - 1][k] >= 0 ? dp[n - 1][k] : -1;
    }

    public int maxPathScoreVertical(int[][] grid, int k) {
        int m = grid.length, n = grid[0].length;
        int[][] dp = new int[m][k + 1];
        for (int i = 0; i < m; i++) {
            Arrays.fill(dp[i], Integer.MIN_VALUE);
        }
        Arrays.fill(dp[0], 0);
        for (int i = 1; i < m; i++) {
            int costIncrease = grid[i][0] != 0 ? 1 : 0;
            for (int c = k; c >= 0; c--) {
                dp[i][c] = c >= costIncrease ? dp[i - 1][c - costIncrease] + grid[i][0] : Integer.MIN_VALUE;
            }
        }
        for (int j = 1; j < n; j++) {
            int costIncrease0 = grid[0][j] != 0 ? 1 : 0;
            for (int c = k; c >= 0; c--) {
                dp[0][c] = c >= costIncrease0 ? dp[0][c - costIncrease0] + grid[0][j] : Integer.MIN_VALUE;
            }
            for (int i = 1; i < m; i++) {
                int costIncrease = grid[i][j] != 0 ? 1 : 0;
                for (int c = k; c >= 0; c--) {
                    dp[i][c] = c >= costIncrease ? Math.max(dp[i][c - costIncrease], dp[i - 1][c - costIncrease]) + grid[i][j] : Integer.MIN_VALUE;
                }
            }
        }
        return dp[m - 1][k] >= 0 ? dp[m - 1][k] : -1;
    }
}

###C#

public class Solution {
    public int MaxPathScore(int[][] grid, int k) {
        return grid.Length >= grid[0].Length ? MaxPathScoreHorizontal(grid, k) : MaxPathScoreVertical(grid, k);
    }

    public int MaxPathScoreHorizontal(int[][] grid, int k) {
        int m = grid.Length, n = grid[0].Length;
        int[][] dp = new int[n][];
        for (int j = 0; j < n; j++) {
            dp[j] = new int[k + 1];
            Array.Fill(dp[j], int.MinValue);
        }
        Array.Fill(dp[0], 0);
        for (int j = 1; j < n; j++) {
            int costIncrease = grid[0][j] != 0 ? 1 : 0;
            for (int c = k; c >= 0; c--) {
                dp[j][c] = c >= costIncrease ? dp[j - 1][c - costIncrease] + grid[0][j] : int.MinValue;
            }
        }
        for (int i = 1; i < m; i++) {
            int costIncrease0 = grid[i][0] != 0 ? 1 : 0;
            for (int c = k; c >= 0; c--) {
                dp[0][c] = c >= costIncrease0 ? dp[0][c - costIncrease0] + grid[i][0] : int.MinValue;
            }
            for (int j = 1; j < n; j++) {
                int costIncrease = grid[i][j] != 0 ? 1 : 0;
                for (int c = k; c >= 0; c--) {
                    dp[j][c] = c >= costIncrease ? Math.Max(dp[j][c - costIncrease], dp[j - 1][c - costIncrease]) + grid[i][j] : int.MinValue;
                }
            }
        }
        return dp[n - 1][k] >= 0 ? dp[n - 1][k] : -1;
    }

    public int MaxPathScoreVertical(int[][] grid, int k) {
        int m = grid.Length, n = grid[0].Length;
        int[][] dp = new int[m][];
        for (int i = 0; i < m; i++) {
            dp[i] = new int[k + 1];
            Array.Fill(dp[i], int.MinValue);
        }
        Array.Fill(dp[0], 0);
        for (int i = 1; i < m; i++) {
            int costIncrease = grid[i][0] != 0 ? 1 : 0;
            for (int c = k; c >= 0; c--) {
                dp[i][c] = c >= costIncrease ? dp[i - 1][c - costIncrease] + grid[i][0] : int.MinValue;
            }
        }
        for (int j = 1; j < n; j++) {
            int costIncrease0 = grid[0][j] != 0 ? 1 : 0;
            for (int c = k; c >= 0; c--) {
                dp[0][c] = c >= costIncrease0 ? dp[0][c - costIncrease0] + grid[0][j] : int.MinValue;
            }
            for (int i = 1; i < m; i++) {
                int costIncrease = grid[i][j] != 0 ? 1 : 0;
                for (int c = k; c >= 0; c--) {
                    dp[i][c] = c >= costIncrease ? Math.Max(dp[i][c - costIncrease], dp[i - 1][c - costIncrease]) + grid[i][j] : int.MinValue;
                }
            }
        }
        return dp[m - 1][k] >= 0 ? dp[m - 1][k] : -1;
    }
}

复杂度分析

  • 时间复杂度:$O(mnk)$,其中 $m$ 和 $n$ 分别是网格 $\textit{grid}$ 的行数和列数,$k$ 是总花费上限。动态规划的状态数是 $O(mnk)$,每个状态的计算时间是 $O(1)$,因此时间复杂度是 $O(mnk)$。

  • 空间复杂度:$O(mnk)$ 或 $O(\min(m, n) \times k)$,其中 $m$ 和 $n$ 分别是网格 $\textit{grid}$ 的行数和列数,$k$ 是总花费上限。空间复杂度取决于实现方式,不优化空间的实现需要创建大小为 $m \times n \times (k + 1)$ 的三维数组因此空间复杂度是 $O(mnk)$,优化空间的实现需要创建大小为 $\min(m, n) \times (k + 1)$ 的二维数组因此空间复杂度是 $O(\min(m, n) \times k)$。

3783. 整数的镜像距离

解法

思路和算法

整数 $n$ 反转的结果为 $\textit{reverse}(n)$,计算 $|n - \textit{reverse}(n)|$ 即可。

计算 $\textit{reverse}(n)$ 的方法为:从低到高遍历整数 $n$ 的每一位,将每一位根据遍历顺序从高到低填入反转后的数字。

代码

###Java

class Solution {
    public int mirrorDistance(int n) {
        return Math.abs(n - reverse(n));
    }

    public int reverse(int n) {
        int reversed = 0;
        while (n != 0) {
            reversed = reversed * 10 + n % 10;
            n /= 10;
        }
        return reversed;
    }
}

###C#

public class Solution {
    public int MirrorDistance(int n) {
        return Math.Abs(n - Reverse(n));
    }

    public int Reverse(int n) {
        int reversed = 0;
        while (n != 0) {
            reversed = reversed * 10 + n % 10;
            n /= 10;
        }
        return reversed;
    }
}

###C++

class Solution {
public:
    int mirrorDistance(int n) {
        return abs(n - reverse(n));
    }

    int reverse(int n) {
        int reversed = 0;
        while (n) {
            reversed = reversed * 10 + n % 10;
            n /= 10;
        }
        return reversed;
    }
};

###Python

class Solution:
    def mirrorDistance(self, n: int) -> int:
        def reverse(n: int) -> int:
            reversed = 0
            while n:
                reversed = reversed * 10 + n % 10
                n //= 10
            return reversed
        return abs(n - reverse(n))

###C

int reverse(int n) {
    int reversed = 0;
    while (n) {
        reversed = reversed * 10 + n % 10;
        n /= 10;
    }
    return reversed;
}

int mirrorDistance(int n) {
    return abs(n - reverse(n));
}

###Go

func mirrorDistance(n int) int {
    return abs(n - reverse(n))
}

func abs(n int) int {
    if n >= 0 {
        return n
    } else {
        return -n
    }
}

func reverse(n int) int {
    reversed := 0
    for n != 0 {
        reversed = reversed * 10 + n % 10
        n /= 10
    }
    return reversed
}

###JavaScript

var mirrorDistance = function(n) {
    return Math.abs(n - reverse(n));
};

var reverse = function(n) {
    let reversed = 0;
    while (n) {
        reversed = reversed * 10 + n % 10;
        n = Math.floor(n / 10);
    }
    return reversed;
};

###TypeScript

function mirrorDistance(n: number): number {
    return Math.abs(n - reverse(n));
};

function reverse(n: number): number {
    let reversed = 0;
    while (n) {
        reversed = reversed * 10 + n % 10;
        n = Math.floor(n / 10);
    }
    return reversed;
};

复杂度分析

  • 时间复杂度:$O(\log_{10} n)$,其中 $n$ 是给定的数字。整数 $n$ 的位数是 $O(\log_{10} n)$,需要遍历整数 $n$ 的每一位。

  • 空间复杂度:$O(1)$。

3741. 三个相等元素之间的最小距离 II

解法

思路和算法

当三个不同下标 $i$、$j$ 和 $k$ 组成有效三元组时,三个下标的任意排列对应的有效三元组的距离都是相等的,因此可以规定 $i < j < k$,则有效三元组的距离是 $|i - j| + |j - k| + |k - i| = (j - i) + (k - j) + (k - i) = 2(k - i)$。为了计算有效三元组的最小距离,需要计算 $2(k - i)$ 的最小可能值。

遍历数组 $\textit{nums}$,使用哈希表记录每个元素值对应的下标列表,从左到右遍历数组 $\textit{nums}$ 即可确保每个元素值对应的下标列表按升序排序。

得到每个元素值对应的下标列表之后,对于每个元素值,遍历其下标列表,计算该元素的有效三元组的最小可能距离。对于下标列表中的任意三个元素 $i$、$j$ 和 $k$,其中 $i < j < k$,这三个元素对应数组 $\textit{nums}$ 中的三个不同下标且组成有效三元组,其距离为 $2(k - i)$。为了计算最小距离,应考虑下标列表中的每组三个相邻元素,其中的最大值与最小值之差的两倍即为该有效三元组的距离,遍历下标列表中的所有由三个相邻元素组成的有效三元组之后即可得到当前元素值的有效三元组的最小距离。对于所有元素值分别计算有效三元组的最小距离之后,即可得到数组 $\textit{nums}$ 的有效三元组的最小距离。

如果一个元素在数组 $\textit{nums}$ 中的出现次数少于三次,则该元素不存在有效三元组。如果所有元素在数组 $\textit{nums}$ 中的出现次数都少于三次,则数组 $\textit{nums}$ 中不存在有效三元组,答案是 $-1$。

代码

###Java

class Solution {
    public int minimumDistance(int[] nums) {
        int minDistance = Integer.MAX_VALUE;
        Map<Integer, List<Integer>> numToIndices = new HashMap<Integer, List<Integer>>();
        int n = nums.length;
        for (int i = 0; i < n; i++) {
            numToIndices.putIfAbsent(nums[i], new ArrayList<Integer>());
            numToIndices.get(nums[i]).add(i);
        }
        Set<Map.Entry<Integer, List<Integer>>> entries = numToIndices.entrySet();
        for (Map.Entry<Integer, List<Integer>> entry : entries) {
            List<Integer> indices = entry.getValue();
            int size = indices.size();
            for (int i = 2; i < size; i++) {
                int distance = (indices.get(i) - indices.get(i - 2)) * 2;
                minDistance = Math.min(minDistance, distance);
            }
        }
        return minDistance != Integer.MAX_VALUE ? minDistance : -1;
    }
}

###C#

public class Solution {
    public int MinimumDistance(int[] nums) {
        int minDistance = int.MaxValue;
        IDictionary<int, IList<int>> numToIndices = new Dictionary<int, IList<int>>();
        int n = nums.Length;
        for (int i = 0; i < n; i++) {
            numToIndices.TryAdd(nums[i], new List<int>());
            numToIndices[nums[i]].Add(i);
        }
        foreach (KeyValuePair<int, IList<int>> pair in numToIndices) {
            IList<int> indices = pair.Value;
            int size = indices.Count;
            for (int i = 2; i < size; i++) {
                int distance = (indices[i] - indices[i - 2]) * 2;
                minDistance = Math.Min(minDistance, distance);
            }
        }
        return minDistance != int.MaxValue ? minDistance : -1;
    }
}

复杂度分析

  • 时间复杂度:$O(n)$,其中 $n$ 是数组 $\textit{nums}$ 的长度。需要遍历数组将每个元素的下标列表存入哈希表,然后需要遍历哈希表计算有效三元组的最小距离,每次遍历的时间都是 $O(n)$。

  • 空间复杂度:$O(n)$,其中 $n$ 是数组 $\textit{nums}$ 的长度。哈希表的空间是 $O(n)$。

3740. 三个相等元素之间的最小距离 I

解法一

思路和算法

当三个不同下标 $i$、$j$ 和 $k$ 组成有效三元组时,三个下标的任意排列对应的有效三元组的距离都是相等的,因此可以规定 $i < j < k$,则有效三元组的距离是 $|i - j| + |j - k| + |k - i| = (j - i) + (k - j) + (k - i) = 2(k - i)$。

数组 $\textit{nums}$ 的长度是 $n$。遍历 $0 \le i < j < k < n$ 的所有三元组 $(i, j, k)$,当 $\textit{nums}[i] = \textit{nums}[j] = \textit{nums}[k]$ 时,三元组 $(i, j, k)$ 是有效三元组,其距离是 $2(k - i)$,使用该距离更新有效三元组的最小距离。遍历结束之后即可得到数组 $\textit{nums}$ 的有效三元组的最小距离。

如果数组 $\textit{nums}$ 中不存在三个不同下标的元素相等,则数组 $\textit{nums}$ 中不存在有效三元组,答案是 $-1$。

代码

###Java

class Solution {
    public int minimumDistance(int[] nums) {
        int minDistance = Integer.MAX_VALUE;
        int n = nums.length;
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                if (nums[j] == nums[i]) {
                    for (int k = j + 1; k < n; k++) {
                        if (nums[k] == nums[j]) {
                            int distance = (k - i) * 2;
                            minDistance = Math.min(minDistance, distance);
                        }
                    }
                }
            }
        }
        return minDistance != Integer.MAX_VALUE ? minDistance : -1;
    }
}

###C#

public class Solution {
    public int MinimumDistance(int[] nums) {
        int minDistance = int.MaxValue;
        int n = nums.Length;
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                if (nums[j] == nums[i]) {
                    for (int k = j + 1; k < n; k++) {
                        if (nums[k] == nums[j]) {
                            int distance = (k - i) * 2;
                            minDistance = Math.Min(minDistance, distance);
                        }
                    }
                }
            }
        }
        return minDistance != int.MaxValue ? minDistance : -1;
    }
}

复杂度分析

  • 时间复杂度:$O(n^3)$,其中 $n$ 是数组 $\textit{nums}$ 的长度。需要遍历的三元组个数是 $O(n^3)$。

  • 空间复杂度:$O(1)$。

解法二

思路和算法

见题解「3741. 三个相等元素之间的最小距离 II」。

代码

###Java

class Solution {
    public int minimumDistance(int[] nums) {
        int minDistance = Integer.MAX_VALUE;
        Map<Integer, List<Integer>> numToIndices = new HashMap<Integer, List<Integer>>();
        int n = nums.length;
        for (int i = 0; i < n; i++) {
            numToIndices.putIfAbsent(nums[i], new ArrayList<Integer>());
            numToIndices.get(nums[i]).add(i);
        }
        Set<Map.Entry<Integer, List<Integer>>> entries = numToIndices.entrySet();
        for (Map.Entry<Integer, List<Integer>> entry : entries) {
            List<Integer> indices = entry.getValue();
            int size = indices.size();
            for (int i = 2; i < size; i++) {
                int distance = (indices.get(i) - indices.get(i - 2)) * 2;
                minDistance = Math.min(minDistance, distance);
            }
        }
        return minDistance != Integer.MAX_VALUE ? minDistance : -1;
    }
}

###C#

public class Solution {
    public int MinimumDistance(int[] nums) {
        int minDistance = int.MaxValue;
        IDictionary<int, IList<int>> numToIndices = new Dictionary<int, IList<int>>();
        int n = nums.Length;
        for (int i = 0; i < n; i++) {
            numToIndices.TryAdd(nums[i], new List<int>());
            numToIndices[nums[i]].Add(i);
        }
        foreach (KeyValuePair<int, IList<int>> pair in numToIndices) {
            IList<int> indices = pair.Value;
            int size = indices.Count;
            for (int i = 2; i < size; i++) {
                int distance = (indices[i] - indices[i - 2]) * 2;
                minDistance = Math.Min(minDistance, distance);
            }
        }
        return minDistance != int.MaxValue ? minDistance : -1;
    }
}

复杂度分析

  • 时间复杂度:$O(n)$,其中 $n$ 是数组 $\textit{nums}$ 的长度。需要遍历数组将每个元素的下标列表存入哈希表,然后需要遍历哈希表计算有效三元组的最小距离,每次遍历的时间都是 $O(n)$。

  • 空间复杂度:$O(n)$,其中 $n$ 是数组 $\textit{nums}$ 的长度。哈希表的空间是 $O(n)$。

3548. 等和矩阵分割 II

解法

思路和算法

矩阵 $\textit{grid}$ 的大小是 $m \times n$。用 $\textit{total}$ 表示矩阵 $\textit{grid}$ 的所有元素之和。

如果不移除单元格,则判断方法与「3546. 等和矩阵分割 I」相同。

对于移除一个单元格的情况,需要计算两部分的元素和之差 $\textit{diff}$,然后考虑如下两个方面。

  1. 判断元素较大的一部分是否包含等于 $\textit{diff}$ 的元素。

  2. 判断是否可以从元素较大的一部分移除一个元素 $\textit{diff}$,使得该部分的剩余元素保持连通。

以下说明判断是否存在符合要求的按行分割,判断过程中需要依次遍历矩阵 $\textit{grid}$ 的每一行。

首先考虑只满足最多移除一个单元格的限制,不考虑连通性限制。

使用两个哈希表分别记录矩阵 $\textit{grid}$ 的上边部分和下边部分的每个元素的出现次数,将两个哈希表分别记为 $\textit{partOne}$ 和 $\textit{partTwo}$。初始时,计算矩阵 $\textit{grid}$ 中的所有元素的出现次数并存入哈希表 $\textit{partTwo}$。

从上到下遍历矩阵 $\textit{grid}$ 的每一行,将遍历到的每个元素在哈希表 $\textit{partOne}$ 中将出现次数增加 $1$ 并在哈希表 $\textit{partTwo}$ 中将出现次数减少 $1$,将哈希表 $\textit{partTwo}$ 中的出现次数变成 $0$ 的元素移除,遍历过程中维护遍历到的所有元素之和 $\textit{partOneSum}$,即上边部分的元素之和。每一行遍历结束之后,执行如下操作。

  • 如果 $\textit{partOneSum} \times 2 = \textit{total}$,则将当前行作为上边部分的最后一行,即可满足在不移除元素的情况下将矩阵水平分割成元素和相等的两个非空部分。

  • 如果 $\textit{partOneSum} \times 2 > \textit{total}$,记 $\textit{diff} = \textit{partOneSum} \times 2 - \textit{total}$,则当哈希表 $\textit{partOne}$ 中存在元素 $\textit{diff}$ 时,将当前行作为上边部分的最后一行,即可满足在从上边部分移除一个元素的情况下将矩阵水平分割成元素和相等的两个非空部分。

  • 如果 $\textit{partOneSum} \times 2 < \textit{total}$,记 $\textit{diff} = \textit{total} - \textit{partOneSum} \times 2$,则当哈希表 $\textit{partTwo}$ 中存在元素 $\textit{diff}$ 时,将当前行作为上边部分的最后一行,即可满足在从下边部分移除一个元素的情况下将矩阵水平分割成元素和相等的两个非空部分。

然后考虑连通性限制。如果从一部分移除一个单元格之后导致该部分的剩余元素不连通,则有如下两种情况。

  • 该部分的行数等于 $1$ 且列数大于 $1$,移除的单元格的列下标大于 $0$ 且小于 $n - 1$。

  • 该部分的行数大于 $1$ 且列数等于 $1$,移除的单元格不是该部分的首行或末行。

为了实现连通性限制,需要做如下调整。

  1. 当遍历到行下标 $i = 0$ 时,只将 $\textit{grid}[0][0]$ 和 $\textit{grid}[0][n - 1]$ 在哈希表 $\textit{partOne}$ 中的出现次数增加 $1$,列下标范围 $[1, n - 2]$ 的元素都不更新哈希表 $\textit{partOne}$。行下标 $i = 0$ 的判断结束之后,将列下标范围 $[1, n - 2]$ 的所有元素在哈希表 $\textit{partOne}$ 中的出现次数增加 $1$。

  2. 当遍历到行下标 $i = m - 2$ 时,将行下标 $m - 1$ 的列下标范围 $[1, n - 2]$ 的所有元素在哈希表 $\textit{partTwo}$ 中的出现次数减少 $1$,并移除出现次数变成 $0$ 的元素。

  3. 当 $n = 1$ 时,对于遍历到的每个行下标 $i$,应做额外判断。如果需要从上边部分移除一个元素 $\textit{diff}$,则必须满足 $\textit{diff} = \textit{grid}[0][0]$ 或 $\textit{diff} = \textit{grid}[i][0]$;如果需要从下边部分移除一个元素 $\textit{diff}$,则必须满足 $\textit{diff} = \textit{grid}[i + 1][0]$ 或 $\textit{diff} = \textit{grid}[m - 1][0]$。

根据上述操作,即可判断是否存在符合要求的按行分割。

使用同样的方法可以判断是否存在符合要求的按列分割,判断过程中需要依次遍历矩阵 $\textit{grid}$ 的每一列。

代码

###Java

class Solution {
    public boolean canPartitionGrid(int[][] grid) {
        long total = 0;
        int m = grid.length, n = grid[0].length;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                total += grid[i][j];
            }
        }
        return canPartition(grid, m, n, total, true) || canPartition(grid, m, n, total, false);
    }

    public boolean canPartition(int[][] grid, int m, int n, long total, boolean horizontal) {
        Map<Long, Integer> partOne = new HashMap<Long, Integer>();
        Map<Long, Integer> partTwo = getCounts(grid, m, n, horizontal);
        long partOneSum = 0;
        if (horizontal) {
            for (int i = 0; i < m - 1; i++) {
                for (int j = 0; j < n; j++) {
                    partOneSum += grid[i][j];
                    update(grid[i][j], i, j, m, n, horizontal, partOne, partTwo);
                }
                if (i == m - 2) {
                    for (int j = 1; j < n - 1; j++) {
                        long num = grid[m - 1][j];
                        partTwo.put(num, partTwo.get(num) - 1);
                        if (partTwo.get(num) == 0) {
                            partTwo.remove(num);
                        }
                    }
                }
                if (existsPartition(partOneSum, total, partOne, partTwo, n, grid, new int[]{0, 0}, new int[]{i, 0}, new int[]{i + 1, 0}, new int[]{m - 1, 0})) {
                    return true;
                }
                if (i == 0) {
                    for (int j = 1; j < n - 1; j++) {
                        long num = grid[0][j];
                        partOne.put(num, partOne.getOrDefault(num, 0) + 1);
                    }
                }
            }
        } else {
            for (int j = 0; j < n - 1; j++) {
                for (int i = 0; i < m; i++) {
                    partOneSum += grid[i][j];
                    update(grid[i][j], i, j, m, n, horizontal, partOne, partTwo);
                }
                if (j == n - 2) {
                    for (int i = 1; i < m - 1; i++) {
                        long num = grid[i][n - 1];
                        partTwo.put(num, partTwo.get(num) - 1);
                        if (partTwo.get(num) == 0) {
                            partTwo.remove(num);
                        }
                    }
                }
                if (existsPartition(partOneSum, total, partOne, partTwo, m, grid, new int[]{0, 0}, new int[]{0, j}, new int[]{0, j + 1}, new int[]{0, n - 1})) {
                    return true;
                }
                if (j == 0) {
                    for (int i = 1; i < m - 1; i++) {
                        long num = grid[i][0];
                        partOne.put(num, partOne.getOrDefault(num, 0) + 1);
                    }
                }
            }
        }
        return false;
    }

    public Map<Long, Integer> getCounts(int[][] grid, int m, int n, boolean horizontal) {
        Map<Long, Integer> counts = new HashMap<Long, Integer>();
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                long num = grid[i][j];
                counts.put(num, counts.getOrDefault(num, 0) + 1);
            }
        }
        return counts;
    }

    public void update(long num, int row, int col, int m, int n, boolean horizontal, Map<Long, Integer> partOne, Map<Long, Integer> partTwo) {
        if (remainConnected(row, col, m, n, horizontal)) {
            partOne.put(num, partOne.getOrDefault(num, 0) + 1);
            partTwo.put(num, partTwo.get(num) - 1);
            if (partTwo.get(num) == 0) {
                partTwo.remove(num);
            }
        }
    }

    public boolean existsPartition(long partOneSum, long total, Map<Long, Integer> partOne, Map<Long, Integer> partTwo, int length, int[][] grid, int[] pos0, int[] pos1, int[] pos2, int[] pos3) {
        if (partOneSum * 2 == total) {
            return true;
        } else if (partOneSum * 2 > total) {
            long diff = partOneSum * 2 - total;
            if (partOne.containsKey(diff)) {
                if (length > 1 || (grid[pos0[0]][pos0[1]] == diff || grid[pos1[0]][pos1[1]] == diff)) {
                    return true;
                }
            }
        } else {
            long diff = total - partOneSum * 2;
            if (partTwo.containsKey(diff)) {
                if (length > 1 || (grid[pos2[0]][pos2[1]] == diff || grid[pos3[0]][pos3[1]] == diff)) {
                    return true;
                }
            }
        }
        return false;
    }

    public boolean remainConnected(int row, int col, int m, int n, boolean horizontal) {
        if (horizontal) {
            return row > 0 || (col == 0 || col == n - 1);
        } else {
            return col > 0 || (row == 0 || row == m - 1);
        }
    }
}

###C#

public class Solution {
    public bool CanPartitionGrid(int[][] grid) {
        long total = 0;
        int m = grid.Length, n = grid[0].Length;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                total += grid[i][j];
            }
        }
        return CanPartition(grid, m, n, total, true) || CanPartition(grid, m, n, total, false);
    }

    public bool CanPartition(int[][] grid, int m, int n, long total, bool horizontal) {
        IDictionary<long, int> partOne = new Dictionary<long, int>();
        IDictionary<long, int> partTwo = GetCounts(grid, m, n, horizontal);
        long partOneSum = 0;
        if (horizontal) {
            for (int i = 0; i < m - 1; i++) {
                for (int j = 0; j < n; j++) {
                    partOneSum += grid[i][j];
                    Update(grid[i][j], i, j, m, n, horizontal, partOne, partTwo);
                }
                if (i == m - 2) {
                    for (int j = 1; j < n - 1; j++) {
                        long num = grid[m - 1][j];
                        partTwo[num]--;
                        if (partTwo[num] == 0) {
                            partTwo.Remove(num);
                        }
                    }
                }
                if (ExistsPartition(partOneSum, total, partOne, partTwo, n, grid, new int[]{0, 0}, new int[]{i, 0}, new int[]{i + 1, 0}, new int[]{m - 1, 0})) {
                    return true;
                }
                if (i == 0) {
                    for (int j = 1; j < n - 1; j++) {
                        long num = grid[0][j];
                        partOne.TryAdd(num, 0);
                        partOne[num]++;
                    }
                }
            }
        } else {
            for (int j = 0; j < n - 1; j++) {
                for (int i = 0; i < m; i++) {
                    partOneSum += grid[i][j];
                    Update(grid[i][j], i, j, m, n, horizontal, partOne, partTwo);
                }
                if (j == n - 2) {
                    for (int i = 1; i < m - 1; i++) {
                        long num = grid[i][n - 1];
                        partTwo[num]--;
                        if (partTwo[num] == 0) {
                            partTwo.Remove(num);
                        }
                    }
                }
                if (ExistsPartition(partOneSum, total, partOne, partTwo, m, grid, new int[]{0, 0}, new int[]{0, j}, new int[]{0, j + 1}, new int[]{0, n - 1})) {
                    return true;
                }
                if (j == 0) {
                    for (int i = 1; i < m - 1; i++) {
                        long num = grid[i][0];
                        partOne.TryAdd(num, 0);
                        partOne[num]++;
                    }
                }
            }
        }
        return false;
    }

    public IDictionary<long, int> GetCounts(int[][] grid, int m, int n, bool horizontal) {
        IDictionary<long, int> counts = new Dictionary<long, int>();
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                long num = grid[i][j];
                if (RemainConnected(i, j, m, n, horizontal)) {
                    counts.TryAdd(num, 0);
                    counts[num]++;
                }
            }
        }
        return counts;
    }

    public void Update(long num, int row, int col, int m, int n, bool horizontal, IDictionary<long, int> partOne, IDictionary<long, int> partTwo) {
        if (RemainConnected(row, col, m, n, horizontal)) {
            partOne.TryAdd(num, 0);
            partOne[num]++;
            partTwo[num]--;
            if (partTwo[num] == 0) {
                partTwo.Remove(num);
            }
        }
    }

    public bool ExistsPartition(long partOneSum, long total, IDictionary<long, int> partOne, IDictionary<long, int> partTwo, int length, int[][] grid, int[] pos0, int[] pos1, int[] pos2, int[] pos3) {
        if (partOneSum * 2 == total) {
            return true;
        } else if (partOneSum * 2 > total) {
            long diff = partOneSum * 2 - total;
            if (partOne.ContainsKey(diff)) {
                if (length > 1 || (grid[pos0[0]][pos0[1]] == diff || grid[pos1[0]][pos1[1]] == diff)) {
                    return true;
                }
            }
        } else {
            long diff = total - partOneSum * 2;
            if (partTwo.ContainsKey(diff)) {
                if (length > 1 || (grid[pos2[0]][pos2[1]] == diff || grid[pos3[0]][pos3[1]] == diff)) {
                    return true;
                }
            }
        }
        return false;
    }

    public bool RemainConnected(int row, int col, int m, int n, bool horizontal) {
        if (horizontal) {
            return row > 0 || (col == 0 || col == n - 1);
        } else {
            return col > 0 || (row == 0 || row == m - 1);
        }
    }
}

复杂度分析

  • 时间复杂度:$O(mn)$,其中 $m$ 和 $n$ 分别是矩阵 $\textit{grid}$ 的行数和列数。需要遍历矩阵常数次。

  • 空间复杂度:$O(mn)$,其中 $m$ 和 $n$ 分别是矩阵 $\textit{grid}$ 的行数和列数。哈希表的空间是 $O(mn)$。

3567. 子矩阵的最小绝对差

解法

思路和算法

矩阵 $\textit{grid}$ 的大小是 $m \times n$,需要计算每个 $k \times k$ 的子矩阵中的任意两个不同值之间的最小绝对差。为了得到最小绝对差,需要得到 $k \times k$ 的子矩阵中的所有元素值,由于最小绝对差一定出现在排序后的两个相邻的不同元素值的差值中,因此需要将子矩阵中的元素值排序,然后计算最小绝对差。

创建 $(m - k + 1) \times (n - k + 1)$ 的二维数组 $\textit{ans}$,计算 $\textit{ans}[i][j]$ 的方法如下。

  1. 遍历矩阵 $\textit{grid}$ 的行下标范围 $[i, i + k - 1]$ 和列下标范围 $[j, j + k - 1]$ 中的 $k^2$ 个元素,使用长度为 $k^2$ 的数组存储。

  2. 将长度为 $k^2$ 的数组按升序排序,排序之后遍历数组中的每一对相邻元素,如果相邻元素不相等则计算绝对差。遍历结束之后即可得到当前子矩阵中的不同值之间的最小绝对差,填入 $\textit{ans}[i][j]$。

分别计算所有 $k \times k$ 的子矩阵的不同值之间的最小绝对差之后,二维数组 $\textit{ans}$ 即为答案。

代码

###Java

class Solution {
    public int[][] minAbsDiff(int[][] grid, int k) {
        int m = grid.length, n = grid[0].length;
        int[][] ans = new int[m - k + 1][n - k + 1];
        for (int i = 0; i < m - k + 1; i++) {
            for (int j = 0; j < n - k + 1; j++) {
                ans[i][j] = getMinAbsDiff(grid, k, i, j);
            }
        }
        return ans;
    }

    public int getMinAbsDiff(int[][] grid, int k, int startRow, int startCol) {
        int total = k * k;
        int[] values = new int[total];
        for (int i = 0; i < k; i++) {
            for (int j = 0; j < k; j++) {
                values[i * k + j] = grid[startRow + i][startCol + j];
            }
        }
        Arrays.sort(values);
        int minAbsDiff = Integer.MAX_VALUE;
        for (int i = 1; i < total; i++) {
            if (values[i] > values[i - 1]) {
                minAbsDiff = Math.min(minAbsDiff, values[i] - values[i - 1]);
            }
        }
        return minAbsDiff != Integer.MAX_VALUE ? minAbsDiff : 0;
    }
}

###C#

public class Solution {
    public int[][] MinAbsDiff(int[][] grid, int k) {
        int m = grid.Length, n = grid[0].Length;
        int[][] ans = new int[m - k + 1][];
        for (int i = 0; i < m - k + 1; i++) {
            ans[i] = new int[n - k + 1];
            for (int j = 0; j < n - k + 1; j++) {
                ans[i][j] = GetMinAbsDiff(grid, k, i, j);
            }
        }
        return ans;
    }

    public int GetMinAbsDiff(int[][] grid, int k, int startRow, int startCol) {
        int total = k * k;
        int[] values = new int[total];
        for (int i = 0; i < k; i++) {
            for (int j = 0; j < k; j++) {
                values[i * k + j] = grid[startRow + i][startCol + j];
            }
        }
        Array.Sort(values);
        int minAbsDiff = int.MaxValue;
        for (int i = 1; i < total; i++) {
            if (values[i] > values[i - 1]) {
                minAbsDiff = Math.Min(minAbsDiff, values[i] - values[i - 1]);
            }
        }
        return minAbsDiff != int.MaxValue ? minAbsDiff : 0;
    }
}

复杂度分析

  • 时间复杂度:$O(mnk^2 \log k)$,其中 $m$ 和 $n$ 分别是矩阵 $\textit{grid}$ 的行数和列数,$k$ 是给定的子矩阵边长。需要计算的子矩阵个数是 $O(mn)$,对于每个子矩阵遍历 $k^2$ 个元素存入数组的时间是 $O(k^2)$,将数组排序的时间是 $O(k^2 \log k^2) = O(k^2 \log k)$,因此每个子矩阵的计算时间是 $O(k^2 \log k)$,时间复杂度是 $O(mnk^2 \log k)$。

  • 空间复杂度:$O(k^2)$,其中 $k$ 是给定的子矩阵边长。对于每个子矩阵计算差值时需要创建长度为 $k^2$ 的数组,将数组排序的空间是 $O(\log k^2) = O(\log k)$,因此空间复杂度是 $O(k^2)$。

❌