LeetCode - 二维数组及滚动数组
1. 二维数组及滚动数组总结
在二维数组num[i][j]
中,每个元素都是一个数组。有时候,二维数组中的某些元素在整个运算过程中都需要用到;但是有的时候我们只需要用到前一个或者两个数组,此时我们便可以用几个数组来代替原来的二维数组来降低空间消耗。这个思维就是:滚动数组。
滚动数组就是使用k
个一维数组来保存原来二维数组的后k
个数组,在使用的过程中通过不断更新这k
个数组来达到与二维数组相同的效果。
注意:滚动数组的目的是:减少空间消耗;滚动数组能够使用的前提是:每次处理时不需要访问二维数组中的所有元素,只与当前处理元素的前一个或两个数组有关,如:num[i][j] = num[i-1][j] + num[i-2][j]
类似情况,我们就可以使用滚动数组。
滚动数组在动态规划过程中经常使用,此处我们先提前了解一下。
2. 题目记录
118. 杨辉三角
分析题意
给定一个非负整数 numRows
,生成「杨辉三角」的前 numRows
行。在「杨辉三角」中,每个数是它左上方和右上方的数的和。
思路分析
关键在于理解:杨辉三角是如何生成的。即:对于第i
行元素来说,除了第一个和最后一个,对于任意一个元素 j
都有nums[i][j] = nums[i-1][j-1] + nums[i-1][j]
class Solution {
public List<List<Integer>> generate(int numRows) {
List<List<Integer>> ans = new ArrayList<>();
for(int row = 1; row <= numRows; row++){
List<Integer> temp = new ArrayList<Integer>();
for(int col = 1; col <= row; col++){
if(col == 1 || col == row){
temp.add(1);
}else{
// row - 1 对应的idx 为 row-1-1
List<Integer> pre = ans.get(row - 2);
// col 对应的idx为col-1
temp.add(pre.get(col-2) + pre.get(col-1));
}
}
ans.add(temp);
}
return ans;
}
}
复杂度分析
时间复杂度:\(O(n^{2})\)
空间复杂度:\(O(1)\) 返回值所占空间不考虑
119. 杨辉三角 II
分析题意
给定一个非负索引 rowIndex
,返回「杨辉三角」的第 rowIndex
行。在「杨辉三角」中,每个数是它左上方和右上方的数的和。
思路分析
这个题和上一道题很相似,区别在于:本题只需要返回指定行的结果。根据题意知道,第i
行的数据只与第i-1
行的数据有关系,所以我们可以使用滚动数组来将二维数组压缩为两个数组。
class Solution {
public List<Integer> getRow(int rowIndex) {
int[] pre = new int[rowIndex + 1];
int[] cur = new int[rowIndex + 1];
for(int idx = 0; idx <= rowIndex; idx++){
for(int j = 0; j < idx + 1; j++){
if(j == 0 || j == idx){
cur[j] = 1;
}else{
cur[j] = pre[j-1] + pre[j];
}
}
int[] temp = pre;
pre = cur;
cur = temp;
}
List<Integer> ans = new ArrayList<>();
for(int i = 0; i < pre.length; i++){
ans.add(pre[i]);
}
return ans;
}
}
复杂度分析
时间复杂度:\(O(n^2)\)
空间复杂度:\(O(n)\),通过滚动数组,我们将空间消耗从\(O(n^{2})\)降为了\(O(n)\)。
扩展
其实这道题可以继续优化,如果不考虑返回数据占用的空间,这道题可以做到O(1)
的空间复杂度。
做到O(1)
空间复杂度的思路就是:将上述两个数组合并为一个数组,关键问题就是怎么才能复用这个数组呢?
我们直接创建一个大小为n
的ans
数组,假定我们想要第4
行的数据,而我们已经遍历过第3
行,那么此时ans
数组中的元素为:
[1, 3, 3, 1, 0]
如果我们从前到后遍历,那么在遍历到j=2
时,数组被更新为:
[1, 4, 3, 1, 0]
那么此时计算j=3
时,j=2
位置的元素已经被覆盖为第4
行的新数据,而不是第3
行的旧数据。所以我们不能从做左到右进行遍历。我们再思考一下,第j
个元素的更新依赖于j-1
和j
元素,只更新j
元素,所以我们可以从后向前遍历!
从后向前遍历防止之前元素被覆盖的思路在背包问题中也有体现。
class Solution {
public List<Integer> getRow(int rowIndex) {
List<Integer> ans = new ArrayList<>();
for(int i = 0; i <= rowIndex; i++){
ans.add(1);
}
for(int idx = 2; idx <= rowIndex; idx++){
// 从后往前遍历,防止前一层的元素被覆盖掉
for(int j = idx; j >=0; j--){
if(j == idx || j == 0){
// 第idx行的第1个和最后1个元素为1,不需要修改
continue;
}else{
ans.set(j, ans.get(j) + ans.get(j-1));
}
}
}
return ans;
}
}
661. 图片平滑器
分析题意
注意:3x3
滑动窗内的9
个数字的平均值,需要向下取整。还需要注意的一点就是:如果个数小于9个,那么求平均值的时候应该除以真正的数字的数目,而不是除以9。
思路分析
按照题意进行滑动窗的模拟操作。
对于(i, j)
坐标,它周围的8个坐标分布如下:
(i-1, j-1) (i-1, j) (i-1, j+1)
(i, j-1) (i, j) (i, j+1)
(i+1, j-1) (i+1, j) (i+1, j+1)
class Solution {
public int[][] imageSmoother(int[][] img) {
int[][] ans = new int[img.length][img[0].length];
for(int i = 0; i < img.length; i++){
for(int j = 0; j < img[0].length; j++){
ans[i][j] = getAverage(img, i, j);
}
}
return ans;
}
int getAverage(int[][] img, int i, int j){
int sum = 0;
int num = 0;
// i - 1 层
if(i - 1 >= 0){
if(j - 1 >= 0){
sum += img[i-1][j-1];
num++;
}
sum += img[i-1][j];
num++;
if(j + 1 < img[0].length){
sum += img[i-1][j+1];
num++;
}
}
if(j - 1 >= 0){
sum += img[i][j-1];
num++;
}
sum += img[i][j];
num++;
if(j + 1 < img[0].length){
sum += img[i][j + 1];
num ++;
}
// i + 1 层
if(i + 1 < img.length){
if(j - 1 >= 0){
sum += img[i + 1][j - 1];
num ++;
}
sum += img[i + 1][j];
num ++;
if(j + 1 < img[0].length){
sum += img[i + 1][j + 1];
num ++;
}
}
return sum / num;
}
}
简化的代码如下:
class Solution {
int[] row = {-1, -1, -1, 0, 0, 0, 1, 1, 1};
int[] col = {-1, 0, 1, -1, 0, 1, -1, 0, 1};
public int[][] imageSmoother(int[][] img) {
int[][] ans = new int[img.length][img[0].length];
for(int i = 0; i < img.length; i++){
for(int j = 0; j < img[0].length; j++){
ans[i][j] = getAverage(img, i, j);
}
}
return ans;
}
int getAverage(int[][] img, int i, int j){
int sum = 0;
int num = 0;
for(int idx = 0; idx < 9; idx++){
// 判断坐标是否合法
if(i + row[idx] >= 0 && i + row[idx] < img.length && j + col[idx] >= 0 && j + col[idx] < img[0].length){
sum += img[i + row[idx]][j + col[idx]];
num ++;
}
}
return sum / num;
}
}
复杂度分析
时间复杂度:\(O(n^2)\)
空间复杂度:\(O(1)\) 返回数组占用空间不计入
598. 范围求和 II
分析题意
注意关键词:初始化时所有的 0
,也就是说所有数值在操作之前都是0
。
思路分析
这道题看似是需要创建一个二维数组,然后一次一次模拟操作,最后遍历查出最大的数据的个数。其实,并不需要这样。我们想一下:因为二维数组初始化时都是0
,所以在所有操作完成之后,二维数组中每个单元格内的数字其实就是有多少个操作包含了这个单元格。又因为所有操作都是从(0, 0)
开始的,所以我们要求最大的整数出现的频次,那其实就是求:所有操作的的最小交集。
class Solution {
public int maxCount(int m, int n, int[][] ops) {
int a = m;
int b = n;
for(int i = 0; i < ops.length; i++){
a = Math.min(a, ops[i][0]);
b = Math.min(b, ops[i][1]);
}
return a * b;
}
}
复杂度分析
时间复杂度:\(O(n)\)
空间复杂度:\(O(1)\)
419. 甲板上的战舰
分析题意
这道题就是一道语文题,能不能做出来完全取决于你有没有理解题目在说什么。
首先,明确一个:题目是要统计board
上的军舰数量,而不是要你计算军舰的数量。我第一次做的时候就是以为要模拟计算军舰的数量,所以一直没有做出来。
其次,要明确战舰的定义,战舰或者是横着摆放或者是竖着摆放。也就是说一个战舰,他要么占一行的很多列,要么站一列的很多行;不可能同时占用很多行和很多列。
上述两个红框就是两个战舰,所以此时答案为2。
思路分析
其实我们只需要找到战舰的头就可以了,那么战舰的头怎么寻找呢?其实就是它的左侧和上侧都没有战舰,那么这个战舰一定是战舰头部。
class Solution {
public int countBattleships(char[][] board) {
int ans = 0;
for(int i = 0; i < board.length; i++){
for(int j = 0; j < board[0].length; j++){
// 此处有战舰
if(board[i][j] == 'X'){
boolean flag = true;
if(i - 1 >= 0 && board[i-1][j] != '.'){
flag = false;
}
if(j - 1 >= 0 && board[i][j-1] != '.'){
flag = false;
}
if(flag){
ans++;
}
}
}
}
return ans;
}
}
复杂度分析
时间复杂度:\(O(m \times n)\)
空间复杂度:\(O(1)\)
LeetCode - 二维数组及滚动数组的更多相关文章
- C. Arcade dp二维费用背包 + 滚动数组 玄学
http://codeforces.com/gym/101257/problem/C 询问从左上角走到右下角,每次只能向右或者向左,捡起三种物品算作一个logo,求最多能得到多少个logo. 设dp[ ...
- LeetCode二维数组中的查找
LeetCode 二维数组中的查找 题目描述 在一个 n*m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增.请完成一个搞笑的函数,输入这样的一个二维数组和一个整数,判断数 ...
- 洛谷 P1972 [SDOI2009]HH的项链-二维偏序+树状数组+读入挂(离线处理,思维,直接1~n一边插入一边查询),hahahahahahaha~
P1972 [SDOI2009]HH的项链 题目背景 无 题目描述 HH 有一串由各种漂亮的贝壳组成的项链.HH 相信不同的贝壳会带来好运,所以每次散步完后,他都会随意取出一段贝壳,思考它们所表达的含 ...
- poj3067 二维偏序树状数组
题解是直接对一维升序排列,然后计算有树状数组中比二维小的点即可 但是对二维降序排列为什么不信呢?? /* */ #include<iostream> #include<cstring ...
- hdu1081 DP类最大子段和(二维压缩+前缀和数组/树状数组计数)
题意:给出一个 n * n 的数字矩阵,问最大子矩阵和是多少. 由于和最长子段和问题类似,一开始想到的就是 DP ,一开始我准备用两个循环进行 DP ,对于每一个 (i,j) ,考察(i - 1,j) ...
- 二维偏序+树状数组【P3431】[POI2005]AUT-The Bus
Description Byte City 的街道形成了一个标准的棋盘网络 – 他们要么是北南走向要么就是西东走向. 北南走向的路口从 1 到 n编号, 西东走向的路从1 到 m编号. 每个路口用两个 ...
- $[SHOI2007]$ 园丁的烦恼 二维数点/树状数组
\(Sol\) 设一个矩阵的左上角为\((x_1,y_1)\),右下角为\((x_2,y_2)\),\(s_{x,y}\)是到\((1,1)\)二维前缀和,那么这个矩阵的答案显然是\(s_{x_2,y ...
- ACM-ICPC2018徐州网络赛 Features Track(二维map+01滚动)
Features Track 31.32% 1000ms 262144K Morgana is learning computer vision, and he likes cats, too. ...
- 【BZOJ】1047: [HAOI2007]理想的正方形(单调队列/~二维rmq+树状数组套树状数组)
http://www.lydsy.com/JudgeOnline/problem.php?id=1047 树状数组套树状数组真心没用QAQ....首先它不能修改..而不修改的可以用单调队列做掉,而且更 ...
随机推荐
- 【一本通提高博弈论】[ZJOI2009]取石子游戏
[ZJOI2009]取石子游戏 题目描述 在研究过 Nim 游戏及各种变种之后,Orez 又发现了一种全新的取石子游戏,这个游戏是这样的: 有 n n n 堆石子,将这 n n n 堆石子摆成一排.游 ...
- 第十八天python3 序列化和反序列化
思考: 内存中的字典.列表.集合以及各种对象,如何保存到一个文件中? 如果是自己定义的类的实例,如何保存到一个文件中? 如何从文件中读取数据,并让它们在内存中再次变成自己对应的类的实例? 要设计一套协 ...
- 字符串的操作和MAth工具类
字符串的操作 常用方法 判断功能方法 equals(String s)判断两个字符串是否相同,区分大小写 equsalsignorecase(String s) 判断两个字符串是否相同,不区分大小写 ...
- Java中类成员访问权限修饰符(public、protected、default、private)
1.public(公共的): 任何类都可以进行访问(最不严格). 2.protected(保护的): 同一包内的类以及其子类可以进行访问. 3.default(缺省的): 类中不加任何访问权限限定的成 ...
- 基于 Next.js实现在线Excel
如果要从头开始使用 React 构建一个完整的 Web 应用程序,需要哪些步骤? 这当然不像把大象装进冰箱那么简单,只需要分成三步:打开冰箱,拿起大象,塞进冰箱就好. 我们需要考虑细节有很多,比如: ...
- EPLAN 中的符号、元件、部件与设备之间的区别
符号(Symbol):电气符号是电器设备(Electrical equipment)的一种图形表达,符号存放在符号库中,是广大电气工程师之间的交流语言,用来传递系统控制的设计思维的.将设计思维体现出来 ...
- 技术分享 | MySQL数据误删除的总结
欢迎来到 GreatSQL社区分享的MySQL技术文章,如有疑问或想学习的内容,可以在下方评论区留言,看到后会进行解答 内容提要 用delete语句 使用drop.truncate删除表以及drop删 ...
- Python 实现列表与二叉树相互转换并打印二叉树封装类-详细注释+完美对齐
# Python 实现列表与二叉树相互转换并打印二叉树封装类-详细注释+完美对齐 from binarytree import build import random # https://www.cn ...
- LuoguP1725 琪露诺 (动态规划)
\(单调队列\) 或 \(堆\) 优化 #include <iostream> #include <cstdio> #include <cstring> #incl ...
- 对于Java中的Loop或For-each,哪个更快
Which is Faster For Loop or For-each in Java 对于Java中的Loop或Foreach,哪个更快 通过本文,您可以了解一些集合遍历技巧. Java遍历集合有 ...