【周赛383】子字符串公共前缀长度(z函数) + 二维前缀和 && 二维差分

82 阅读6分钟

100214. 边界上的蚂蚁

链接

3028. 边界上的蚂蚁

class Solution:
    def returnToBoundaryCount(self, nums: List[int]) -> int:
        return sum(s == 0  for s in accumulate(nums))
class Solution:
    def returnToBoundaryCount(self, nums: List[int]) -> int:
        left = 0
        res = 0 
        for num in nums:      
            left += num
            if left == 0:
                res += 1
                
        return res

image.png

class Solution {
public:
    int returnToBoundaryCount(vector<int>& nums) {
        int location = 0; // 当前 距离 原点 位置
        int res = 0;
        for (int num : nums){
            location += num;
            if (location == 0){
                res += 1;
            }
        }
        return res;        
    }
};

100204. 将单词恢复初始状态所需的最短时间 I

链接

3029. 将单词恢复初始状态所需的最短时间 I

100203. 将单词恢复初始状态所需的最短时间 II

链接

3031. 将单词恢复初始状态所需的最短时间 II

方法: 最长公共前缀长度 Z 函数 O(n)\lgroup O(n)\rgroup

image.png

class Solution:
    def minimumTimeToInitialState(self, word: str, k: int) -> int:
        n = len(word)
        z = [0] * n # word[i:] 与 s[0:] 匹配的长度
        left = right = 0  # Z 盒子 左右边界
        for i in range(1, n):
            if i <= right: # 在 盒子里  left i right  0 ...
                z[i] = min(z[i - left], right - i + 1)   # 后续 可能还可以 继续匹配, 这里限制 到盒子, 盒子外的需要暴力判断
            while i + z[i] < n and word[z[i]] == word[i + z[i]]:
                left, right = i, i + z[i]
                z[i] += 1
            if i % k == 0 and z[i] >= n - i: # 匹配的前缀 长度足够
                return i // k
        return (n - 1) // k + 1 # 只能 重新构造的情况
            
class Solution:
    def minimumTimeToInitialState(self, word: str, k: int) -> int:
        # word[cnt * k:] + s = word
        cnt = 1
        n = len(word)
        while cnt * k < n:
            if word[cnt * k:] == word[: n - cnt * k]:
                return cnt 
            cnt += 1
        return cnt

image.png

class Solution {
public:
    int minimumTimeToInitialState(string word, int k) {
        int n = word.size();
        vector<int> z(n, 0);
        int left = 0, right = 0;
        for (int i = 1; i < n; ++i){
            if (i <= right){
                z[i] = min(z[i - left], right - i + 1);
            }
            while (i + z[i] < n && word[z[i]] == word[i + z[i]]){
                left = i;
                right = i + z[i];
                z[i] += 1;
            }
            if (i % k == 0 && z[i] >= n - i){
                return i / k;
            }
        }
        return (n - 1) / k + 1;
    }
};

—— 2223. 构造字符串的总得分和

class Solution:
    def sumScores(self, s: str) -> int:
        n = len(s)
        res = n    # 初始值 是n 
        z = [0] * n  # 以 i 开始的子字符串 和 原字符串的 公共前缀 长度
        left = right = 0
        for i in range(1, n):
            if i <= right:
                z[i] = min(z[i - left], right - i + 1)
            while i + z[i] < n and s[z[i]] == s[i + z[i]]:
                left, right = i, i + z[i]
                z[i] += 1
            res += z[i]

        return res 

100189. 找出网格的区域平均强度 O(9mn)O(mn)\lgroup O(9mn)、O(mn)\rgroup

链接

3030. 找出网格的区域平均强度

class Solution:
    def resultGrid(self, image: List[List[int]], threshold: int) -> List[List[int]]:
        m, n = len(image), len(image[0])
        result = [[0] * n for _ in range(m)]  # 记录 均值的累加和
        cnt = [[0] * n for _ in range(m)]  # 记录 属于的区域 个数
        for i in range(2, m):
            for j in range(2, n):
                # 检查左右相邻格子
                ok = True
                for row in range(i - 2, i + 1):
                    if abs(image[row][j - 2] - image[row][j - 1]) > threshold or abs(image[row][j - 1] - image[row][j]) > threshold:
                        ok = False
                        break  # 不合法,下一个
                if not ok: continue

                # 检查上下相邻格子
                for y in range(j - 2, j + 1):
                    if abs(image[i - 2][y] - image[i - 1][y]) > threshold or abs(image[i - 1][y] - image[i][y]) > threshold:
                        ok = False
                        break  # 不合法,下一个
                if not ok: continue

                # 合法,计算 3x3 子网格的平均值
                avg = sum(image[x][y] for x in range(i - 2, i + 1) for y in range(j - 2, j + 1)) // 9  # 忘了 除 9 了

                # 更新 3x3 子网格内的 result
                for x in range(i - 2, i + 1):
                    for y in range(j - 2, j + 1):
                        result[x][y] += avg  # 先累加,最后再求平均值
                        cnt[x][y] += 1

        for i in range(m):
            for j in range(n):
                if cnt[i][j] == 0:  # (i,j) 不属于任何子网格
                    result[i][j] = image[i][j]
                else:
                    result[i][j] //= cnt[i][j]  # 求平均值
        return result
            
class Solution {
public:
    vector<vector<int>> resultGrid(vector<vector<int>>& image, int threshold) {
        int m = image.size(), n = image[0].size();
        vector<vector<int>> res(m, vector<int>(n, 0));
        vector<vector<int>> cnt(m, vector<int>(n, 0));
        for (int i = 2; i < m; ++i){
            for (int j = 2; j < n; ++j){
                bool flag = true;
                for (int row = i - 2; row <= i; ++row){ //  注意 要变的是哪个参数
                    if (abs(image[row][j - 2] - image[row][j - 1]) > threshold || abs(image[row][j - 1] - image[row][j]) > threshold){
                        flag = false;
                        break;
                    }
                }
                if (!flag){
                    continue;
                }
                
                for (int col = j - 2; col <= j; ++col){
                    if (abs(image[i - 2][col] - image[i - 1][col]) > threshold || abs(image[i - 1][col] - image[i][col]) > threshold){
                        flag = false;
                        break;
                    }
                }
                if (!flag){
                    continue;
                }
                
                // 求 平均值
                int avg = 0;
                for (int x = i - 2; x <= i; ++x){
                    for (int y = j - 2; y <= j; ++y){
                        avg+= image[x][y];
                    }
                }
                avg /= 9;
                
                // 更新 区域个数  和 均值 累加和
                for (int x = i - 2; x <= i; ++x){
                    for (int y = j - 2; y <= j; ++y){
                        res[x][y] += avg;
                        cnt[x][y] += 1;
                    }
                }
            }
        }
        
        for (int i = 0; i < m; ++i){
            for (int j = 0; j < n; ++j){
                if (cnt[i][j] == 0){
                    res[i][j] = image[i][j];
                }else{
                    res[i][j] /= cnt[i][j];                
                }
            }
        }
        
        return res;
    }
};

—— 2132. 用邮票贴满网格图

方法: 二维前缀 + 二维差分 O(mn)\lgroup O(mn)\rgroup

image.png

image.png

image.png

class Solution:
    def possibleToStamp(self, grid: List[List[int]], stampHeight: int, stampWidth: int) -> bool:
        m, n = len(grid), len(grid[0])

        # 计算 二维 前缀和  用于 判断 某个区域能否放 邮票
        s = [[0] * (n + 1) for _ in range(m + 1)]
        for i in range(m):
            for j in range(n):
                s[i + 1][j + 1] = s[i + 1][j] + s[i][j + 1] - s[i][j] + grid[i][j]

        # 计算 二维差分, 用于 快速修改 可放邮票区域的 邮票数量  + 1
        # 后续 还要恢复 判断 是否 有 未有邮票的空格。 在 最上面 多加一行 和 最左边 多加 一列
        d = [[0] * (n + 2) for _ in range(m + 2)]
        for i2 in range(stampHeight, m + 1):
            for j2 in range(stampWidth, n + 1):
                i1 = i2 - stampHeight + 1
                j1 = j2 - stampWidth + 1
                if s[i2][j2] - s[i2][j1-1] - s[i1- 1][j2] + s[i1 - 1][j1 - 1] == 0: # 空白面积 可放 邮票
                    # 修改 该区域的 邮票数量
                    d[i1][j1] += 1
                    d[i1][j2 + 1] -= 1
                    d[i2 + 1][j1] -= 1
                    d[i2 + 1][j2 + 1] += 1

        # 根据 d  还原 每个 格子的 邮票数量
        for i in range(m):
            for j in range(n):
                d[i + 1][j + 1] += d[i + 1][j] + d[i][j + 1] - d[i][j] 
                if grid[i][j] == 0 and d[i + 1][j + 1] == 0:
                    return False 
        return True  

image.png

class Solution {
public:
    bool possibleToStamp(vector<vector<int>>& grid, int stampHeight, int stampWidth) {
        int m = grid.size(), n = grid[0].size();

        // 二维前缀和, 快速求解 区域和 判断是否能放下邮票
        vector<vector<int>> s(m + 1, vector<int>(n + 1, 0));
        for (int i = 0; i < m; ++i){
            for (int j = 0; j < n; ++j){
                s[i + 1][j + 1] = s[i + 1][j] + s[i][j + 1] - s[i][j] + grid[i][j];
            }
        }

        // 二维 差分 用于 快速 修改 区间内格子 的 邮票数量
        vector<vector<int>> d(m + 2, vector<int>(n + 2, 0));
        for (int i2 = stampHeight; i2 <= m; ++i2){
            for (int j2 = stampWidth; j2 <= n; ++j2){
                int i1 = i2 - stampHeight + 1;
                int j1 = j2 - stampWidth + 1;
                if (s[i2][j2] - s[i2][j1- 1] - s[i1- 1][j2] + s[i1 - 1][j1 - 1] == 0){
                    d[i1][j1] += 1;

                    d[i1][j2 + 1] -= 1;
                    d[i2 + 1][j1] -= 1;

                    d[i2 + 1][j2 + 1] += 1;
                }
            }
        }

        // 根据 d  还原 每个格子的邮票数量
        for (int i = 0; i < m; ++i){
            for (int j = 0; j < n; ++j){
                d[i + 1][j + 1] += d[i + 1][j] + d[i][j + 1] - d[i][j];
                if (grid[i][j] == 0 && d[i + 1][j + 1] == 0){
                    return false;
                }
            }
        }

        return  true;
    }
};