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;
    }
}