《跟我学算法系列文章》21天轻松通关——数据结构与算法-day01

《跟我学算法系列文章》21天轻松通关——数据结构与算法-day01

——01.动态规划+滑动窗口

介绍 动态规划+滑动窗口:
元素和为目标值的子矩阵数量
难度:困难/动态规划、滑动窗口

——02.滑动窗口题目

1. 滑动窗口的最大值(Leetcode 剑指 Offer 59 - I /简单)
2. 长度最小的子数组(Leetcode 209/中等)
3. 最小覆盖子串(Leetcode 76/困难)lagou、labuladong(1.7.1)
4. 滑动窗口最大值 (Leetcode 239/困难)
5. 字符串排列 (Leetcode 239/中等) labuladong(1.7.2)
6. 找出所有字母异位词 (Leetcode 242/简单)
7. 最长无重复字串(Leetcode 3/简单)



前言

本文为拉勾课程《数据结构与算法训练营》和labuladong笔记整合,Java代码为主题进行编写,预计20天更新完毕。本着“只有亲身实践过并整理成体系才属于自己真正掌握的知识” 的理念写出本篇文章,后续每天更新,持续关注,欢迎留言讨论~。


提示:以下是本篇文章正文内容,下面案例仅供参考

一、题目描述

在这里插入图片描述

二、解题思路

1. 理解题意

题目主干
元素总和等于目标值的非空子矩阵的数量
子矩阵: x1, y1, x2, y2 —— 左上角、右下角
满足 x1 <= x <= x2 且 y1 <= y <= y2 的所有单元 matrix[x][y] 的集合
附加限制
统计满足条件非空子矩阵数量
宽松限制
1 <= matrix.length <= 300
1 <= matrix[0].length <= 300
-1000 <= matrix[i] <= 1000
-10^8 <= target <= 10^8
细节问题
矩阵中存在负数

2.算法选择

数据结构选择
• 输入的数据类型为矩阵和整数,矩阵对应的数据结构为二维数组
• 我们需要对矩阵的所有子矩阵进行求和验证
• 数据结构方面先选定二维数组
• 需要意识到到二维数组的时间复杂度比一位数组要大
• 一维数组遍历O(n),二维数组遍历O(mn)
• 一维数组子数组个数n
(n+1)/2,复杂度为O(n2),二维数组子矩阵个数 (m*n+1)mn/2,复杂度O(m2n2)

3.一般解法

算法思维选择:朴素解法
• 遍历每一个位置为起点
• 遍历其后的每一个终点位置
• 验证子矩阵元素和
时间复杂度:O(m3 n3)
空间复杂度:O(1)
在这里插入图片描述

4.算法改进——前缀和算法

是否存在无效代码或者无效空间消耗
A[i][j]为起点的子矩阵求和中存在大量重复运算
思考动态规划:使用A[i][j]为起点的备忘录优化
opt[m][n]=opt[m-1][n]+opt[m][n-1]-opt[m-1][n-1]+A[m][n] ,其中m-1>=i,n-1>=j
这种备忘录也称作前缀和算法
在这里插入图片描述
基本解法思路剖析
• 遍历每一个位置为起点、遍历其后的每一个终点位置
• 通过前缀和(备忘录) 计算子矩阵和
opt[m][n]=opt[m-1][n]+opt[m][n-1]-opt[m-1][n-1]+A[m][n]
• 判断子矩阵和是否满足条件
细节问题
• 与起始位置同行:opt[i][n]=opt[i][n-1]+A[i][n]
• 与起始位置同列:opt[m][j]=opt[m-1][j]+A[m][j]
Java编码实现
时间复杂度:O(m2 n2)
空间复杂度:O(m*n)
在这里插入图片描述

5.算法再改进——滑动窗口

5.1 思考更优解
是否存在无效代码或者无效空间消耗
• 动态规划:已使用备忘录、使用迭代而不是递归方式
• 需要对每一个起始位置尝试每一个结束位置,还是会有重复计算
是否有更好的算法思维
• 如何进一步减少重复计算?
关键知识点:滑动窗口 Sliding window
像窗口一样滑动,窗口内大部分元素不变
用以解决数组/字符串的子元素问题
查找满足一定条件的连续区间的问题
“请找到满足xx的最x的区间(子串、子数组)的xx”*
可以将嵌套的循环问题转换为单循环问题
当区间发生变化时,可以通过旧有的计算结果对搜索空间进行剪枝
从而减少重复计算,降低了时间复杂度

6.滑动窗口示例

滑动窗口示例题目:
给定一个整数数组,计算长度为 k 的连续子数组的最大总和。
暴力解法:从头开始计算相邻k个总和取最大值,时间复杂度O(n*k)
优化解法:
创建一个大小为k的窗口
窗口从左向右滑动
求和:减左侧出窗值,加右侧新入窗值
减少重复运算
长度固定的滑动窗口
在这里插入图片描述

7.滑动窗口解答

算法思维选择:滑动窗口+行前缀和
• 行前缀和 rowSum
• 窗口:固定列长度
(0,1,1,3) 总和=rowSum(0,3)-rowSum(0,0) +rowSum(1,3)-rowSum(1,0)
(0,1,2,3) 总和=子矩阵(0,1,1,3) 总和 +rowSum(2,3)-rowSum(2,0)
(1,1,2,3) 总和=子矩阵(0,1,2,3) 总和 – 第0行窗口和
窗口总和=第0行到当前行窗口和-第0行到起始行窗口和 == 目标值
第0行到当前行窗口和-目标值 是否在前面出现过?
哈希表:存第0行到第i行的总和
• 将四层循环降维三层循环
在这里插入图片描述
最优解思路及编码剖析

1.遍历矩阵,按行计算每个位置的行前缀和
2. 遍历每一列作为起始列

2.1 遍历起始列之后的每一列作为结束列

2.1.1 创建/清空哈希表(key:窗口和,value:出现次数)
2.1.2 在固定列长度下,按行向下扩大窗口

2.1.2.1 计算窗口内本行和,结合上一窗口和计算本窗口和
2.1.2.2 判断本窗口和 -target的值是否在哈希表出现过,出现过表示满足目标则增加总次数
2.1.2.3 本窗口和 放入map中,如果窗口和 重复则次数+1

细节问题:前一列越界、窗口和 刚好等于target
Java编码实现
复杂度分析
• 时间复杂度O(mn2) • 空间复杂度O(mn)
在这里插入图片描述
题目变形

  1. 元素和大于目标值的非空子矩阵数量
  2. 元素和小于目标值的非空子矩阵数量
    延伸扩展
    • 滑动窗口的应用: 网络限流,令牌桶算法
    • 正确使用滑动窗口算法可有效降低时间复杂度

7.总结

• 强化对动态规划原理的理解及实际应用
• 掌握滑动窗口的核心思想及原理
• 掌握滑动窗口的应用

猜你喜欢

转载自blog.csdn.net/weixin_45091011/article/details/113821575