59. 螺旋矩阵 II
定义一个总数,是所有格子走完中心的最大数,target = n*n
从 1 开始,每走一步,当前数 +1 ,while(curNum<=target) 就继续走
定义每圈螺旋走位的边界,初始值:left=0; right=n-1; top=0; bottom=n-1;
1、在顶部一行 从左到右走,从 left 走到 right
由于占了顶部一行,top 要向下缩进一行
2、在右边一列 从上到下走,从 top 走到 bottom
由于占了右边一列,right 要向左缩进一列
3、在底部一行 从右到左走,从 right 走到 left
由于占了底部一行,bottom 要向上缩进一行
4、在左边一列 从下到上走,从 bottom 走到 top
由于占了左边一列,left 要向右缩进一列
class Solution { public int[][] generateMatrix(int n) { int[][] resMat = new int[n][n]; int target = n*n; int left = 0; int right = n-1; int top = 0; int bottom = n-1; int curNum = 1; while(curNum <= target) { // 在顶部一行,从左走到右 for (int i=left;i<=right;i++) { // 注意这里是 [top][i] resMat[top][i] = curNum; curNum ++; } // 从左到右一行走完了,占了上边一行,top 要向下缩进一行 top++; // 在右部一列,从上走到下 for (int i=top;i<=bottom;i++) { // 注意这里是 [i][right] resMat[i][right] = curNum; curNum ++; } // 从上到下一列走完了,占了右边一列,right 要向左缩进一列 right--; // 在底部一行,从右走到左 for (int i=right;i>=left;i--) { // 注意这里是 >= left i-- // 注意这里是 [bottom][i] resMat[bottom][i] = curNum; curNum ++; } // 从右到左一行走完了,占了下边一行,bottom 要向上缩进一行 bottom--; // 在左部一列,从下走到上 for (int i=bottom;i>=top;i--) { // 注意这里是 >= top i-- // 注意这里是 [i][left] resMat[i][left] = curNum; curNum ++; } // 从下到上一列走完了,占了左边一列,left 要向右缩进一列 left++; } return resMat; } }
54. 螺旋矩阵
输入: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]
class Solution { public List<Integer> spiralOrder(int[][] matrix) { int length = matrix.length; int width = matrix[0].length; int sum = length*width; int left = 0; int right = width-1; int top = 0; int bottom = length-1; int i = 0; int j = 0; int cnt = 0; List<Integer> res = new ArrayList<>(); while(cnt<sum) { // 在顶部一行 j=top; //从左走到右 for(i=left;i<=right;i++) { res.add(matrix[j][i]); //每走一步,计数+1 cnt++; } // 在顶部一行,从左走到右,top向下 top++; // 加这个的原因是,因为总数不一定是4的倍数,可能是长方形,所以不一定能走完一个螺旋,就结束了 if (cnt==sum) { break; } //在右部一列 i=right; //从上走到下 // 注意这里 top 是已经++ 过的,所以这次走不到 [n,n], 但是前面在顶行走的时候,是 i<=right, 所以会走到[n,n] for(j=top;j<=bottom;j++) { res.add(matrix[j][i]); cnt++; } //在右部一列,从上走到下,right向左 right--; if (cnt==sum) { break; } //在底部一行 j=bottom; //从右走到左 for(i=right;i>=left;i--) { res.add(matrix[j][i]); cnt++; } //在底部一行,从右走到左,bottom向上 bottom--; if (cnt==sum) { break; } //在左部一列 i=left; //从下走到上 for(j=bottom;j>=top;j--) { res.add(matrix[j][i]); cnt++; } //在左部一列,从下走到上,left向右 left++; if (cnt==sum) { break; } } return res; } }
189. 轮转数组
class Solution { public void rotate(int[] nums, int k) { int n = nums.length; k = k % n; // 1234567 k=3 // 1.翻转所有元素 7654321 reverse(nums, 0, n-1); // 2.翻转 0...k 5674321 reverse(nums, 0, k-1); // 3.翻转 k...n 5671234 reverse(nums, k ,n-1); } private void reverse(int[] nums, int a, int b) { int start = a; int end = b; while(start<end) { int tmp = nums[start]; nums[start] = nums[end]; nums[end] = tmp; start++; end--; } } }
118. 杨辉三角
class Solution { /* 1 1 1 1 2 1 1 3 3 1 1 4 6 4 1 */ public List<List<Integer>> generate(int numRows) { List<List<Integer>> res = new ArrayList(); for (int i=0;i<numRows;i++) { List<Integer> curRow = new ArrayList(); curRow.add(1); for (int j=1;j<i;j++) { curRow.add(res.get(i-1).get(j-1) + res.get(i-1).get(j)); } if (i!=0) { curRow.add(1); } res.add(curRow); } return res; } }
73. 矩阵置零
第一行用来标记某列是否有0,因为某列有0的话,那么第一行的这一列所在的格也会被置为 0,所以不影响
第一列用来标记某行是否有0,因为某行有0的话,那么第一列的这一行所在的格也会被置为 0,所以不影响
但是这样就不知道第一行、第一列原本的数中,是否含0了,所以先遍历第一行第一列,用两个变量存储第一行第一列本来是否含0
步骤:
- 看第一行是否有0,存储在 firstRowHasZero 变量中
- 看第一列是否有0,存储在 firstColHasZero 变量中
- 遍历整个矩阵,如果某元素为0,则将这个元素对应过去的第一行第一列那两个元素置为0
- 根据第一行标记的结果,如果第一行某元素为0,则将这个元素所在的列全置为0
- 根据第一列标记的结果,如果第一列某元素为0,则将这个元素所在的行全置为0
- 根据 firstRowHasZero ,如果第一行原本有0,将第一行全置为0
- 根据 firstColHasZero ,如果第一列原本有0,将第一列全置为0
class Solution { public void setZeroes(int[][] matrix) { // 第一行用来标记某列是否有0,因为某列有0的话,那么第一行的这一列所在的格也会被置为 0,所以不影响 // 第一列用来标记某行是否有0,因为某行有0的话,那么第一列的这一行所在的格也会被置为 0,所以不影响 // 但是这样就不知道第一行、第一列原本的数中,是否含0了,所以先遍历第一行第一列,用两个变量存储第一行第一列本来是否含0 boolean firstRowHasZero = false; boolean firstColHasZero = false; // 看第一行是否有 0 for (int i=0;i<matrix[0].length;i++) { if (matrix[0][i]==0) { firstRowHasZero = true; break; } } // 看第一列是否有 0 for (int i=0;i<matrix.length;i++) { if (matrix[i][0]==0) { firstColHasZero = true; break; } } // 在第一行第一列标记某行某列是否有0 for (int i=1;i<matrix[0].length;i++) { for (int j=1;j<matrix.length;j++) { if (matrix[j][i] == 0) { matrix[j][0] = 0; matrix[0][i] = 0; } } } // 根据第一行标记的结果,将一些列置 0 // 注意要从 1 开始,否则如果左上角元素为 0 的话,直接把第一行第一列全置为0了 for (int i=1;i<matrix[0].length;i++) { if (matrix[0][i]==0) { for (int j=1;j<matrix.length;j++) { matrix[j][i] = 0; } } } // 根据第一列标记的结果,将一些行置 0 // 注意要从 1 开始,否则如果左上角元素为 0 的话,直接把第一行第一列全置为0了 for (int j=1;j<matrix.length;j++) { if (matrix[j][0]==0) { for (int i=1;i<matrix[0].length;i++) { matrix[j][i] = 0; } } } // 第一行原本有 0,将第一行置为0 if (firstRowHasZero) { for (int i=0;i<matrix[0].length;i++) { matrix[0][i]=0; } } // 第一列原本有 0,将第一列置为0 if (firstColHasZero) { for (int i=0;i<matrix.length;i++) { matrix[i][0]=0; } } } }
238. 除自身以外数组的乘积
class Solution { /* 原数组: [1 2 3 4] 左部分的乘积: 1 1 1*2 1*2*3 右部分的乘积: 2*3*4 3*4 4 1 结果: 1*2*3*4 1*3*4 1*2*4 1*2*3*1 */ // 从上面的图可以看出,当前位置的结果就是它左部分的乘积再乘以它右部分的乘积。 // 因此需要进行两次遍历,第一次遍历用于求左部分的乘积, // 第二次遍历在求右部分的乘积的同时,再将最后的计算结果一起求出来。 public int[] productExceptSelf(int[] nums) { int[] res = new int[nums.length]; int pre1 = 1; for (int i=0;i<nums.length;i++) { res[i] = pre1; pre1 = pre1*nums[i]; } int pre2 = 1; for (int i=nums.length-1;i>=0;i--) { res[i] = res[i]*pre2; pre2 = pre2*nums[i]; } return res; } }
41. 缺失的第一个正数
解法一:
最后的答案一定是 <= n+1 的一个正整数。
因为数组的每一个位置都按序放不同的正整数,那么结果就是 n+1。比如 [5,2,3,1,4] 结果就是 6
但实际情况多为还跳跃了一些,放了别的数,所以最后的结果是 <=n+1
所以用数组元素是否为负,来表示这个取值为这个下标的元素是否在数组里出现过。比如上图最后的结果 [-3,4,-7,-1,9,7] 第 0 个元素取值为负,说明 1 在数组里出现过;第 2 个元素取值为正,说明 3 在数组中出现过;第 3 个元素取值为负,说明 4 在数组中出现过。
解法二(我的,时间复杂度应该不够 O(n)):
结果的初始值为最小的正整数 1
- 如果数字出现过,就加到 set 里
- 如果数字 == res,说明这个 res 出现过,那么 res 就要继续往上加,加到一个没出现过的正整数(不在 set 里)
class Solution { public int firstMissingPositive(int[] nums) { // 最后的答案一定是 <= n+1 // 因为数组的每一个位置都按序放不同的正整数,那么结果就是 n+1 // 但实际情况多为还跳跃了一些,放了别的数,所以最后的结果是 <n+1 int n = nums.length; // 1.将所有负数变为 n+1 (负数都不可能是答案) for (int i=0;i<n;i++) { if (nums[i] <= 0) { nums[i] = n+1; } } // 2.将所有 <=n 的数,将其对应位置的数变为 负(最后如果为负,对于 1~n 的正整数,这些出现过) // 注意 [2,1] 第一次循环变为 [2,-1] 第二次循环到了 -1 这里,-1 不可能作为下标,所以要取绝对值 for (int i=0;i<n;i++) { int valueAbs = Math.abs(nums[i]); if (valueAbs <= n) { // 为什么要 -Math.abs(nums[valueAbs-1]) 加上绝对值符号 // 因为比如 [1,1] 第一次循环变为 [-1,1] 第二次循环就不能直接加 - ,否则就成了 [1,1] nums[valueAbs-1] = -Math.abs(nums[valueAbs-1]); } } // 3.遍历数组,找到第一个不为负的数,其下标就是没出现过的最小正整数 for (int i=0;i<n;i++) { if (nums[i] > 0) { // 因为下标是从 0 开始的,而正整数是从1开始的,所以最后要 + 1 return i+1; } } // [3,2,1] 最后变为 [-3,-2,-1],那么结果就是 4 return n+1; } public int firstMissingPositive2(int[] nums) { int res = 1; Set<Integer> set = new HashSet(); for (int i=0;i<nums.length;i++) { if (nums[i] > res) { set.add(nums[i]); } else if (nums[i] == res) { // 之前已经出现过的话,继续往上加,加到一个没出现过的 while (set.contains(res + 1)) { res++; } res++; } } return res; } }
48. 旋转图像
方法一:旋转
官方题解:https://leetcode.cn/problems/rotate-image/solution/xuan-zhuan-tu-xiang-by-leetcode-solution-vu3m/
图解:https://leetcode.cn/problems/rotate-image/solution/48-xuan-zhuan-tu-xiang-fu-zhu-ju-zhen-yu-jobi/
一圈一圈旋转,用一个临时变量作为出口
- 当矩阵大小 n 为偶数时,取前 n/2 行、前 n/2 列的元素为起始点;
- 当矩阵大小 n 为奇数时,取前 n/2 行、前 (n+1)/2 列的元素为起始点。
首尾已知,从末尾用最终表达式向前推导,直到逻辑闭环
tmp <= 5 <= 15 <= 16 <= 11
tmp <= 1 <= 13 <= 12 <= 10
tmp <= 4 <= 3 <= 6 <= 8
方法二:翻转
见题解:https://leetcode.cn/problems/rotate-image/solution/lu-qing-ge-chong-by-pennx-ce3x/
先写出最终的表达式:matrix[i][j] -> matrix[j][n-i-1]
根据最终的表达式推导出需要两步翻转完成
- 先上下翻转:
matrix[i][j] -> matrix[n-i-1][j]
- 再行列互换(对角线翻转):
matrix[n-i-1][j] ->
matrix[j][n-i-1]
上面两步得到了最终的表达式
class Solution { public void rotate(int[][] matrix) { int n = matrix.length; for (int i=0;i<n/2;i++) { for (int j=0;j<(n+1)/2;j++) { int tmp = matrix[i][j]; matrix[i][j] = matrix[n-j-1][i]; matrix[n-j-1][i] = matrix[n-i-1][n-j-1]; matrix[n-i-1][n-j-1] = matrix[j][n-i-1]; matrix[j][n-i-1] = tmp; } } } public void rotate2(int[][] matrix) { // 实际上一定是正方形,行列数是一样的,这里自己做了没必要的区分 int rowN = matrix.length; int colN = matrix[0].length; // 水平镜像翻转 for (int i=0;i<rowN/2;i++) { for (int j=0;j<colN;j++) { int tmp = matrix[i][j]; matrix[i][j] = matrix[rowN-i-1][j]; matrix[rowN-i-1][j] = tmp; } } // 主对角线镜像翻转 for (int i=0;i<rowN;i++) { for (int j=0;j<i;j++) { int tmp = matrix[i][j]; matrix[i][j] = matrix[j][i]; matrix[j][i] = tmp; } } } }
240. 搜索二维矩阵 II
解法一:O(mlogn)
由于矩阵 matrix 中每一行的元素都是升序排列的,因此我们可以对每一行都使用一次二分查找,判断target 是否在该行中,从而判断target 是否出现。
解法二:O(m+n)
数组从左到右和从上到下都是升序的,如果从右上角出发开始遍历呢?
会发现每次都是向左数字会变小,向下数字会变大,有点和二分查找树相似。二分查找树的话,是向左数字变小,向右数字变大。
所以我们可以把 target 和当前值比较。
- 如果 target 的值大于当前值,那么就向下走。
- 如果 target 的值小于当前值,那么就向左走。
- 如果相等的话,直接返回 true 。
class Solution { public boolean searchMatrix(int[][] matrix, int target) { // 从右上角开始 int i=matrix[0].length - 1; int j=0; int nowVal = matrix[j][i]; // 第一个元素就等于 target,直接返回 true if (nowVal == target) { return true; } // 向左 递减,向下递增 while (nowVal != target && i>=0 && j<=matrix.length-1) { nowVal = matrix[j][i]; // 小于目标值,就向下使其增加 if (nowVal < target) { j++; } // 大于目标值,就向左使其减少 else if (nowVal > target) { i--; } else { return true; } } return false; } }
74. 搜索二维矩阵
直接用 搜索二维矩阵II 的解法就能通过
官方题解里说是:
方法一:两次二分查找
思路
由于每行的第一个元素大于前一行的最后一个元素,且每行元素是升序的,所以每行的第一个元素大于前一行的第一个元素,因此矩阵第一列的元素是升序的。
我们可以对矩阵的第一列的元素二分查找,找到最后一个不大于目标值的元素,然后在该元素所在行中二分查找目标值是否存在。
36. 有效的数独
class Solution { public boolean isValidSudoku(char[][] board) { // 二维数组。1-9 行,每个数字出现的次数 int[][] rowCount = new int[9][9]; // 二维数组。1-9 列,每个数字出现的次数 int[][] colCount = new int[9][9]; // 第 i,j 个单元格,每个数字出现的次数 int[][][] gridCount = new int[3][3][9]; for (int i=0;i<board.length;i++) { for (int j=0;j<board[0].length;j++) { if (board[i][j]=='.') { continue; } // 数字是 1-9,下标是 0-8, 所以-1 int a = board[i][j] - '0' - 1; // 第 i 行,数字 a 出现的次数+1 rowCount[i][a]++; // 第 j 列,数字 a 出现的次数+1 colCount[j][a]++; int gridi = i/3; int gridj = j/3; // 第 (i/3, j/3) 个格子,数字 a 出现的次数+1 gridCount[gridi][gridj][a]++; // 如果每行/每列/每格,某数字出现大于一次,返回 false if (rowCount[i][a]>1 || colCount[j][a]>1 || gridCount[gridi][gridj][a]>1) { return false; } } } return true; } }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· DeepSeek “源神”启动!「GitHub 热点速览」
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· NetPad:一个.NET开源、跨平台的C#编辑器