剑指 Offer 66. 构建乘积数组

题目:给定一个数组 A[0,1,…,n-1],请构建一个数组 B[0,1,…,n-1],其中 B[i] 的值是数组 A 中除了下标 i 以外的元素的积, 即 B[i]=A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1]。不能使用除法。

示例:
      输入: [1,2,3,4,5]
      输出: [120,60,40,30,24]
提示:
      1、所有元素乘积之和不会溢出 32 位整数
      2、a.length <= 100000
解题思路:
本题的难点在于 不能使用除法 ,即需要 只用乘法 生成数组 B 。根据题目对 B[i] 的定义,可列表格,如下图所示。

根据表格的主对角线(全为 1 ),可将表格分为 上三角下三角 两部分。分别迭代计算下三角和上三角两部分的乘积,即可 不使用除法 就获得结果。

算法流程:
1、初始化:数组 B ,其中 B[0]=1 ;辅助变量 tmp=1 ;
2、计算 B[i] 的 下三角 各元素的乘积,直接乘入 B[i] ;
3、计算 B[i] 的 上三角 各元素的乘积,记为 tmp ,并乘入 B[i] ;
4、返回 B 。

复杂度分析:

  • 时间复杂度 O(N): 其中 N 为数组长度,两轮遍历数组 a ,使用 O(N) 时间。
  • 空间复杂度 O(1): 变量 tmp 使用常数大小额外空间(数组 b 作为返回值,不计入复杂度考虑)。
class Method{public int[] constructArr(int[] a) {if(a.length == 0) return new int[0];int[] b = new int[a.length];b[0] = 1;int tmp = 1;for(int i = 1; i < a.length; i++) {b[i] = b[i - 1] * a[i - 1];}for(int i = a.length - 2; i >= 0; i--) {tmp *= a[i + 1];b[i] *= tmp;}return b;}
}

剑指 Offer 29. 顺时针打印矩阵

题目:输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。

示例 1:
      输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
      输出:[1,2,3,6,9,8,7,4,5]
示例 2:
      输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
      输出:[1,2,3,4,8,12,11,10,9,5,6,7]
限制:
      1、0 <= matrix.length <= 100
      2、0 <= matrix[i].length <= 100
方法一:模拟
可以模拟打印矩阵的路径。初始位置是矩阵的左上角,初始方向是向右,当路径超出界限或者进入之前访问过的位置时,顺时针旋转,进入下一个方向。

判断路径是否进入之前访问过的位置需要使用一个与输入矩阵大小相同的辅助矩阵 visited,其中的每个元素表示该位置是否被访问过。当一个元素被访问时,将 visited 中的对应位置的元素设为已访问。

如何判断路径是否结束?由于矩阵中的每个元素都被访问一次,因此路径的长度即为矩阵中的元素数量,当路径的长度达到矩阵中的元素数量时即为完整路径,将该路径返回。

class Method1{public int[] spiralOrder(int[][] matrix) {if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {return new int[0];}int rows = matrix.length, columns = matrix[0].length;boolean[][] visited = new boolean[rows][columns];int total = rows * columns;int[] order = new int[total];int row = 0, column = 0;int[][] directions = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};int directionIndex = 0;for (int i = 0; i < total; i++) {order[i] = matrix[row][column];visited[row][column] = true;int nextRow = row + directions[directionIndex][0], nextColumn = column + directions[directionIndex][1];if (nextRow < 0 || nextRow >= rows || nextColumn < 0 || nextColumn >= columns || visited[nextRow][nextColumn]) {directionIndex = (directionIndex + 1) % 4;}row += directions[directionIndex][0];column += directions[directionIndex][1];}return order;}
}

复杂度分析:

  • 时间复杂度:O(mn),其中 m 和 n 分别是输入矩阵的行数和列数。矩阵中的每个元素都要被访问一次。
  • 空间复杂度:O(mn)。需要创建一个大小为 m×n 的矩阵 visited 记录每个位置是否被访问过。

方法二:按层模拟
可以将矩阵看成若干层,首先打印最外层的元素,其次打印次外层的元素,直到打印最内层的元素。

定义矩阵的第 k 层是到最近边界距离为 k 的所有顶点。例如,下图矩阵最外层元素都是第 1 层,次外层元素都是第 2 层,剩下的元素都是第 3 层。

[[1, 1, 1, 1, 1, 1, 1],[1, 2, 2, 2, 2, 2, 1],[1, 2, 3, 3, 3, 2, 1],[1, 2, 2, 2, 2, 2, 1],[1, 1, 1, 1, 1, 1, 1]]

对于每层,从左上方开始以顺时针的顺序遍历所有元素。假设当前层的左上角位于 (top,left),右下角位于 (bottom,right),按照如下顺序遍历当前层的元素。
1、从左到右遍历上侧元素,依次为 (top,left) 到 (top,right)。
2、从上到下遍历右侧元素,依次为 (top+1,right) 到 (bottom,right)。
3、如果 left<right 且 top<bottom,则从右到左遍历下侧元素,依次为 (bottom,right−1) 到 (bottom,left+1),以及从下到上遍历左侧元素,依次为 (bottom,left) 到 (top+1,left)。

遍历完当前层的元素之后,将 left 和 top 分别增加 1,将 right 和 bottom 分别减少 1,进入下一层继续遍历,直到遍历完所有元素为止。

class Method2{public int[] spiralOrder(int[][] matrix) {if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {return new int[0];}int rows = matrix.length, columns = matrix[0].length;int[] order = new int[rows * columns];int index = 0;int left = 0, right = columns - 1, top = 0, bottom = rows - 1;while (left <= right && top <= bottom) {for (int column = left; column <= right; column++) {order[index++] = matrix[top][column];}for (int row = top + 1; row <= bottom; row++) {order[index++] = matrix[row][right];}if (left < right && top < bottom) {for (int column = right - 1; column > left; column--) {order[index++] = matrix[bottom][column];}for (int row = bottom; row > top; row--) {order[index++] = matrix[row][left];}}left++;right--;top++;bottom--;}return order;}
}

复杂度分析

  • 时间复杂度:O(mn),其中 m 和 n 分别是输入矩阵的行数和列数。矩阵中的每个元素都要被访问一次。
  • 空间复杂度:O(1)。除了输出数组以外,空间复杂度是常数。

方法三:模拟、设定边界
解题思路:

根据题目示例 matrix = [[1,2,3],[4,5,6],[7,8,9]] 的对应输出 [1,2,3,6,9,8,7,4,5] 可以发现,顺时针打印矩阵的顺序是 “从左向右、从上向下、从右向左、从下向上” 循环。

  • 因此,考虑设定矩阵的“左、上、右、下”四个边界,模拟以上矩阵遍历顺序。

算法流程:
1、空值处理: 当 matrix 为空时,直接返回空列表 [] 即可。
2、初始化: 矩阵 左、右、上、下 四个边界 l , r , t , b ,用于打印的结果列表 res 。
3、循环打印: “从左向右、从上向下、从右向左、从下向上” 四个方向循环,每个方向打印中做以下三件事 (各方向的具体信息见下表) ;
(1)根据边界打印,即将元素按顺序添加至列表 res 尾部;
(2)边界向内收缩 1 (代表已被打印);
(3)判断是否打印完毕(边界是否相遇),若打印完毕则跳出。
4、返回值: 返回 res 即可。

打印方向 1. 根据边界打印 2. 边界向内收缩 3. 是否打印完毕
从左向右 左边界l ,右边界 r 上边界 t 加 1 是否 t > b
从上向下 上边界 t ,下边界b 右边界 r 减 1 是否 l > r
从右向左 右边界 r ,左边界l 下边界 b 减 1 是否 t > b
从下向上 下边界 b ,上边界t 左边界 l 加 1 是否 l > r

复杂度分析:

  • 时间复杂度 O(MN): M, N 分别为矩阵行数和列数。
  • 空间复杂度 O(1): 四个边界 l , r , t , b 使用常数大小的 额外 空间( res 为必须使用的空间)。

代码:

Java 代码利用了 ++ 操作的便利性,详情可见 ++i 和 i++ 的区别 ;

res[x++] 等价于先给 res[x] 赋值,再给 x 自增 1 ;
++t > b 等价于先给 t 自增 1 ,再判断 t > b 逻辑表达式。

class Method3{public int[] spiralOrder(int[][] matrix) {if(matrix.length == 0) return new int[0];int l = 0, r = matrix[0].length - 1, t = 0, b = matrix.length - 1, x = 0;int[] res = new int[(r + 1) * (b + 1)];while(true) {for(int i = l; i <= r; i++) res[x++] = matrix[t][i]; // left to right.if(++t > b) break;for(int i = t; i <= b; i++) res[x++] = matrix[i][r]; // top to bottom.if(l > --r) break;for(int i = r; i >= l; i--) res[x++] = matrix[b][i]; // right to left.if(t > --b) break;for(int i = b; i >= t; i--) res[x++] = matrix[i][l]; // bottom to top.if(++l > r) break;}return res;}
}

剑指 Offer 31. 栈的压入、弹出序列

题目:输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。

示例 1:
      输入:pushed = [1,2,3,4,5], popped = [4,5,3,2,1]
      输出:true
      解释:我们可以按以下顺序执行:
      push(1), push(2), push(3), push(4), pop() -> 4,
      push(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1
示例 2:
      输入:pushed = [1,2,3,4,5], popped = [4,3,5,1,2]
      输出:false
      解释:1 不能在 2 之前弹出。
提示:
      1、0 <= pushed.length == popped.length <= 1000
      2、0 <= pushed[i], popped[i] < 1000
      3、pushed 是 popped 的排列。
解题思路:
如下图所示,给定一个压入序列pushed和弹出序列popped,则压入/弹出操作的顺序(即排列)是 唯一确定 的。
如下图所示,栈的数据操作具有 先入后出 的特性,因此某些弹出序列是无法实现的。
考虑借用一个辅助栈 stack ,模拟 压入 / 弹出操作的排列。根据是否模拟成功,即可得到结果。

  • 入栈操作: 按照压栈序列的顺序执行。
  • 出栈操作: 每次入栈后,循环判断 “栈顶元素 = 弹出序列的当前元素” 是否成立,将符合弹出序列顺序的栈顶元素全部弹出。

由于题目规定 栈的所有数字均不相等 ,因此在循环入栈中,每个元素出栈的位置的可能性是唯一的(若有重复数字,则具有多个可出栈的位置)。因而,在遇到 “栈顶元素 = 弹出序列的当前元素” 就应立即执行出栈。

算法流程:
1、初始化: 辅助栈 stack ,弹出序列的索引 i ;
2、遍历压栈序列: 各元素记为 num ;
(1)元素 num 入栈;
(2)循环出栈:若 stack 的栈顶元素 = 弹出序列元素 popped[i] ,则执行出栈与 i++ ;
3、返回值: 若 stack 为空,则此弹出序列合法。
复杂度分析:

  • 时间复杂度 O(N): 其中 N 为列表 pushed 的长度;每个元素最多入栈与出栈一次,即最多共 2N 次出入栈操作。
  • 空间复杂度 O(N): 辅助栈 stack 最多同时存储 N 个元素。

代码:
题目指出 pushed 是 popped 的排列 。因此,无需考虑 pushed 和 popped 长度不同包含元素不同 的情况。

class Method{public boolean validateStackSequences(int[] pushed, int[] popped) {Stack<Integer> stack = new Stack<>();int i = 0;for(int num : pushed) {stack.push(num); // num 入栈while(!stack.isEmpty() && stack.peek() == popped[i]) { // 循环判断与出栈stack.pop();i++;}}return stack.isEmpty();}
}

总结:

今天晚上已经将剑指Offer的75道题已经更新完了!当然这并不是意味着数据结构和算法系列就已经结束了!数据结构和算法当然还有很多!我准备后续再更新数据结构和算法的其他模块!剑指Offer只是其中的一部分!还有各种的算法,如:ACM等等。明天开始准备为小伙伴们整理一波Java基础的学习路线,供初学的小伙伴们学习!对于在校想学习Java的小伙伴会有些帮助哦!小伙伴们的浏览、收藏、点赞、关注就是我前进道路上的最大动力!
      今天是最不幸的日子,早上刚朦胧的睁开眼,阳光普照一切似乎都很美好!可就在一瞬间的时间,网上处处报道袁隆平院士逝世的消息!就在10:30的时候散播谣言的平台公开道歉!这场因谣言产生的风波才算过去!因此,在任何事情没有确凿证据之前,先管好自己的嘴!不信谣、不传谣!
      就在下午14:00整,我手机上的一条信息震撼了全世界!官方报道袁隆平院士真正的离开了我们!一代伟人就这样永远的离开了我们!我的内心极其的沉痛!全国人民的内心也无比的沉重!如果没有袁隆平院士,不知道还有多少人可能到现在还填不饱肚子!正因为有了袁隆平院士,粮食产量日益增长,国家才真正解决了温饱问题!说起来天气也怪,下午的天由阴沉转变为雨天,在回到家之前,在路上又看到吴孟超院士也逝世了。在同一天的时间里共和国失去了两位得力的干将!他们也永远的离开了我们!
      两位院士为国家做出的贡献实在是太多太多了!一个让所有的人不再饿肚子的折磨;一个让所有的人不再受肝胆的病痛折磨。两位共和国院士的逝世对国家来说都是巨大的损失!或许这场雨是老天在为这两位院士的逝世而哭泣吧!在以后的日子里吾辈应当养成先辈良好的品德和思想!养成勤俭节约、淡泊名利的作风!争取做一个对国家对社会有贡献的人!逝去的先辈们,你们的大功大德我们后辈定将铭记在心,你们的丰功伟绩定将载入史册、流芳百世!
      臧克家说过:有的人活着,他已经死了!有的人死了,他还活着!虽然两位院士永久的离开了我们,但他们却活在了我们每个人的心中!吾辈应当自强、继续努力、奋发图强!争取早日成为国家的栋梁之材为国家和人民做一份真正的实事!
      向逝去的英雄致敬!愿逝去的英雄能够在天堂得以安息!
      最后,愿我们都能在各行各业中能够取得不同的成就,不负亲人、朋友、老师、长辈和国家的期望!能够用自身的所学知识为国家贡献出自己的一份力量!一起加油!
                                                                                                                       2021年5月22日夜

数据结构和算法三十六相关推荐

  1. 数据结构笔记(三十六)-- 插入排序与直接插入排序

    插入排序 插入排序就是将要排序的表分成有序表和无序表两个部分,在无序表中取一个元素,在有序表中找到其位置,然后将其插入到有序表中.这样的排序算法就是插入排序 一.插入排序的分类 直接插入排序 折半插入 ...

  2. OpenCV学习笔记(三十六)——Kalman滤波做运动目标跟踪 OpenCV学习笔记(三十七)——实用函数、系统函数、宏core OpenCV学习笔记(三十八)——显示当前FPS OpenC

    OpenCV学习笔记(三十六)--Kalman滤波做运动目标跟踪 kalman滤波大家都很熟悉,其基本思想就是先不考虑输入信号和观测噪声的影响,得到状态变量和输出信号的估计值,再用输出信号的估计误差加 ...

  3. Android版数据结构与算法汇总十二章

    Android版数据结构与算法(一):基础简介 https://www.cnblogs.com/leipDao/p/9140726.html Android版数据结构与算法(二):基于数组的实现Arr ...

  4. 程序员编程艺术第三十六~三十七章、搜索智能提示suggestion,附近点搜索

    第三十六~三十七章.搜索智能提示suggestion,附近地点搜索 作者:July.致谢:caopengcs.胡果果. 时间:二零一三年九月七日. 题记 写博的近三年,整理了太多太多的笔试面试题,如微 ...

  5. 三十六、Java集合中的HashMap

    @Author:Runsen @Date:2020/6/3 作者介绍:Runsen目前大三下学期,专业化学工程与工艺,大学沉迷日语,Python, Java和一系列数据分析软件.导致翘课严重,专业排名 ...

  6. 嵌入式实时操作系统ucos-ii_「正点原子NANO STM32开发板资料连载」第三十六章 UCOSII 实验 1任务调度...

    1)实验平台:alientek NANO STM32F411 V1开发板2)摘自<正点原子STM32F4 开发指南(HAL 库版>关注官方微信号公众号,获取更多资料:正点原子 第三十六章 ...

  7. 三十六進制之間隨便轉換

    去年在網上給一家公司投簡歷的時候,對方要求寫一個任意進制轉換的函數,當時沒有回過神來,也不知道JAVA中有這樣的函數,呵呵.于是就自己操刀,寫了這個三十六進制之音隨便轉的函數.不過,權當練習吧,如果你 ...

  8. 实例三十六:精确除法计算(*)

    实例三十六:精确除法计算 问题描述: 使用数组精确计算 M/N(0<M<N<100)的值.如果 M/N 是无限循环小数,则计算输出它的第一个循环节,并输出循环节的起止位置. The ...

  9. 推荐系统三十六式——学习笔记(三)

    由于工作需要,开始学习推荐算法,参考[极客时间]->[刑无刀大牛]的[推荐系统三十六式],学习并整理. 3 原理篇之紧邻推荐 3.1 协同过滤 要说提到推荐系统中,什么算法最名满天下,我想一定是 ...

最新文章

  1. VTK:可视化之ScaleGlyphs
  2. 资源图与死锁定理的灵活运用
  3. 未能打开组策略对象 您可能没有合适的权限
  4. AC010笔记之三:总结
  5. php5.6源码包,PHP-5.6.8 源码包编译安装
  6. 唯唯码 - ios/android的app下载地址合并为一个二维码
  7. PMP考试重点总结四——规划过程组(2)
  8. 如何使用Veeam One默认安装出来的Sql Server数据库
  9. scipy.ndimage.measurements label理解
  10. arcgis 循环模型批量处理_科学网-ArcGIS模型构建器批处理操作-张凌的博文
  11. 一台win10pc无法连接win7共享打印机
  12. Python每日一记42机器学习中特征重要性feature_importances_
  13. 是否对纯色背景的IDE感到乏味?那就让vscode背景变成你想要的样子
  14. python 官网下载地址
  15. android 四大组件Broadcast Receiver
  16. linux下jdk的安装与配置jdk-6u45-linux-i586.bin
  17. animation css 透明度逐渐_CSS实现透明度变化的动画 (淡入淡出效果)
  18. 【统计学习】5分钟了解假设检验中的第一类错误和第二类错误
  19. 组策略(GPO)利用与横向移动
  20. Laravel中curl_multi并发爬取百度关键词排名

热门文章

  1. 1609. 奇偶树-层次遍历-力扣双百代码
  2. 英语发音规则---oo
  3. 入门数据产品,so easy
  4. FPGA是什么?为什么要使用它?
  5. 2014阿里巴巴校园招聘笔试
  6. 北汇信息 | 什么是高级驾驶辅助系统:ADAS 概述
  7. 对卡尔曼滤波的系统学习
  8. ASIC加速技术的发展趋势:基于区块链和量子计算的ASIC加速方案
  9. 自考计算机基础00018难吗,自考公共课00018-计算机应用基础(看完必过).doc
  10. 一种基于RMON的网络探测器