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

步骤:

  1. 看第一行是否有0,存储在 firstRowHasZero 变量中
  2. 看第一列是否有0,存储在 firstColHasZero 变量中
  3. 遍历整个矩阵,如果某元素为0,则将这个元素对应过去的第一行第一列那两个元素置为0
  4. 根据第一行标记的结果,如果第一行某元素为0,则将这个元素所在的列全置为0
  5. 根据第一列标记的结果,如果第一列某元素为0,则将这个元素所在的行全置为0
  6. 根据 firstRowHasZero ,如果第一行原本有0,将第一行全置为0
  7. 根据 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;
    }
}
复制代码