LeetCode刷题笔记——数组篇

233 阅读11分钟

数组

tip:本文只用于个人记录

485.最大连续1(简单)

给定一个二进制数组 nums , 计算其中最大连续 1 的个数。

class Solution {
    //遍历一次数组即可
    public int findMaxConsecutiveOnes(int[] nums) {
        int count = 0;
        int max = 0;
        for(int n : nums){
            if(n != 1){
                count = 0;
            }else{
                count++;
                if(count > max){
                    max = count;
                }
            }
        }
        return max;
    }
}

485.最大连续1(简单)

当提莫攻击艾希,艾希的中毒状态正好持续 duration 秒。

正式地讲,提莫在 t 发起发起攻击意味着艾希在时间区间 [t, t + duration - 1](含 t 和 t + duration - 1)处于中毒状态。如果提莫在中毒影响结束 前 再次攻击,中毒状态计时器将会 重置 ,在新的攻击之后,中毒影响将会在 duration 秒后结束。

给你一个非递减的整数数组 timeSeries ,其中 timeSeries[i] 表示提莫在 timeSeries[i] 秒时对艾希发起攻击,以及一个表示中毒持续时间的整数 duration。

返回艾希处于中毒状态的总秒数。

class Solution {
    //根据每次攻击开始时间计算中毒效果结束时间
    //若此时间小于下次攻击开始时间,则持续时间加duration
    //若此时间大于下次攻击开始时间,则持续时间加两次攻击开始时间的间隔
    //将攻击时间开始时间重置
    public int findPoisonedDuration(int[] timeSeries, int duration) {
        int len = timeSeries.length;
        if(len == 0) return 0;
        if(len == 1) return duration;
        int result = 0;
        int endtime = 0;
        for(int i = 0; i < len; i++){
            if(i != len - 1){       //防止数组越界
                endtime = timeSeries[i] + duration;
                if(endtime <= timeSeries[i+1]){
                    result += duration;
                }else{
                    result += (timeSeries[i+1] - timeSeries[i]);
                }
            }else{
                result += duration;     //最后一次攻击直接加效果持续时间即可
            }    
        }
        return result;
    }
}

414.第三大的数(简单)★

给一个非空数组,返回数组中第三大的数。若不存在,则返回数组中最大的数。

解法1:
class Solution {
    //维持一个长度为三的有序集合,遍历一次数组,将当前遍历到的数加入容器
    //当容器长度超过3时,删除容器中最小的数,当容器中的数不足三个时,返回最大数
    //需要特别注意TreeSet的方法写法
    public int thirdMax(int[] nums) {
        TreeSet<Integer> s = new TreeSet<Integer>();
        for(int num : nums){
            s.add(num);
            if(s.size() > 3){
                s.remove(s.first());
            }
        }
        return s.size() == 3 ? s.first() : s.last();
    }
}

解法2:
class Solution {
    //使用三个变量a,b,c记录最大值,次大值,最小值
    public int thirdMax(int[] nums) {
        Integer a = null;
        Integer b = null;
        Integer c = null;
        for(int num : nums){
            if(a == null || num > a){   //使用null值在条件比较时需要先判断非空                  
                c = b;                 //若将abc初始值设为Integer.MAX可以不用判断空指针,使判断语句更简便
                b = a;
                a = num;
            }else if(num < a && (b == null || num > b)){
                c = b;
                b = num;
            }else if(b != null && b > num && (c == null || num > c)){
                c = num;
            }
        }
        return c == null ? a : c;       //不够三个元素时
    }
}

628.三个数的最大乘积(简单)★

给你一个整型数组 nums ,在数组中找出由三个数组成的最大乘积,并输出这个乘积。

class Solution {
    //先对数组进行排序,当数组内的元素全为正数或全为负数时,最大乘积即为最大三个数的乘积
    //当元素有正有负时,最大乘积有可能时三个最大正数之积,也可能是两个最小负数和最大正数之积
    //也可以不用排序,只需要找出最大的三个数和最小的两个数即可确定最大乘积,故只要对数组进行一次线性扫描即可
    public int maximumProduct(int[] nums) {
        Arrays.sort(nums);
        int len = nums.length;
        int max1 = nums[len-1] * nums[len-2] * nums[len-3];
        int max2 = nums[0] * nums[1] * nums[len-1];
        return max1 > max2 ? max1 : max2;
    }
}

645.错误的集合(简单)

集合 s 包含从 1 到 n 的整数。但有一个数字重复,有一个数字丢失

给定一个数组 nums 代表了集合 S 发生错误后的结果。

请你找出重复出现的整数,再找到丢失的整数,将它们以数组的形式返回

class Solution {
    //使用HashMap记录数字与其出现的次数,再遍历找到重复和丢失的值
    //实际上也可以直接使用一个数组来记录,索引对应数字,数组中存储出现次数
    public int[] findErrorNums(int[] nums) {
        int len = nums.length;
        HashMap<Integer, Integer> map = new HashMap<>();
        for(int num : nums){
            map.put(num, map.getOrDefault(num, 0) + 1);     //注意getORDefault的用法,num不存在时返回0
        }
        int[] ans = new int[2];
        for(int i = 1; i < len + 1; i++){
            int count = map.getOrDefault(i, 0);
            if(count == 2){
                ans[0] = i;
            }else if(count == 0){
                ans[1] = i;
            }
        }
        return ans;
    }
}

697.数组的度(简单)★

给定一个非空且只包含非负数的整数数组 nums,数组的 度 的定义是指数组里任一元素出现次数的最大值。你的任务是在 nums 中找到与 nums 拥有相同大小的度的最短连续子数组,返回其长度。

示例

输入:nums = [1,2,2,3,1] 输出:2

1和2出现的次数都为2,但子数组[2,2]长度比[1,2,2,3,1]短,故返回[2,2]长度2

class Solution {
    //由于题目需要求得最短连续子数组,故需要对每个数字记录出现的次数,第一次和最后一次出现的位置
    //故使用一个HashMap<Integer, int[]>
    public int findShortestSubArray(int[] nums) {
        HashMap<Integer, int[]> map = new HashMap<>();
        int len = nums.length;
        for(int i = 0; i < len; i++){
            if(map.containsKey(nums[i])){
                map.get(nums[i])[0]++;      //出现次数累加
                map.get(nums[i])[2] = i;    //更新最后一次出现的位置
            }else{
                map.put(nums[i], new int[]{1, i, i});
            }
        }
        int minLen = 0, maxNum = 0;     //分别表示最短连续子数组的长度和出现次数最多的数字出现的次数
        for(Map.Entry<Integer, int[]> entry : map.entrySet()){
            int[] arr = entry.getValue();
            if(maxNum < arr[0]){
                maxNum = arr[0];
                minLen = arr[2] - arr[1] + 1;
            }else if(maxNum == arr[0]){
                if(minLen > arr[2] - arr[1] + 1){
                    minLen = arr[2] - arr[1] + 1;
                }
            }
        }
        return minLen;
    }
}

448.找到所有数组中消失的数字(简单)

给你一个含 n 个整数的数组 nums ,其中 nums[i] 在区间 [1, n] 内。请你找出所有在 [1, n] 范围内但没有出现在 nums 中的数字,并以数组的形式返回结果。

输入: nums = [4,3,2,7,8,2,3,1] 输出: [5,6]

class Solution {
    //遍历nums,使用一个长度为n的数组记录出现的数字,再遍历一次记录数组即可得到未出现的数字
    public List<Integer> findDisappearedNumbers(int[] nums) {
        int len = nums.length;
        int[] record = new int[len + 1];    //舍弃索引为0的位置,使数字与索引一一对应
        ArrayList<Integer> list = new ArrayList<>();
        for(int i = 0; i < len; i++){
            record[nums[i]]++;
        }
        for(int i = 1; i < len + 1; i++){       //遍历record
            if(record[i] == 0){
                list.add(i);
            }
        }
        return list;    
    }
}

//实际上可以把nums本身作为hash数组,不需要使用额外的空间
//具体做法是遍历到一个数时,就把此数对应位置加上n,因为数组中的数为1-n,加上n必然大于n
//要取得此位置上原本的数只需要对n取模即可,最后哪个位置上的数小于n即说明未被加过n,即未出现的数字

class Solution {
    public List<Integer> findDisappearedNumbers(int[] nums) {
        int n = nums.length;
        ArrayList<Integer> list = new ArrayList<>();
        for(int num : nums){
            int x = (num - 1) % n;  //要取得原来的数需要取模
            nums[x] += n;
        }
        for(int i = 0; i < n; i++){
            if(nums[i] <= n){
                list.add(i+1);
            }
        }
        return list;
    }
}

442.数组中重复的数据(中等)★

给你一个长度为 n 的整数数组 nums ,其中 nums 的所有整数都在范围 [1, n] 内,且每个整数出现 一次 或 两次 。请你找出所有出现 两次 的整数,并以数组形式返回。 你必须设计并实现一个时间复杂度为 O(n) 且仅使用常量额外空间的算法解决此问题

class Solution {
    //由于数组中的数都在1-n之间,可以把原数组nums作为hash数组
    //遍历到一个数,将这个数对应位置上的元素加上n,取数时取模即可得到原来的数
    //出现两次的数会加两次n,比2n大,其他数加一次n,小于或等于2n
    public List<Integer> findDuplicates(int[] nums) {
        int n = nums.length;
        ArrayList<Integer> list = new ArrayList<>();
        for(int num : nums){
            int x = (num - 1) % n;  //num对应num-1位置,取模恢复为原来的数
            nums[x] += n;
        }

        for(int i = 0; i < n; i++){
            if(nums[i] > 2*n){
                list.add(i + 1);
            }
        }
        return list;
    }
}

//此题与上一题即原地哈希,除了上述解法,还可以使用正负号标记或将元素放置到对应位置上等方法

41.缺失的第一个正数(困难)★

给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。 请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。

法1
class Solution {
    //数组长度为n,故要找的数只可能出现在1~n+1(n+1为特殊情况,另外判断)
    //使用原地哈希的思路,把遍历到的数(在1~n范围内)放到对应位置,即nums[i] = i + 1
    //再遍历一次数组,遇到第一个位置与数据对应不上的数据时,其位置对应的数即为所求
    //例如[3,4,-1,1],调整后变为[1,-1,3,4],-1不在1~4范围内,故其位置对应的数2即为未出现的最小整数
    
    public int firstMissingPositive(int[] nums) {
        int n = nums.length;
        for(int i = 0; i < n; i++){
            //只操作在范围内的数,交换的数不能相同,换数并不是只换一次,换完后若当前的数满足条件还要继续换
            while(nums[i] != (i+1) && nums[i]< n+1 && nums[i] > 0 && nums[nums[i]-1] != nums[i]){    
                swap(nums, nums[i] - 1, i);     //交换nums[nums[i]-1]与nums[i]的值
            }
        }
        for(int i = 0; i < n; i++){
            if(nums[i] != i + 1){       //遇到第一个不在其对应位置上的数即为答案
                return i + 1;
            }
        }
        return n + 1;   //类似[1,2,3]的情况时,答案为4
    }

    public void swap(int[] nums, int i, int j){     //交换数组应该输入要交换的索引,而不是数
        int temp;
        temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

法2:如图所示

图片.png

274.H指数(中等)

给你一个整数数组 citations ,其中 citations[i] 表示研究者的第 i 篇论文被引用的次数。计算并返回该研究者的 h 指数。

根据维基百科上 h 指数的定义:h 代表“高引用次数”,一名科研人员的 h指数是指他(她)的 (n 篇论文中)总共有 h 篇论文分别被引用了至少 h 次。且其余的 n - h 篇论文每篇被引用次数 不超过 h 次。

如果 h 有多种可能的值,h 指数 是其中最大的那个。

输入: citations = [3,0,6,1,5]
输出: 3
class Solution {
    //先对数组进行排序,然后从大到小列举可能出现的h指数,h指数不超过数组长度
    public int hIndex(int[] citations) {
        int len = citations.length;
        Arrays.sort(citations);
        for(int i = len; i > 0; i--){
            if(citations[len - i] >= i){
                return i;
            }
        }
        return 0;
    }
}

//也可以使用计数排序,把被引用的数量限制在n之内(超过n的都赋值为n)
//使用一个n长度的数组记录被引用次数和论文数量的关系
//最后从n开始遍历数组,遇到第一个符合条件的输出,不符合条件的累加论文数量

453.最小操作次数使数组元素相等(简单)★

给你一个长度为 n 的整数数组,每次操作将会使 n - 1 个元素增加 1 。返回让数组所有元素相等的最小操作次数。

class Solution {
    //每次操作将使n-1个元素增加1,可以反过来理解为将1个元素减少1
    //故只需要累加每个元素与数组最小值的差值即可获取操作次数
    public int minMoves(int[] nums) {
        int min = Arrays.stream(nums).min().getAsInt();
        int ans = 0;
        for(int num : nums){
            ans += num - min;
        }
        return ans;
    }
}

665.非递减数列(中等)★

给你一个长度为 n 的整数数组 nums ,请你判断在 最多 改变 1 个元素的情况下,该数组能否变成一个非递减数列。

我们是这样定义一个非递减数列的: 对于数组中任意的 i (0 <= i <= n-2),总满足 nums[i] <= nums[i + 1]。

class Solution {
    //需要依次遍历相邻的两个元素,并且判断这两个元素是否递减
    //遇到一个递减序列直接修改元素
    //有两种修改元素的方法,修改前面等于后面或者后面等于前面,修改完后看是否满足非递减序列
    public boolean checkPossibility(int[] nums) {
        int len = nums.length;
        if(len <= 2) return true;
        for(int i = 0; i < len - 1; i++){
            if(nums[i] > nums[i+1]){
                int temp = nums[i];
                nums[i] = nums[i+1];
                if(isSorted(nums)){
                    return true;
                }
                nums[i] = temp;
                nums[i+1] = nums[i];
                if(isSorted(nums)){
                    return true;
                }
                return false;
            }
        }
        return true;
    }

    public boolean isSorted(int[] nums){        //只需要相邻元素都满足nums[i] <= nums[i+1]即可
        int n = nums.length;
        for(int i = 0; i < n - 1; i++){
            if(nums[i] > nums[i+1]){
                return false;
            }
        }
        return true;
    }
}

//另外一个思路是当修改nums[i] = nums[i + 1]时,需要判断是否仍然满足nums[i - 1] < nums[i]
//若不满足则只能修改nums[i + 1] = nums[i], 这个不会影响前面地序列关系,继续检查是否为非递减序列
//这个方法避免了每次都去从头开始检查数组,整个过程只需要遍历一次数组

283.移动0(简单)

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。注意必须在不复制数组的情况下原地对数组进行操作。

class Solution {
    //对0进行计数,非0元素直接移动到目标位置
    public void moveZeroes(int[] nums) {
        int count = 0;
        int len = nums.length;
        for(int i = 0; i < len; i++){
            if(nums[i] == 0){
                count++;
            }else{
                nums[i - count] = nums[i];
            }
        }
        for(int i = len - count; i < len; i++){     //将后面的位置变为0
            nums[i] = 0;
        }
    }
}

118.杨辉三角(简单)

给定一个非负整数 numRows 生成「杨辉三角」的前 numRows 行。

输入: numRows = 5
输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]
class Solution {
    public List<List<Integer>> generate(int numRows) {
        ArrayList<List<Integer>> ans = new ArrayList<List<Integer>>();
        for(int i = 0; i < numRows; i++){
            ArrayList<Integer> list = new ArrayList<>();
            for(int j = 0; j <= i; j++){
                if(j == 0 || j == i){
                    list.add(1);
                }else{
                    list.add(ans.get(i - 1).get(j - 1) + ans.get(i - 1).get(j));
                }
            }
            ans.add(list);
        }
        return ans;
    }
}

119.杨辉三角2(简单)★

给定一个非负索引 rowIndex,返回「杨辉三角」的第 rowIndex 行。

思路1:构造出整个杨辉三角形,最后取rowIndex行的数据,代码与上题类似

优化:由于只需要求一行数据,且当前行的数据只与上一行有关,故可以只保存两行内容

class Solution {
    //优化:由于当前行的计算只与上一行有关,故只需要保存上一行和当前行的数据即可(当前行计算完毕后赋给上一行)
    public List<Integer> getRow(int rowIndex) {
        ArrayList<Integer> pre = new ArrayList<>();
        for(int i = 0; i <= rowIndex; i++){
            ArrayList<Integer> con = new ArrayList<>();
            for(int j = 0; j <= i; j++){
                if(j == 0 || j == i){
                    con.add(1);
                }else{
                    con.add(pre.get(j-1) + pre.get(j));
                }
            }
            pre = con;
        }
        return pre;
    }
}

终极优化:只使用一个数组

图片.png

class Solution {
    //优化:从后往前计算,只使用一个数组
    public List<Integer> getRow(int rowIndex) {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(1);        //初始化第一个位置为1
        for(int i = 1; i <= rowIndex; i++){
            list.add(0);    //每遍历一行初始化一个位置的元素为0
            for(int j = i; j > 0; j--){     //从后往前计算
                list.set(j, list.get(j-1) + list.get(j));
            }
        }
        return list;
    }
}

661.图片平滑器(简单)★

图像平滑器 是大小为 3 x 3 的过滤器,用于对图像的每个单元格平滑处理,平滑处理后单元格的值为该单元格的平均灰度。

每个单元格的 平均灰度 定义为:该单元格自身及其周围的 8 个单元格的平均值,结果需向下取整。(即,需要计算蓝色平滑器中 9 个单元格的平均值)。

如果一个单元格周围存在单元格缺失的情况,则计算平均灰度时不考虑缺失的单元格(即,需要计算红色平滑器中 4 个单元格的平均值)。

图片.png

思路:常规方法即模拟,逐个单元计算,但需要不断进行累加操作,故使用二维数组前缀和。

二维数组前缀和:f[i][j] 是以 (i, j) 为右下角,(0, 0) 为左上角的区域和。

图片.png

当求 (x1, y1) 作为左上角,(x2, y2) 作为右下角 的区域和的时候,可以直接利用前缀和数组快速求解:

sum[x2][y2] - sum[x1 - 1][y2] - sum[x2][y1 - 1] + sum[x1 - 1][y1 - 1]

class Solution {
    public int[][] imageSmoother(int[][] img) {
        int m = img.length, n = img[0].length;
        int[][] sum = new int[m + 1][n + 1];
        int[][] result = new int[m][n];
        for(int i = 1; i <= m; i++){        //下标从1开始避免i-1数组越界
            for(int j = 1; j<= n; j++){
                sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i-1][j-1] + img[i-1][j-1];   //注意sum从1开始,img从0开始
            }
        }
        //计算以(c,d)为右下角,(a,b)为左上角的矩阵之和
        for(int i = 0; i < m; i++){
            for(int j = 0; j < n; j++){
                int a = Math.max(0, i-1), b = Math.max(0, j-1);     //防止越界
                int c = Math.min(m-1, i+1), d = Math.min(n-1, j+1);
                int count = (c - a + 1) * (d - b + 1);
                int tot = sum[c+1][d+1] - sum[a][d+1] - sum[c+1][b] + sum[a][b];
                result[i][j] = tot / count;
            }
        }
        return result;
    }
}

598.范围求和2(简单)

给你一个 m x n 的矩阵 M ,初始化时所有的 0 和一个操作数组 op ,其中 ops[i] = [ai, bi] 意味着当所有的 0 <= x < ai 和 0 <= y < bi 时, M[x][y] 应该加 1。 在执行完所有操作后,计算并返回 矩阵中最大整数的个数。

图片.png

class Solution {
    //每次操作都是以M[m-1,n-1]为右下角,M[0,0]为左上角的矩形
    //故只需要找出ops最小的行数和列数,相乘即可得到矩形的元素个数
    public int maxCount(int m, int n, int[][] ops) {
        for(int[] op : ops){
            m = Math.min(m, op[0]);
            n = Math.min(n, op[1]);
        }
        return m * n;
    }

189.轮转数组(中等)

给你一个数组,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。

class Solution {
    //思路1:每次轮转取出最后一个数,把前面的数依次往后移动一位,最后把第一位设为取出来的数
    //思路2:直接把最后k个数取出来,把前面的数往后移动k位,再把取出来的数放到前面
    //思路3:把数前n-k位逆序,后k位逆序,再把数组整体逆序
    public void rotate(int[] nums, int k) {
        int len = nums.length;
        if(k >= len){
            k = k % len;    //当移动的长度超过数组长度时,相当于len次操作后回到原样
        }
        reverse(nums, 0, len-k-1);
        reverse(nums, len-k, len-1);
        reverse(nums, 0, len-1);
    }

    //数组逆序
    public void reverse(int[] nums, int begin, int end){
        while(begin < end && begin >= 0 && end < nums.length){
            int temp = nums[begin];
            nums[begin] = nums[end];
            nums[end] = temp;
            begin++;
            end--;
        }
    }
}

396.旋转数组(中等)

给定一个长度为 n 的整数数组 nums 。假设 arrk 是数组 nums 顺时针旋转 k 个位置后的数组,我们定义 nums 的 旋转函数 F 为: F(k) = 0 * arrk[0] + 1 * arrk[1] + ... + (n - 1) * arrk[n - 1]

返回 F(0), F(1), ..., F(n-1)中的最大值 。

输入: nums = [4,3,2,6]
输出: 26
解释:
F(0) = (0 * 4) + (1 * 3) + (2 * 2) + (3 * 6) = 0 + 3 + 4 + 18 = 25
F(1) = (0 * 6) + (1 * 4) + (2 * 3) + (3 * 2) = 0 + 4 + 6 + 6 = 16
F(2) = (0 * 2) + (1 * 6) + (2 * 4) + (3 * 3) = 0 + 6 + 8 + 9 = 23
F(3) = (0 * 3) + (1 * 2) + (2 * 6) + (3 * 4) = 0 + 2 + 12 + 12 = 26
所以 F(0), F(1), F(2), F(3) 中的最大值是 F(3) = 26 。
class Solution {
    //找规律,F(n)和F(n-1)可以使用错位相消法
    //F(k)=F(k-1)+sum(nums)-n*nums[n-k]
    //注意这题使用模拟法会超时
    public int maxRotateFunction(int[] nums) {
        int n = nums.length;
        int numsSum = Arrays.stream(nums).sum();    //注意如何计算数组和
        int f = 0;
        //计算F(0)
        for(int i = 0; i < n; i++){
            f += nums[i] * i;
        }
        int res = f;
        //计算f(1) - f(n-1)
        for(int j = n - 1; j > 0; j--){
            f += numsSum - n * nums[j];
            res = Math.max(f, res);
        }
        return res;
    }
}

54.螺旋矩阵(中等)★

给你一个 mn 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。

图片.png

class Solution {
    //按内外层模拟,如实例1中,5为第二层,其他为第一层,每次遍历一层结束后再遍历内一层
    //遍历一层时,需要定义这一层的左上角和右下角作为转折点
    public List<Integer> spiralOrder(int[][] matrix) {
        ArrayList<Integer> res = new ArrayList<>();
        if(matrix.length == 0 || matrix[0].length == 0 || matrix == null) return res;
        int rows = matrix.length;
        int cols = matrix[0].length;
        int left = 0;
        int top = 0;
        int right = cols - 1;
        int bottom = rows - 1;
        while(left <= right && top <= bottom){
            for(int col = left; col <= right; col++){
                res.add(matrix[top][col]);
            }
            for(int row = top + 1; row <= bottom; row++){
                res.add(matrix[row][right]);
            }
            if(left < right && top < bottom){           //  防止重复遍历
                for(int col = right - 1; col >= left; col--){
                    res.add(matrix[bottom][col]);
                }
                for(int row = bottom - 1; row > top; row--){
                    res.add(matrix[row][left]);
                }
            }
            left++;
            right--;
            top++;
            bottom--;
        }
        return res;
    }
}

59.螺旋数组2(中等)

给你一个正整数 n ,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix

class Solution {
    //按内外层遍历矩阵,记录当前的左上角位置和右下角位置,做法与上题类似
    public int[][] generateMatrix(int n) {
        int num = 1;    //记录当前放在矩阵中的数
        int[][] res = new int[n][n];
        int left = 0;
        int right = n-1;
        int top = 0;
        int bottom = n-1;
        while(left <= right && top <= bottom){
            for(int i = left; i <= right; i++){
                res[top][i] = num;
                num++;
            }
            for(int i = top + 1; i <= bottom; i++){
                res[i][right] = num;
                num++;
            }
            if(left < right && top < bottom){
                for(int i = right - 1; i >= left; i--){
                    res[bottom][i] = num;
                    num++;
                }
                for(int i = bottom - 1; i > top; i--){
                    res[i][left] = num;
                    num++;
                }
            }
            left++;
            right--;
            top++;
            bottom--;
        }
    return res;
    }
}

498.对角线遍历(中等)

遍历顺序如图

图片.png

class Solution {
    //遍历路线的规律是,向右上角和向左下角遍历轮流进行
    //先考虑全部是从左下角到右上角的遍历,所有遍历的起点是从左上角到左下角再到右下角
    public int[] findDiagonalOrder(int[][] mat) {
        int flag = 0;       //标志此次遍历是否需要逆序
        int row = mat.length;
        int col = mat[0].length;
        ArrayList<Integer> res = new ArrayList<>();
        for(int i = 0; i < row; i++){
            res.addAll(leftDownIterator(mat, i, 0, flag));
            flag = flag == 0 ? 1 : 0;
        }
        for(int j = 1; j < col; j++){
            res.addAll(leftDownIterator(mat, row-1, j, flag));
            flag = flag == 0 ? 1 : 0;
        }
        return res.stream().mapToInt(Integer::valueOf).toArray();  //List<Integer>转int[]
    }

    public ArrayList<Integer> leftDownIterator(int[][] mat, int x, int y, int flag){
        int row = mat.length;
        int col = mat[0].length;
        ArrayList<Integer> list = new ArrayList<>();
        while(x >= 0 && y < col){
            list.add(mat[x][y]);
            x--;
            y++;
        }
        if(flag == 1){
            Collections.reverse(list);
        }
        return list;
    }
}
tip:
向list中追加list,使用addAll
将List<Integer>转int[]:list.stream().mapToInt(Integer::valueOf).toArray(); 
int[]转List<Integer>:Arrays.stream(data).boxed().collect(Collectors.toList()); 
String[]转List<String>:List<String> list = Arrays.asList(strings);       
List<String>转String[]:String[] strings = list.toArray(new String[0]); 

566.重塑矩阵(简单)★

在 MATLAB 中,有一个非常有用的函数 reshape ,它可以将一个 m x n 矩阵重塑为另一个大小不同(r x c)的新矩阵,但保留其原始数据。

给你一个由二维数组 mat 表示的 m x n 矩阵,以及两个正整数 r 和 c ,分别表示想要的重构的矩阵的行数和列数。重构后的矩阵需要将原始矩阵的所有元素以相同的行遍历顺序填充。如果具有给定参数的 reshape 操作是可行且合理的,则输出新的重塑矩阵;否则,输出原始矩阵。

//直接模拟,按行遍历矩阵并赋值到新矩阵中
class Solution {
    public int[][] matrixReshape(int[][] mat, int r, int c) {
        int rows = mat.length;
        int cols = mat[0].length;
        if(rows * cols != r * c) return mat;
        int[][] res = new int[r][c];
        int colscount = 0;
        int rowscount = 0;
        for(int i = 0; i < rows; i++){
            for(int j = 0; j < cols; j++){
                if(colscount >= c){
                    colscount = 0;
                    rowscount++;
                }
                res[rowscount][colscount] = mat[i][j];
                colscount++;
            }
        }
        return res;
    }
}
//逐个元素遍历,记录第几个元素,二维映射到一维,直接赋值
class Solution {
    public int[][] matrixReshape(int[][] mat, int r, int c) {
        int rows = mat.length;
        int cols = mat[0].length;
        if(rows * cols != r * c){
            return mat;
        }
        int[][] res = new int[r][c];
        for(int i = 0; i < rows * cols; i++){       //i记录当前是第几个元素,且是从0开始的
            res[i/c][i%c] = mat[i/cols][i%cols];
        }
        return res;
    }
}

48.旋转图像(中等)★

给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。 你必须在原地旋转图像.

图片.png

class Solution {
    public void rotate(int[][] matrix) {
        int n = matrix.length;
        //先将行列互换,也就是轴对称的元素互换,注意换遍历一半元素即可
        for(int i = 0; i < n; i++){             
            for(int j = i + 1; j < n; j++){
                int temp = matrix[i][j];
                matrix[i][j] = matrix[j][i];
                matrix[j][i] = temp;
            }
        }
        //将列对称互换,即0列与n-1列互换
        for(int i = 0; i < n; i++){     //先固定行
            for(int j = 0; j < n / 2; j++){
                int temp = matrix[i][j];
                matrix[i][j] = matrix[i][n-j-1];
                matrix[i][n-j-1] = temp;
            }
        }        
    }
}

73.矩阵置0(中等)★

给定一个m x n的矩阵,如果一个元素为0,则将其所在行和列的所有元素都设为0。请使用原地算法

图片.png

class Solution {
    //使用矩阵第一行和第一列来记录对应的行和列是否存在0,
    //若存在将其修改为0,若不存在则不用修改
    //使用两个额外的变量记录第一行和第一列是否存在0
    public void setZeroes(int[][] matrix) {
        boolean firstRow = false;   
        boolean firstCol = false;
        int rows = matrix.length;
        int cols = matrix[0].length;
        for(int i = 0; i < cols; i++){
            if(matrix[0][i] == 0){
                firstRow = true;
                break;
            }
        }
        for(int i = 0; i < rows; i++){
            if(matrix[i][0] == 0){
                firstCol = true;
                break;
            }
        }
        for(int i = 1; i < rows; i++){
            for(int j = 1; j < cols; j++){
                if(matrix[i][j] == 0){
                    matrix[0][j] = 0;
                    matrix[i][0] = 0;
                }
            }
        }
        for(int i = 1; i < rows; i++){
            for(int j = 1; j < cols; j++){
                if(matrix[i][0] == 0 || matrix[0][j] == 0){
                    matrix[i][j] = 0;
                }
            }
        }
        if(firstRow){
            for(int i = 0; i < cols; i++){
                matrix[0][i] = 0;
            }
        }
        if(firstCol){
            for(int i = 0; i < rows; i++){
                matrix[i][0] = 0;
            }
        }
    }
}
//实际上可以只使用一个变量来记录第一列是否有0
//第一列的第一个位置即可记录第一行是否存在0
//但此时需要从下往上更新,防止每一列的第一个元素被提前修改

289.生命游戏(中等)★

给定一个包含 m × n 个格子的面板,每一个格子都可以看成是一个细胞。每个细胞都具有一个初始状态: 1 即为 活细胞 (live),或 0 即为 死细胞 (dead)。每个细胞与其八个相邻位置(水平,垂直,对角线)的细胞都遵循以下四条生存定律:

如果活细胞周围八个位置的活细胞数少于两个,则该位置活细胞死亡;
如果活细胞周围八个位置有两个或三个活细胞,则该位置活细胞仍然存活;
如果活细胞周围八个位置有超过三个活细胞,则该位置活细胞死亡;
如果死细胞周围正好有三个活细胞,则该位置死细胞复活;

下一个状态是通过将上述规则同时应用于当前状态下的每个细胞所形成的,其中细胞的出生和死亡是同时发生的。

给你 m x n 网格面板 board 的当前状态,返回下一个状态。使用原地操作。

图片.png

class Solution {
    //关键是记录当前位置九宫格内的活细胞数,同时还要保存它原本的状态
    //若附近有n个活细胞,原本的状态为x,则记录为x+10*n
    //记录全部位置的状态后,逐个更新
    //优化:反过来想,只有活的细胞能影响其他的细胞,故遍历到活细胞时,更新它影响的细胞即可,避免遍历很多死细胞
    public void gameOfLife(int[][] board) {
        int rows = board.length;
        int cols = board[0].length;
        //记录状态
        for(int i = 0; i < rows; i++){
            for(int j = 0; j < cols; j++){
                if(board[i][j] % 10 == 1){          //只对活细胞更新
                    update(board, i, j);
                }       
            }
        }
        //更新
        for(int i = 0; i < rows; i++){
            for(int j = 0; j < cols; j++){
                int num = board[i][j] / 10;     //周围的活细胞数量
                if(board[i][j] % 10 == 1){        //原本是活细胞
                    if(num < 2 || num > 3){
                        board[i][j] = 0;
                    }else{
                        board[i][j] = 1;
                    }
                }else{
                    if(num != 3){
                        board[i][j] = 0;
                    }else{
                        board[i][j] = 1;
                    }
                }
            }
        }
    }

    //更新当前位置对周围位置状态信息的影响
    public void update(int[][] board, int x, int y){
        int rows = board.length;
        int cols = board[0].length;
        for(int i = x-1; i <= x+1; i++){
            for(int j = y-1; j <= y+1; j++){
                if(i >= 0 && j >= 0 && i < rows && j < cols && !(i == x && j == y)){    //避免出界或对自己更新
                    board[i][j] += 10;
                }
            }
        }
    }
}

303.区域和检索-数组不可变(简单)★

给定一个整数数组nums,计算索引left和right(包含left和right)之间的nums元素的和 ,其中 left <= right。

实现 NumArray 类:

NumArray(int[] nums) 使用数组 nums 初始化对象
int sumRange(int i, int j) 返回数组nums中索引leftright之间的元素的总和
class NumArray {
    //使用前缀和计算
    int[] sum;
    public NumArray(int[] nums) {
        int n = nums.length;
        sum = new int[n];
        sum[0] = nums[0];
        for(int i = 0; i < n-1; i++){       //当前位置n记录数组nums[n] + nums[n-1] + ... + nums[0]
            sum[i+1] = sum[i] + nums[i+1];
        }
    }
    
    public int sumRange(int left, int right) {
        if(left == 0){
            return sum[right];
        }else{
            return sum[right] - sum[left-1];
        } 
    }
}

304.二维区域和检索-矩阵不可变(中等)★

给定一个二维矩阵 matrix,计算其子矩形范围内元素的总和,该子矩阵的 左上角 为 (row1, col1) ,右下角 为 (row2, col2) 。

class NumMatrix {
    //二维数组前缀和,每行都为一维数组前缀和,每列也看成一维数组前缀和
    //即f(i,j)为以matrix[i][j]为右下角的矩阵的和
    //为了避免对数组越界的判断,使用f(i+1,j+1)表示以matrix[i][j]为右下角的矩阵和,这样做会使程序简化许多
    int[][] sum;
    public NumMatrix(int[][] matrix) {
        int rows = matrix.length;
        if(rows > 0){
            int cols = matrix[0].length;
            sum = new int[rows+1][cols+1];
            for(int i = 0; i < rows; i++){
                for(int j = 0; j < cols; j++){
                    sum[i+1][j+1] = sum[i][j+1] + sum[i+1][j] - sum[i][j] + matrix[i][j];
                }
            }  
        }
    }
    
    public int sumRegion(int row1, int col1, int row2, int col2) {
        return sum[row2+1][col2+1] - sum[row1][col2+1] - sum[row2+1][col1] + sum[row1][col1];
    }
}

238.除自己以外数组的乘积(中等)★

你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。

在 O(n) 时间复杂度内完成此,请不要使用除法,除法在遇到0时会出问题。

class Solution {
    //声明2个数组分别存储前缀乘积和后缀乘积,注意不包括自己
    public int[] productExceptSelf(int[] nums) {
        int n = nums.length;
        int[] answer = new int[n];
        int[] pre = new int[n+1];
        int[] last = new int[n+1];
        pre[0] = 1;         //第一个元素没有前缀
        last[n-1] = 1;
        for(int i = 1; i < n; i++){
            pre[i] = pre[i-1] * nums[i-1];  //pre[i]为nums[i]的前缀积
        }
        for(int j = n - 2; j >= 0; j--){
            last[j] = last[j+1] * nums[j+1];
        }
        for(int i = 0; i < n; i++){
            answer[i] = pre[i] * last[i];
        }
        return answer;
    }
}
class Solution {
    //可以只使用一个answer数组和一个R变量完成题目
    public int[] productExceptSelf(int[] nums) {
        int n = nums.length;
        int[] answer = new int[n];
        answer[0] = 1;         //第一个元素没有前缀
        for(int i = 1; i < n; i++){
            answer[i] = answer[i-1] * nums[i-1];    //使用answer充当pre数组
        }
        int R = 1;      //用R表示当前元素后缀积
        for(int j = n - 1; j >= 0; j--){
            answer[j] = answer[j] * R;      //将answer[j]更新为前缀积*后缀积
            R = R * nums[j];                //更新answer[j-1]的后缀积
        }
        return answer;
    }
}