LeetCode1074 / 560 / 1248 / 363 / 剑指 Offer 68 - II
Posted Zephyr丶J
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LeetCode1074 / 560 / 1248 / 363 / 剑指 Offer 68 - II相关的知识,希望对你有一定的参考价值。
1074. 元素和为目标值的子矩阵数量
题目描述
给出矩阵 matrix 和目标值 target,返回元素总和等于目标值的非空子矩阵的数量。
子矩阵 x1, y1, x2, y2 是满足 x1 <= x <= x2 且 y1 <= y <= y2 的所有单元 matrix[x][y] 的集合。
如果 (x1, y1, x2, y2) 和 (x1', y1', x2', y2') 两个子矩阵中部分坐标不同(如:x1 != x1'),那么这两个子矩阵也不同。
示例 1:
输入:matrix = [[0,1,0],[1,1,1],[0,1,0]], target = 0
输出:4
解释:四个只含 0 的 1x1 子矩阵。
示例 2:
输入:matrix = [[1,-1],[-1,1]], target = 0
输出:5
解释:两个 1x2 子矩阵,加上两个 2x1 子矩阵,再加上一个 2x2 子矩阵。
示例 3:
输入:matrix = [[904]], target = 0
输出:0
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/number-of-submatrices-that-sum-to-target
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路
前缀和加四层循环,我看着矩阵大小估摸着好像能过,就写了一下,没想到还真过了,百分之34,时间复杂度O(m2n2)
class Solution {
public int numSubmatrixSumTarget(int[][] matrix, int target) {
//暴力解,求解一个前缀和数组,然后遍历起点和终点位置,求和
//我感觉到是动态规划,因为只需要求矩阵的数目,而不需要具体多少
int m = matrix.length;
int n = matrix[0].length;
//以当前位置为起点,右下角的矩阵
int[][] pre = new int[m + 1][n + 1];
for(int i = 1; i <= m; i++){
for(int j = 1; j <= n; j++){
pre[i][j] = pre[i - 1][j] + pre[i][j - 1] + matrix[i - 1][j - 1] - pre[i - 1][j - 1];
}
}
int count = 0;
//起点i,j
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++){
//终点t,k
for(int t = i; t < m; t++){
for(int k = j; k < n; k++){
if(pre[t + 1][k + 1] - pre[t + 1][j] - pre[i][k + 1] + pre[i][j] == target)
count++;
}
}
}
}
return count;
}
}
看了答案,感觉又学到了,上个月363基本用过相同的思路,但是奈何当时没感觉,看来这样处理矩阵和也是一种套路
什么套路呢,就是先遍历上下边界(或者左右,主要看哪个长度小),然后计算此边界内,每列元素的和,将它存储到一个数组中。
这时,问题就变成了给定一个数组,和一个目标值,计算该数组内和为目标值的子数组的个数
这个问题的处理就用前缀和加哈希表能快速处理,也就是遍历数组中的元素,将遍历到元素之和存储在哈希表中,如果当前前缀和减去目标值也在哈希表中存在,那么就说明找到了一个子矩阵,使得子矩阵之和为目标值
具体呢,看代码吧:
class Solution {
public int numSubmatrixSumTarget(int[][] matrix, int target) {
//暴力解,求解一个前缀和数组,然后遍历起点和终点位置,求和
//我感觉到是动态规划,因为只需要求矩阵的数目,而不需要具体多少
int m = matrix.length;
int n = matrix[0].length;
int res = 0; //最终结果
//遍历上边界
for(int i = 0; i < m; i++){
//存储当前处理的每一列的和
int[] sum = new int[n];
//遍历下边界
for(int j = i; j < m; j++){
for(int k = 0; k < n; k++){
sum[k] += matrix[j][k];
}
//此时sum中存储的是i到j行,每一列元素的和
//问题就变成了求子数组的和
int pre = 0; //前缀和
Map<Integer, Integer> map = new HashMap<>();
//这里为什么要预先放个0进去,是因为如果当前pre就等于目标值了,如果不放这个进去会统计不到这个值
map.put(0, 1);
for(int num : sum){
pre += num;
//如果存在pre - target,那么结果++
if(map.containsKey(pre - target)){
res += map.get(pre - target);
}
map.put(pre, map.getOrDefault(pre, 0) + 1);
}
}
}
return res;
}
}
或者说,用363中三叶姐的那种写法,先处理二维矩阵的前缀和,然后再用上面相同的思路,即确定三个边界,来加速结果的查找,这样更好理解,只不过空间复杂度大了点
class Solution {
public int numSubmatrixSumTarget(int[][] matrix, int target) {
//暴力解,求解一个前缀和数组,然后遍历起点和终点位置,求和
//我感觉到是动态规划,因为只需要求矩阵的数目,而不需要具体多少
int m = matrix.length;
int n = matrix[0].length;
int res = 0; //最终结果
//前缀和
int[][] pre = new int[m + 1][n + 1];
for(int i = 1; i <= m; i++){
for(int j = 1; j <= n; j++){
pre[i][j] = pre[i - 1][j] + pre[i][j - 1] + matrix[i - 1][j - 1] - pre[i - 1][j - 1];
}
}
//遍历上边界
for(int i = 1; i <= m; i++){
//遍历下边界
for(int j = i; j <= m; j++){
//哈希表存储,前缀和->出现的次数
Map<Integer, Integer> map = new HashMap<>();
//对于当前列
for(int k = 1; k <= n; k++){
//计算前缀和
int right = pre[j][k] - pre[i - 1][k];
//这句话可以和上面map.put(0,1)互换,一个道理
if(right == target) res++;
if(map.containsKey(right - target)){
res += map.get(right - target);
}
map.put(right, map.getOrDefault(right, 0) + 1);
}
}
}
return res;
}
}
最后,总结一下,毕竟也算一个类型题了。
对于二维数组中子矩阵和的查找,最容易想到的方法就是遍历四个边,时间复杂度是四次方。
而优化的方法呢,是遍历三个边,即通过三个边的处理,将这个问题转化成已知一个数组,求数组中子数组和为目标值的问题,然后就能通过前缀和加哈希表的思想来加速处理,使得时间复杂度降低
再把两道相似的题看一下:
560. 和为K的子数组
题目描述
给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。
示例 1 :
输入:nums = [1,1,1], k = 2
输出: 2 , [1,1] 与 [1,1] 为两种不同的情况。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/subarray-sum-equals-k
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路
刚刚那道题的前置题,懂了思路秒A
class Solution {
public int subarraySum(int[] nums, int k) {
int pre = 0;
int res = 0;
Map<Integer, Integer> map = new HashMap<>();
for(int num : nums){
pre += num;
if(pre == k)
res++;
if(map.containsKey(pre - k))
res += map.get(pre - k);
map.put(pre, map.getOrDefault(pre, 0) + 1);
}
return res;
}
}
1248. 统计「优美子数组」
题目描述
给你一个整数数组 nums 和一个整数 k。
如果某个 连续 子数组中恰好有 k 个奇数数字,我们就认为这个子数组是「优美子数组」。
请返回这个数组中「优美子数组」的数目。
示例 1:
输入:nums = [1,1,2,1,1], k = 3
输出:2
解释:包含 3 个奇数的子数组是 [1,1,2,1] 和 [1,2,1,1] 。
示例 2:
输入:nums = [2,4,6], k = 1
输出:0
解释:数列中不包含任何奇数,所以不存在优美子数组。
示例 3:
输入:nums = [2,2,2,1,2,2,1,2,2,2], k = 2
输出:16
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/count-number-of-nice-subarrays
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路
和上面一样,主要练习这种前缀和+哈希表优化的思想
当然,滑动窗口也可以
class Solution {
public int numberOfSubarrays(int[] nums, int k) {
int pre = 0;
int res = 0;
int[] sum = new int[nums.length + 1];
sum[0] = 1;
for(int num : nums){
if((num & 1) == 1)
pre += 1;
if(pre - k >= 0)
res += sum[pre - k];
sum[pre]++;
}
return res;
}
}
363. 矩形区域不超过 K 的最大数值和
题目描述
给你一个 m x n 的矩阵 matrix 和一个整数 k ,找出并返回矩阵内部矩形区域的不超过 k 的最大数值和。
题目数据保证总会存在一个数值和不超过 k 的矩形区域。
示例 1:
输入:matrix = [[1,0,1],[0,-2,3]], k = 2
输出:2
解释:蓝色边框圈出来的矩形区域 [[0, 1], [-2, 3]] 的数值和是 2,且 2 是不超过 k 的最大数字(k = 2)。
示例 2:
输入:matrix = [[2,2,-1]], k = 3
输出:3
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/max-sum-of-rectangle-no-larger-than-k
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路
忘了具体什么时候的每日一题了,那时候应该还没有开始写csdn哈哈
基本上和今天的每日一题一样,这个题找的是数值和不超过k的矩阵区域,那么在遍历右边界的时候,因为矩阵中的值存在负数,所以不能保证是单调递增的,这时需要将数据结构由哈希表变成一个可以排序的数据结构,那么就可以用二分查找找到对应的最小值
class Solution {
public int maxSumSubmatrix(int[][] mat, int k) {
int m = mat.length, n = mat[0].length;
// 预处理前缀和,二维矩阵的前缀和,这里简单
int[][] sum = new int[m + 1][n + 1];
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + mat[i - 1][j - 1];
}
}
//将二维问题转化为一维问题,就是固定三个边界,找另一个边界
int ans = Integer.MIN_VALUE;
// 遍历子矩阵的上边界
for (int top = 1; top <= m; top++) {
// 遍历子矩阵的下边界
for (int bot = top; bot <= m; bot++) {
// 使用「有序集合」维护所有遍历到的右边界
TreeSet<Integer> ts = new TreeSet<>();
ts.add(0);
// 遍历子矩阵的右边界,这里为什么从1开始,是因为上面的前缀和矩阵就是从1开始的
for (int r = 1; r <= n; r++) {
// 通过前缀和计算 right,即上下边界中到0到right部分的和
int right = sum[bot][r] - sum[top - 1][r];
// 通过二分找 left,找到大于right- k的最小值
Integer left = ts.ceiling(right - k);
//如果left存在,那么就更新ans
if (left != null) {
int cur = right - left;
ans = Math.max(ans, cur);
}
// 将遍历过的 right 加到有序集合
ts.add(right);
}
}
}
return ans;
}
}
再贴一个三叶姐空间时间都优化过的代码
class Solution {
public int maxSumSubmatrix(int[][] mat, int k) {
int m = mat.length, n = mat[0].length;
boolean isRight = n > m;
int[] sum = isRight ? new int[n + 1] : new int[m + 1];
int ans = Integer.MIN_VALUE;
for (int i = 1; i <= (isRight ? m : n); i++) {
Arrays.fill(sum, 0);
for (int j = i; j <= (isRight ? m : n); j++) {
TreeSet<Integer> ts = new TreeSet<>();
ts.add(0);
int a = 0;
for (int fixed = 1; fixed <= (isRight ? n : m); fixed++) {
sum[fixed] += isRight ? mat[j - 1][fixed - 1] : mat[fixed - 1][j - 1] ;
a += sum[fixed];
Integer b = ts.ceiling(a - k);
if (b != null) {
int cur = a - b;
ans = Math.max(ans, cur);
}
ts.add(a);
}
}
}
return ans;
}
}
作者:AC_OIer
链接:https://leetcode-cn.com/problems/max-sum-of-rectangle-no-larger-than-k/solution/gong-shui-san-xie-you-hua-mei-ju-de-ji-b-dh8s/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
剑指 Offer 68 - II. 二叉树的最近公共祖先
题目描述
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]
示例 1:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。
示例 2:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/er-cha-shu-de-zui-jin-gong-gong-zu-xian-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路
刚开始想到的思路是,后序遍历的同时,用两个指针表示是否已经找到了对应的值,然后如果在某一个结点同时两个指针都是true,就返回这个结点。当然,问题很大,问题出在这样找的话,如果找到了第二个值就会直接返回第二个值所在的结点
于是,就想怎么标记当前已经找到了对应的结点,即在一颗子树中有相应的结点,然后就想到了给递归加返回值,也就是如果在找的过程中,已经找到了一个节点,就将该结点的父节点都标记为true,如果在某个结点,左右标记都为true或者一个为true的同时,当前结点的值也为一个要找的值,就将这个结点输出。后面的所有结点就不用遍历了。直接返回
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
TreeNode res = null;
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
//怎么找一个祖先呢,就是先找到这两个值,然后用两个标记分别标记这两个值是否找到了
//后序遍历
helper(root, pLeetCode1248. 统计「优美子数组」
[LeetCode] 1248. Count Number of Nice Subarrays
[LeetCode] 1248. Count Number of Nice Subarrays 统计优美子数组
⭐算法入门⭐《前缀和》中等03 —— LeetCode 1248. 统计「优美子数组」