LeetCode笔记|数组

刷题顺序参考 有没有人一起从零开始刷力扣 - 力扣(LeetCode)

题目分类 题目编号
数组的遍历 485、495、414、628
统计数组中的元素 645、697、448、442、41、274
数组的改变、移动 453、665、283
二维数组及滚动数组 118、119、661、598、419
数组的旋转 189、396
特定顺序遍历二维数组 54、59、498
二维数组变换 566、48、73、289
前缀和数组 303、304、238

文章目录

  • 小技巧&注意点
  • 数组的遍历
    • 【485】最大连续1的个数
    • 【495】提莫攻击
    • 【414】第三大的数
    • 【628】三个数组成的最大乘积
  • 统计数组中的元素
    • 【645】错误的集合
    • 【697】数组的度
    • 【448】找到所有数组中消失的数字
    • 【442】数组中的重复数组
    • 【41】缺失的第一个正数
      • 思路一
      • 思路二
    • 【274】H指数
  • 数组的改变、移动
    • 【453】最小操作次数使数组元素相等
    • 【665】非递减数列
    • 【283】移动零
  • 二维数组及滚动数组
    • 【118】杨辉三角
    • 【119】杨辉三角II
    • 【661】图片平滑器
    • 【598】范围求和II
    • 【419】甲板上的战舰
  • 数组的旋转
    • 【189】轮转数组
      • 思路一
      • 思路二
      • 思路三
    • 【396】旋转函数
  • 特定顺序遍历二维数组
    • 【54】螺旋矩阵
    • 【59】螺旋矩阵II
    • 【498】对角线遍历
  • 二维数组变换
    • 【566】重塑矩阵
    • 【48】旋转图像
    • 【73】矩阵置零
    • 【289】生命游戏
    • 【303】区域和检索 - 数组不可变
      • 思路一
      • 思路二
    • 【304】二维区域和检索 - 矩阵不可变
    • 【238】除自身以外数组的乘积
      • 思路一
      • 思路二

小技巧&注意点

  1. 涉及数值范围时不要忘记使用静态量,如Long.MIN_VALUELong.MAX_VALUE——【414】

数组的遍历

【485】最大连续1的个数

Link:485. 最大连续 1 的个数 - 力扣(LeetCode) (leetcode-cn.com)

还比较简单,遍历一次就好了,遇到1开始计数,遇到0计数清零,每遍历一次判断一下最大计数次数。

class Solution {
    public int findMaxConsecutiveOnes(int[] nums) {
        int max=0,count=0;
        for (int i=0;i<nums.length;i++) {
            if (nums[i]== 0){
                count =0;
            }else{
                count++;
            }
            max=count>max?count:max;
        }
        return max;
    }
}
//执行耗时:2 ms,击败了62.55% 的Java用户
//内存消耗:39.8 MB,击败了40.11% 的Java用户

【495】提莫攻击

Link:495. 提莫攻击 - 力扣(LeetCode) (leetcode-cn.com)

模拟一下中毒就好,不过是从状态的角度去而不是模拟整个时间轴,刚开始列出整个时间轴来计算结果超时。。。

根据攻击的时间对中毒时间累加,要注意攻击太快状态是覆盖的,也就是说上次攻击的持续时间打了折扣。

同时,在最后一次攻击结束后,要记的再加上中毒时间

class Solution {
    public int findPoisonedDuration(int[] timeSeries, int duration) {
        int sum = 0;
        for (int i=1;i<timeSeries.length; i++) {
            if(timeSeries[i]-timeSeries[i-1]<duration){
                sum+=timeSeries[i]-timeSeries[i-1];
            }else{
                sum+=duration;
            }
        }
        sum+=duration;
        return sum;
    }
}
//执行耗时:2 ms,击败了92.87% 的Java用户
//内存消耗:40 MB,击败了80.11% 的Java用户

【414】第三大的数

Link:414. 第三大的数 - 力扣(LeetCode) (leetcode-cn.com)

刚开始就是冲着o(n)去的,思路也很简单,遍历数组记下最大的三个就可以,但也遇到不少问题

  1. 重复数字要避免重新计数:

    • 添加计数器,遍历默认+1,判断到有重复则-1;
  2. 数据大小范围:

    • 由于我是使用数组进行存储最大的数,其初始化值为0,提交后才发现用例中可能会出现负数;

    • 后来参考了一些解决方法后得知可以使用基本数据类型的最小值,如Long.MIN_VALUE,再尝试使用Integer.MIN_VALUE发现当用例包含Integer.MIN_VALUE时会不对其计数,这和数组初始化值有关,所以还是老老实实用了Long.MIN_VALUE

class Solution {
    public int thirdMax(int[] nums) {
        long[] res = new long[] {
            Long.MIN_VALUE, Long.MIN_VALUE, Long.MIN_VALUE
        } ;
        int count=0;
        for (int i = 0; i < nums.length; i++) {
            count++;
            if (nums[i] == res[0] || nums[i] == res[1] || nums[i] == res[2]) {
                count--;
                continue;
            }
            if (nums[i] > res[0]) {
                res[2] = res[1];
                res[1] = res[0];
                res[0] = nums[i];
            } else if (nums[i] > res[1]) {
                res[2] = res[1];
                res[1] = nums[i];
            } else if (nums[i] > res[2]) {
                res[2] = nums[i];
            }
        }
        return count>2?(int)res[2]:(int)res[0];
    }
}

//执行耗时:1 ms,击败了91.83% 的Java用户
//内存消耗:38.5 MB,击败了10.32% 的Java用户

收获:涉及数值范围时不要忘记使用静态量,如Long.MIN_VALUELong.MAX_VALUE

【628】三个数组成的最大乘积

Link:628. 三个数的最大乘积 - 力扣(LeetCode) (leetcode-cn.com)

最大无非就是大大相乘,排个序就可以得到最大的三个数了。

需要注意的就是负负得正,所以还要考虑两个最小负数和最大正数的乘积。

class Solution {
    public int maximumProduct(int[] nums) {
        int len = nums.length;
        if (len < 4) {
            return nums[0] * nums[1] * nums[2];
        }
        Arrays.sort(nums);
        int m1 = nums[0] * nums[1] * nums[len - 1];
        int m2 = nums[len - 1] * nums[len - 2] * nums[len - 3];
        return Math.max(m1,m2);
    }
}
//执行耗时:11 ms,击败了64.19% 的Java用户
//内存消耗:40 MB,击败了28.01% 的Java用户

使用Arrays.sort()是一个简便的方法,但其耗时与内存消耗都比较大,比较快的还是如【414】进行遍历取出最值,此处所需的值有最大的三个正值与最小的两个负值。

不过还是遇到相同的问题,初始化五个值时还是习惯性的初始化为0,当数全为负数时其最大值就会取到0,考虑最值问题时还是要牢记用基本数据类型的范围呐!!

class Solution {
    public int maximumProduct(int[] nums) {
        int max1=Integer.MIN_VALUE, max2=Integer.MIN_VALUE, max3=Integer.MIN_VALUE, min1=Integer.MAX_VALUE, min2=Integer.MAX_VALUE;
        for (int num: nums) {
            if (num>max1){
                max3=max2; max2=max1; max1=num;
            }else if(num>max2){
                max3=max2; max2=num;
            }else if(num>max3){
                max3=num;
            }
            if(num<min1){
                min2=min1;min1=num;
            }else if (num<min2){
                min2=num;
            }
        }
        return Math.max(max1*max2*max3,max1*min1*min2);
    }
}
//执行耗时:2 ms,击败了99.57% 的Java用户
//内存消耗:40 MB,击败了20.03% 的Java用户

统计数组中的元素

【645】错误的集合

Link: 645. 错误的集合 - 力扣(LeetCode) (leetcode-cn.com)

刚开始担心它的集合是不是连续的,就先试一试结果通过了。。

由于整数大小有限制,直接按照这个限制建个数组进行统计就好了。要注意最后判重以及判漏只要判断给定集合的长度就够了。

class Solution {
    public int[] findErrorNums(int[] nums) {
        int len = nums.length;
        int[] m =new int[10001];
        int[] res = new int[2];
        for (int i = 0; i < len; i++) {
            m[nums[i]]++;
        }
        for (int i = 1; i < len+1; i++) {
            if(m[i] ==2 )
                res[0] = i;
            if(m[i]==0)
                res[1] = i;
        }
        return res;
    }
}
//执行耗时:2 ms,击败了84.33% 的Java用户
//内存消耗:39.6 MB,击败了95.07% 的Java用户

【697】数组的度

Link:697. 数组的度 - 力扣(LeetCode) (leetcode-cn.com)

题目意思相当于找出重复次数最多的数字间的最短距离。

遍历一次,使用Map对出现的数字进行计数,并记下第一次出现的位置。每次遍历对计数更新,并与当前的度比较,若比度大,度要更新,最短距离也更新;若与度相同,取较短距离(距离为下标之差+1)

class Solution {
    public int findShortestSubArray(int[] nums) {
        Map<Integer, int[]> count = new HashMap<Integer, int[]>();
        int d = 0;
        int minSubd = 0;
        for (int i = 0; i < nums.length; i++) {
            if (count.containsKey(nums[i])) {
                count.get(nums[i])[0] += 1;
            } else {
                count.put(nums[i], new int[]{1, i});
            }
            if (count.get(nums[i])[0] > d) {
                d = count.get(nums[i])[0];  //更新度
                minSubd = i - count.get(nums[i])[1] + 1; //更新最小子数组
            } else if (count.get(nums[i])[0] == d) {
                minSubd = Math.min(minSubd, i - count.get(nums[i])[1] + 1);
            }

        }

        return minSubd;
    }
}
//执行耗时:19 ms,击败了40.43% 的Java用户
//内存消耗:45.1 MB,击败了5.06% 的Java用户

【448】找到所有数组中消失的数字

Link:448. 找到所有数组中消失的数字 - 力扣(LeetCode) (leetcode-cn.com)

两次遍历,一次判断哪些数字有,第二次取出没有的数字构成列表。不过空间消耗有点大了

  • 要注意下标
class Solution {
    public List<Integer> findDisappearedNumbers(int[] nums) {
        int len = nums.length;
        int[] count = new int[len];
        for (int num : nums) {
            count[num - 1] = 1;
        }
        List<Integer> result = new ArrayList<Integer>();
        for (int i = 0; i < len; i++) {
            if (count[i]==0){
                result.add(i+1);
            }
        }
        return result;
    }
}
//执行耗时:3 ms,击败了100.00% 的Java用户
//内存消耗:47.4 MB,击败了38.39% 的Java用户

【442】数组中的重复数组

Link:442. 数组中重复的数据 - 力扣(LeetCode) (leetcode-cn.com)

与【448】思路一致,同样空间消耗比较大

  • 同样需要注意下标
class Solution {
    public List<Integer> findDuplicates(int[] nums) {
        int len = nums.length;
        int[] count = new int[len];
        for (int num : nums) {
            count[num - 1]++;
        }
        List<Integer> result = new ArrayList<Integer>();
        for (int i = 0; i < len; i++) {
            if (count[i] == 2) {
                result.add(i + 1);
            }
        }
        return result;
    }
}

【41】缺失的第一个正数

Link:41. 缺失的第一个正数 - 力扣(LeetCode) (leetcode-cn.com)

思路一

使用Arrays.sort排序后,依次遍历,当遍历数大于0时,采用一个不断累加的数x进行比较(当两数相同,x加一)

  • 测试用例中存在重复的数,所以与x比较时,先判断数num是否比x大(小于x的数已经比较过确定数组中存在,大于则说明没出现过,直接返回x),再根据是否与x相等来决定x是+1还是不变。
  • 测试结果相比较下较差
class Solution {
    public int firstMissingPositive(int[] nums) {
        Arrays.sort(nums);
        int i = 1;
        for (int num : nums) {
            if (num > 0) {
                if (num > i) {
                    return i;
                }else{
                    i += num==i?1:0;
                }
            }
        }
        return i;
    }
}
//执行耗时:4 ms,击败了21.19% 的Java用户
//内存消耗:95 MB,击败了11.36% 的Java用户

思路二

对于给出的任一数组,其数值范围必定不会超出数组长度+1(考虑极限情况,数组排序恰好满足递增正数数组,返回结果也是长度+1)。所以新建长度相同的空数组,在原数组基础上遍历,对符合条件(正数,在长度范围内)的数,在空数组上对应index处计数。遍历结束后新数组中第一个与index不符的数就是结果。

  • 也可以在原数组上进行位置调换
class Solution {
    public int firstMissingPositive(int[] nums) {
        int len = nums.length;
        int[] count = new int[len];
        for (int i = 0; i < len; i++) {
            if (nums[i] > 0 && nums[i] < len + 1) {
                count[nums[i]-1] = 1;
            }
        }
        for (int i = 0; i < len; i++) {
            if(count[i]!=1)
                return i+1;
        }
        return len+1;
    }
}
//执行耗时:2 ms,击败了95.07% 的Java用户
//内存消耗:92.9 MB,击败了91.92% 的Java用户

【274】H指数

Link:274. H 指数 - 力扣(LeetCode) (leetcode-cn.com)

有点难解释,主要还是对题目理解吧。

采用Arrays.sort()进行排序,从小到大进行遍历测试,因为H指数不会超过数组长度。对于遍历的i只要判断倒数第i篇是否满足引用大于等于i次就可以。

class Solution {
    public int hIndex(int[] citations) {
        int n = citations.length;
        Arrays.sort(citations);
        int ressult =0;
        for (int i = 0; i < n; i++) {
            if(citations[n-i-1]>=i+1){
                ressult = ressult>i+1?ressult:i+1;
            }
        }
        return ressult;
    }
}
//执行耗时:1 ms,击败了76.77% 的Java用户
//内存消耗:36.5 MB,击败了10.66% 的Java用户

数组的改变、移动

【453】最小操作次数使数组元素相等

453. 最小操作次数使数组元素相等 - 力扣(LeetCode) (leetcode-cn.com)

Emmmmm…这是一道数学题。。。难为人啊(抱头痛哭)。。。想半天想不出来,只觉得最大和最小的差值要用上。。。最后看了分析囧么肥事-短话长说-图解数组篇-【435】最小移动次数使数组元素相等

就是计算每个数与最小值的差的和

class Solution {
    public int minMoves(int[] nums) {
        int len = nums.length;
        Arrays.sort(nums);
        int result= 0;
        for (int i = len-1; i >0 ; i--) {
            result += nums[i] - nums[0];
        }
        return result;
    }
}
//执行耗时:13 ms,击败了21.18% 的Java用户
//内存消耗:38.8 MB,击败了53.12% 的Java用户

看了一些大佬的分析居然还用上了动态规划。。www。。。。。

对数学不好的娃来说这种简单题就是噩梦

【665】非递减数列

Link:665. 非递减数列 - 力扣(LeetCode) (leetcode-cn.com)

原本是希望从相邻元素的差值之中找规律,但想了很久也没有整理出来TAT

参考的是评论区的码不停蹄大佬的解题思路,在自己解读中我也将其模拟为登山中挖坑填坑的一个过程(似乎理解起来就形象了?)

解题的一大难点在于修改数列时是修改前面较大的数(挖坑)还是修改后面较小的数(填坑),而判断的过程在我看来是使用一个长度为3的移动窗口进行判定。

class Solution {
    public boolean checkPossibility(int[] nums) {
        if (nums.length < 3)
            return true;
        int count = 0;
        for (int i = 1; i < nums.length && count<2; i++) {
            if (nums[i-1] <= nums[i]) {
                continue;
            }
            // 此时出现一个坑i,前面的已保证为非递减
            count++;
            if(i-2>=0 && nums[i-2]>nums[i]){
                // 把坑i填到和i-1一样平就可以
                nums[i] = nums[i-1];
            }else{
                //1. 坑在第二个,把第一个挖低即可
                //2. 把坑i-1挖到和i一样高度就可以,此时i-1还能保证比i-2高
                nums[i-1] = nums[i];
            }
        }
        return count<2;
    }
}
//执行耗时:1 ms,击败了94.10% 的Java用户
//内存消耗:39.7 MB,击败了51.70% 的Java用户

被这两道题卡的死死的了,现在不管啥题都小心翼翼,生怕哪里没考虑到,提交都心惊胆战的

【283】移动零

Link:283. 移动零 - 力扣(LeetCode) (leetcode-cn.com)

简单题,刚开始想的是建新数组,遍历一遍原数组统计0个数,把非零填入数组,最后用0补上,然后看到了“必须在原数组上操作”

按照提示使用了双指针,自己写个例子推一下很容易就推出

class Solution {
    public void moveZeroes(int[] nums) {
        for (int i = 0; i < nums.length; i++) {
            for (int j = i+1; j < nums.length; j++) {
                if(nums[i]==0 && nums[j]!=0){
                    nums[i] =nums[j];
                    nums[j] =0;
                    break;
                }
            }
        }
    }
}
//执行耗时:53 ms,击败了5.01% 的Java用户
//内存消耗:39.7 MB,击败了22.91% 的Java用户
  1. 难得有一道一次过,虽然结果比起大佬们差太多 >…<
  2. 参考了下最快的方法,发现自己还是傻,把自己原来的两个思路结合一下就是了。。
  3. 除此之外,还看到了很多巧妙的写法,双指针的运用也很多,我用的应该是最捞的了
class Solution {
    public void moveZeroes(int[] nums) {
        int zero =0;
        for (int i = 0; i < nums.length; i++) {
            if(nums[i] ==0){
                zero++;
            }else{
                nums[i-zero] = nums[i];
            }
        }
        for (int i = 0; i < zero; i++) {
            nums[nums.length-1-i]=0;
        }
    }
}
//执行耗时:1 ms,击败了57.44% 的Java用户
//内存消耗:39.8 MB,击败了5.17% 的Java用户

二维数组及滚动数组

【118】杨辉三角

Link:118. 杨辉三角 - 力扣(LeetCode) (leetcode-cn.com)

杨辉三角,老经典了,其构造方法很简单

只要注意一下对最边缘的1特殊处理一下就好了,中间区域的处理都是一样的,不过有点费内存

class Solution {
    public List<List<Integer>> generate(int numRows) {
        List<List<Integer>> res = new ArrayList<>();
        int i = 0;
        while (i < numRows) {
            List<Integer> row = new ArrayList<>();
            for (int j = 0; j <= i; j++) {
                if (j == 0 || j == i) {
                    row.add(1);
                } else {
                    row.add(res.get(i-1).get(j - 1) + res.get(i-1).get(j));
                }
            }
            res.add(row);
            i++;
        }
        return res;
    }
}
// 执行耗时:0 ms,击败了100.00% 的Java用户
// 内存消耗:36.3 MB,击败了41.40% 的Java用户

还是要骂一下自己大伞兵,由于第一行只有一个元素,所以我一开始单独拎出来写了,但参考了官方解答之后,意识到了就算是一个元素,但是和其他行没有区别,同样是有头和尾的数列,改完之后内存消耗瞬间上了一档。

【119】杨辉三角II

Link:119. 杨辉三角 II - 力扣(LeetCode) (leetcode-cn.com)

一种无脑的方式就是用上题的结果,不过这样这题的意义就️了

要获取某一行,就是在一串数组上不断的对相邻数字相加,只要注意保存好被替换掉的数字就可以

这题还有个坑就是他的rowIndex是从0开始计数,而上一道是从1开始计数

class Solution {
    public List<Integer> getRow(int rowIndex) {
        List<Integer> res = new ArrayList<>();
        res.add(1);
        for (int i = 1; i <= rowIndex; i++) {
            int tmp = 1;
            for (int j = 1; j <= i; j++) {
                if(j==i){
                    res.add(1);
                }else{
                    int c=res.get(j);
                    res.set(j,tmp+res.get(j));
                    tmp=c;
                }
            }
        }
        return res;
    }
}
// 执行耗时:1 ms,击败了78.49% 的Java用户
// 内存消耗:36.1 MB,击败了58.29% 的Java用户

冲冲冲!(这几道都是一遍过,希望别太膨胀了)

【661】图片平滑器

Link:661. 图片平滑器 - 力扣(LeetCode) (leetcode-cn.com)

题目理解起来很容易,但如何更好地实现是个难题,无奈之下只能暴力解决

总共9个数,判断出来哪个位置能有数字加上去就好了

class Solution {
    public int[][] imageSmoother(int[][] img) {
        int m=img.length;
        int n=img[0].length;
        int[][] res = new int[m][n];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                int sum=img[i][j];// 中
                int count=1;
                if(i!=0){
                    sum+=img[i-1][j];//上
                    count++;
                }
                if(i!=m-1){
                    sum+=img[i+1][j];//下
                    count++;
                }
                if(j!=0){
                    sum+=img[i][j-1];//左
                    count++;
                }
                if(j!=n-1){
                    sum+=img[i][j+1];//右
                    count++;
                }
                if(i!=0 && j!=0){
                    sum+=img[i-1][j-1];//左上
                    count++;
                }
                if(i!=0 && j!=n-1){
                    sum+=img[i-1][j+1];//右上
                    count++;
                }
                if(i!=m-1 && j!=0){
                    sum+=img[i+1][j-1];//左下
                    count++;
                }
                if(i!=m-1 && j!=n-1){
                    sum+=img[i+1][j+1];//右下
                    count++;
                }
                res[i][j] = (int)sum/count;
            }
        }
        return res;
    }
}
// 执行耗时:5 ms,击败了93.28% 的Java用户
// 内存消耗:39.3 MB,击败了63.44% 的Java用户

官方解答也是用了暴力破解,但明显比我的一堆if高大上多了

class Solution {
    public int[][] imageSmoother(int[][] M) {
        int R = M.length, C = M[0].length;
        int[][] ans = new int[R][C];

        for (int r = 0; r < R; ++r)
            for (int c = 0; c < C; ++c) {
                int count = 0;
                for (int nr = r-1; nr <= r+1; ++nr)
                    for (int nc = c-1; nc <= c+1; ++nc) {
                        if (0 <= nr && nr < R && 0 <= nc && nc < C) {
                            ans[r][c] += M[nr][nc];
                            count++;
                        }
                    }
                ans[r][c] /= count;
            }
        return ans;
    }
}
//执行耗时:9 ms,击败了41.18% 的Java用户
//内存消耗:39.6 MB,击败了10.92% 的Java用户

看了评论区的大佬们似乎都被这道题搞心态了hhhh

有位大佬说的很对:“看了官方代码显得我是个zz” TAT

【598】范围求和II

Link:598. 范围求和 II - 力扣(LeetCode) (leetcode-cn.com)

第一想法是按照输入的操作不断对数组++,加完判断一下是否为最大,进行计数或修改最大值并重置计数,然而提交时候极限40000*40000数组就超内存了。。。还有个坑就是输入的操作可能为空

仔细思考之后,只要进行了操作都必定从[0][0]开始,所以[0][0]必然是最大的,然后就是找出和[0][0]进行了相同操作的元素个数。应该只要找到最小的a和b就好了吧。

class Solution {
    public int maxCount(int m, int n, int[][] ops) {
        int minA = Integer.MAX_VALUE, minB = Integer.MAX_VALUE;
        if (ops.length == 0) return m * n;
        for (int[] op : ops) {
            if (op[0] < minA)
                minA = op[0];
            if (op[1] < minB)
                minB = op[1];
        }
        return minA * minB;
    }
}
// 执行耗时:0 ms,击败了100.00% 的Java用户
// 内存消耗:38.5 MB,击败了49.06% 的Java用户

对不起!我反思!虽然后面思路自己想的,但还是瞄到了评论区,初始值最大化也是受评论区的指点,不然我还是忘,www…

【419】甲板上的战舰

Link:419. 甲板上的战舰 - 力扣(LeetCode) (leetcode-cn.com)

思考一下,只要计数战舰头就可以了,只要左边和上边有’X’的都不是战舰头,比较麻烦的就是甲板左侧和甲板顶部需要单独考虑一下。不过代码写的比较繁杂。

class Solution {
    public int countBattleships(char[][] board) {
        int count = 0, row = board.length, length = board[0].length;
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < length; j++) {
                if (board[i][j] == 'X') {
                    if(i==0){
                        if(j==0) count++;
                        if(j!=0){
                            if(board[i][j-1] !='X') count++;
                        }
                    }else{
                        if(j==0){
                            if(board[i-1][j]!='X') count++;
                        }else{
                            if(board[i-1][j]!='X' && board[i][j-1]!='X') count++;
                        }
                    }
                }
            }
        }
        return count;
    }
}
//执行耗时:0 ms,击败了100.00% 的Java用户
//内存消耗:38.1 MB,击败了36.01% 的Java用户

去题解学习了一下,把一堆if给精简了,离散数学还是不太行啊TAT

if((i==0||board[i-1][j]!='X')&& (j==0||board[i][j-1]!='X')) count++;

数组的旋转

【189】轮转数组

Link:189. 轮转数组 - 力扣(LeetCode) (leetcode-cn.com)

需要注意的是轮转次数可能会大于数组长度,所以需要注意对轮转次数取余

思路一

使用新数组进行替换,但未能实现,因为其方法类型为void,普通的赋值 = 无法实现nums的赋值

参考官方题解一后,学到了用System.arrayCopy进行赋值,或使用Array.copeOf复制原数组

class Solution {
    public void rotate(int[] nums, int k) {
        int length = nums.length;
        int[] res = new int[length];
        k %=length;
        for (int i = 0; i < length; i++) {
            res[(i+k)%length] = nums[i];
        }
        System.arraycopy(res,0,nums,0,length);
    }
}
// 执行耗时:1 ms,击败了62.88% 的Java用户
// 内存消耗:55.3 MB,击败了53.86% 的Java用户

思路二

直接将nums各个元素换到移了k个位后的位置,这样只需要k次就可以完成,但实际测试后发现存在重复循环的过程,部分元素也可能没有遍历到,靠自己也没有实现

官方题解二的环状替换就是和我的思路一致,不过我想不出后续的处理TAT

后来花时间自己去想了不按照题解的思路去分析:循环的个数由lengthklength-k的公因数,如示例中当k=2 or 4时,循环次数为2;同时为了缩减代码,将循环初始位作为备份单元,如示例中的nums[0] and nums[1] 作为缓存。但提交之后在一个lengthk达上万的测试案例中崩了。最后分析了很久,最后意识到,由于思考的参数比较简单,都是采用length-k6-2, 6-4, 9-3, 9-6等作为分析,所以只分析到简单例子的层面,如10-6, 16-10, 25-15等复杂点组合没有想到,所以没有分析到最大公因数。

同时,最大公因数如何获取对我来说也是一个痛点。

class Solution {
    public void rotate(int[] nums, int k) {
        int length = nums.length, x = 0;
        k %= length;
        if (k == 0) return;
      	// 未考虑到最大公因数
      	/*if((length%k==0 ||length%(length-k)==0) && k!=1 && k!=length-1){
          	x=k;
      	}else{
       	   	x=1;
      	}*/
        x = gcd(k,length);
        int i = 0;
        while (i < x) { // 循环的次数
            int index = i;
            for (int j = 0; j < length / x; j++) { //每次循环参与的元素个数
                int tmp = nums[(index + k) % length];
                nums[(index + k) % length] = nums[i];
                nums[i] = tmp;
                index = (index + k) % length;
            }
            i++;
        }
    }
    public int gcd(int x, int y) {
        return y > 0 ? gcd(y, x % y) : x;
    }
}

思路三

采用数组倒置的方式,进行三次翻转

上个思路花了我整整半天的时间,实在是写不动了 wwwwww

class Solution {
    public void rotate(int[] nums, int k) {
        k %= nums.length;
        reverse(nums, 0, nums.length - 1);
        reverse(nums, 0, k - 1);
        reverse(nums, k, nums.length - 1);
    }

    public void reverse(int[] nums, int start, int end) {
        while (start < end) {
            int temp = nums[start];
            nums[start] = nums[end];
            nums[end] = temp;
            start += 1;
            end -= 1;
        }
    }
}

【396】旋转函数

Link:396. 旋转函数 - 力扣(LeetCode) (leetcode-cn.com)

暴力破解,日常超时

动态规划有点难顶啊,还是去看了评论TAT

nums[0] = 4 3 2 6 F
x0 x1 x2 x3 F[0]
x3 x0 x1 x2 F[1] = F[0] - sum + 4 * nums[0]
x2 x3 x0 x1 F[2] = F[1] - sum + 4 * nums[1]
x1 x2 x3 x0 F[3] = F[2] - sum + 4 * nums[2]
class Solution {
    public int maxRotateFunction(int[] nums) {
        int length = nums.length, sum = 0, max = Integer.MIN_VALUE;
        int[] F = new int[length];
        for (int i = 0; i < length; i++) {
            F[0] += nums[i] * i;
            sum += nums[i];
        }
        max = Math.max(max, F[0]);
        for (int i = 1; i < length; i++) {
            F[i] = F[i - 1] - sum + nums[i-1] * length;
            max = Math.max(max, F[i]);
        }
        return max;
    }
}
// 执行耗时:6 ms,击败了17.07% 的Java用户
// 内存消耗:51.2 MB,击败了20.73% 的Java用户

特定顺序遍历二维数组

【54】螺旋矩阵

Link:54. 螺旋矩阵 - 力扣(LeetCode) (leetcode-cn.com)

分析分析分析!

相当于依次取出最外围的那圈数字,所以总共有 m/2 or n/2圈,每圈算完把m,n -2就可以,同时记一下圈数round,每一圈算一下数字个数(当圈为1行或1列时计算方式不同),分四个阶段对i or j进行增减,此时需要添加到List中的元素下标为[i+round,j+round]。进行模拟就可以

class Solution {
    public List<Integer> spiralOrder(int[][] matrix) {
        List<Integer> res = new ArrayList<>();
        int m = matrix.length, n = matrix[0].length, round = 0;
        while (m > 0 && n > 0) {
            int i = 0, j = 0;
            int count = (m == 1 || n == 1) ? m * n : (m + n - 2) * 2; // 若圈为1行或1列,须单独考虑
            for (int k = 0; k < count; k++) {
                res.add(matrix[i + round][j + round]);
                if (k < n - 1) { //圈的上
                    j++;
                } else if (k < m + n - 2) { //圈的右
                    i++;
                } else if (k < n + m + n - 2 - 1) { //圈的下
                    j--;
                } else { //圈的左
                    i--;
                }
                // System.out.println(i+","+j);
            }
            m -= 2;
            n -= 2;
            round++; // 计算圈数
        }
        return res;
    }
}
//执行耗时:8 ms,击败了100.00% 的Java用户
//内存消耗:36.6 MB,击败了38.84% 的Java用户

对比了下官方题解,相当于其中的方法二,不过官方的使用位置指针与四个角落的相对位置来调整,而我是根据圈的长度来调整。emmm…我的看着简单点吧(可能吧,先自夸一手)

【59】螺旋矩阵II

Link:59. 螺旋矩阵 II - 力扣(LeetCode) (leetcode-cn.com)

与上一道殊途同归

class Solution {
    public int[][] generateMatrix(int n) {
        int[][] res = new int[n][n];
        int round = 0, num=1;
        while(n>0){
            int i=0,j=0;
            int count = n==1? 1:(n-1)*4;
            for (int k = 0; k < count; k++) {
                res[i+round][j+round] = num++;
                if (k < n - 1) { //圈的上
                    j++;
                } else if (k < (n-1)*2) { //圈的右
                    i++;
                } else if (k < (n-1)*3) { //圈的下
                    j--;
                } else { //圈的左
                    i--;
                }
            }
            n-=2;
            round++;
        }
        return res;
    }
}
// 执行耗时:0 ms,击败了100.00% 的Java用户
// 内存消耗:36.3 MB,击败了70.46% 的Java用户

【498】对角线遍历

Link:498. 对角线遍历 - 力扣(LeetCode) (leetcode-cn.com)

还是模拟,但是更麻烦了啊!!

对于m*n的矩阵mat,总共有对角线m+n-1条,所以第一层便利需要m+n-1次;同样采用下标指针i,j进行取值,当指针位于矩阵内时,即为当前循环符合条件的数;取出一个值后,将指针转到下一个可能的值(往右上或左下),倘若指针超出了矩阵范围,则说明需要进入下一条对角线了。

需要注意的是,在左上方的对角线转换时,只需要对i,j其一进行++操作;但在右下方的对角线转换中,指针需要进行了三个格子的移动。按照我的思路这部分是比较麻烦的。

最后的耗时可真的感人。。。。

class Solution {
    public int[] findDiagonalOrder(int[][] mat) {
        int m=mat.length, n=mat[0].length;
        int[] res = new int[m*n];
        int i=0,j=0,count=0;
        for (int k = 0; k < m+n-1; k++) {
            int l = 0;
            while(i>=0 && i<m && j>=0 && j<n){
                //System.out.println(count+"-"+i+"-"+j);
                res[count++] = mat[i][j];
                if(k % 2 == 0){
                    i--;
                    j++;
                }else{
                    i++;
                    j--;
                }
                if(i>=m) j++;
                if(j>=n) i++;
            }
            if(i<0) i++;
            if(j<0) j++;
            if(i>=m) {i--;j++;}
            if(j>=n) {j--;i++;}
        }
        return res;
    }
}
//执行耗时:152 ms,击败了6.61% 的Java用户
//内存消耗:40.1 MB,击败了81.15% 的Java用户

看了下官方题解,思路一最清晰,获取每条对角线,根据序号的奇偶判断是否翻转;思路二是模拟,我是基于每条对角线,官方是一个一个去遍历,使用flag判断方向,总结的比我明了好多。反思了下我的代码,应该是重复操作有点多,导致耗时较多

二维数组变换

【566】重塑矩阵

Link:566. 重塑矩阵 - 力扣(LeetCode) (leetcode-cn.com)

简单!两次遍历,把下标计算一下放到新矩阵的对应位置就好

class Solution {
    public int[][] matrixReshape(int[][] mat, int r, int c) {
        int m= mat.length, n=mat[0].length;
        if(m*n != r*c || (m==r && n==c)) return mat;
        int[][] res = new int[r][c];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                res[(i*n+j)/c][(i*n+j)%c] = mat[i][j];
            }
        }
        return res;
    }
}
//执行耗时:0 ms,击败了100.00% 的Java用户
//内存消耗:39.3 MB,击败了46.81% 的Java用户

看官方题解,其实没必要两次循环,一次循环就可以,循环 m*n

【48】旋转图像

Link:48. 旋转图像 - 力扣(LeetCode) (leetcode-cn.com)

总结出数学规律很简单,元素matrix[i][j]的最终位置为matrix[j][n-i-1]

难点在于如何在原矩阵中进行操作。我的想法是一圈一圈处理,如最外围一圈一共需要交换元素4*(n-1)次,总共需要操作的圈数为n/2(若n为奇数,最中间的数不必操作)。

由于存在各种数值的交换操作,所以用于备份的参数比较多,这是有点拉的。

//   [a][b]    ---   [b][n-a-1]
//      |                 |
//      |                 |
// [n-b-1][a]  ---  [n-a-1][n-b-1]

class Solution {
    public void rotate(int[][] matrix) {
        int n=matrix.length,round = 0;
        while (round<n/2) {
            int a=round,b=round;
            for (int i = 0; i < n-2*round-1; i++) {
                int pre = matrix[a][b];
                for (int j = 0; j < 4; j++) {
                    int tmp = matrix[b][n-a-1];
                    matrix[b][n-a-1] = pre;
                    pre = tmp;
                    int c=a;
                    a=b;
                    b=n-c-1;
                }
                b++;
            }
            round++;
        }
    }
}
//执行耗时:0 ms,击败了100.00% 的Java用户
//内存消耗:38.8 MB,击败了5.08% 的Java用户

参考官方题解中的原地旋转,像其中的4次循环,就没有必要用for循环写,直接进行交换就好

int pre = matrix[a][b];
matrix[a][b] = matrix[n-b-1][a];
matrix[n-b-1][a] = matrix[n-a-1][n-b-1];
matrix[n-a-1][n-b-1] = matrix[b][n-a-1];
matrix[b][n-a-1] = pre;

//执行耗时:0 ms,击败了100.00% 的Java用户
//内存消耗:38.3 MB,击败了87.44% 的Java用户

【73】矩阵置零

还简单的,用两个数组保存有0的行号和列号,再进行一次遍历把元素换成0即可。不过这种就是空间复杂度为O{m+n}

class Solution {
    public void setZeroes(int[][] matrix) {
        int m= matrix.length, n=matrix[0].length;
        int[] M = new int[m];
        int[] N = new int[n];
        for (int i = 0; i < m; i++)
            for (int j = 0; j < n; j++)
                if(matrix[i][j] == 0) {
                    M[i] = 1;
                    N[j] = 1;
                }
        for (int i = 0; i < m; i++)
            for (int j = 0; j < n; j++) {
                if(M[i]==1 || N[j] == 1)
                    matrix[i][j] =0;
            }
    }
}

参考官方题解,O{1}的空间复杂度主要是使用了原矩阵的某一行/列+额外一个数组来记录0的情况。

【289】生命游戏

Link:289. 生命游戏 - 力扣(LeetCode) (leetcode-cn.com)

最简单直接的还是模拟,同时还要考虑周围8个数是否存在。emmmm又是疯狂if的一道题

对于进阶的原地算法,表示无能为力

class Solution {
    public void gameOfLife(int[][] board) {
        int m = board.length, n = board[0].length;
        int[][] state = new int[2][m*n];
        int count = 0;
        for (int i = 0; i < m; i++)
            for (int j = 0; j < n; j++) {
                int live = 0;
                if (i != 0)
                    live += board[i - 1][j] == 1 ? 1 : 0;
                if (i != m - 1)
                    live += board[i + 1][j] == 1 ? 1 : 0;
                if (j != 0)
                    live += board[i][j - 1] == 1 ? 1 : 0;
                if (j != n - 1)
                    live += board[i][j + 1] == 1 ? 1 : 0;
                if (i != 0 && j != 0)
                    live += board[i - 1][j - 1] == 1 ? 1 : 0;
                if (i != 0 && j != n - 1)
                    live += board[i - 1][j + 1] == 1 ? 1 : 0;
                if (i != m - 1 && j != 0)
                    live += board[i + 1][j - 1] == 1 ? 1 : 0;
                if (i != m - 1 && j != n - 1)
                    live += board[i + 1][j + 1] == 1 ? 1 : 0;
                if (board[i][j] == 1) {
                    if (live < 2 || live > 3) {
                        state[0][count] = i;
                        state[1][count] = j;
                        count++;
                    }
                } else {
                    if (live == 3) {
                        state[0][count] = i;
                        state[1][count] = j;
                        count++;
                    }
                }
            }
        for (int i = 0; i < count; i++)
            board[state[0][i]][state[1][i]] = board[state[0][i]][state[1][i]]==1?0:1;
    }
}
//执行耗时:0 ms,击败了100.00% 的Java用户
//内存消耗:36.8 MB,击败了47.25% 的Java用户

逛了逛评论区,发现这个生命游戏还挺有意思,这里放个科大大佬提供的生命游戏模拟 生命游戏 (ustc.edu.cn)

看了官方题解的原地算法,Amazing!(´・Д・)」只要再拿两个状态2/3来分别表示0/1修改情况就可以了,果然大道至简……

【303】区域和检索 - 数组不可变

Link:303. 区域和检索 - 数组不可变 - 力扣(LeetCode) (leetcode-cn.com)

第一次写这种形式的题,虽说思路很简单,但纠结于不会写

最后只能去评论区参考下写法TAT

思路一

class NumArray {
    private int nums[];
    public NumArray(int[] nums) {
        this.nums = nums;
    }
    public int sumRange(int left, int right) {
        int sum=0;
        for (int i = left; i <= right; i++) {
            sum+=nums[i];
        }
        return sum;
    }
}
//执行耗时:70 ms,击败了13.68% 的Java用户
//内存消耗:41.2 MB,击败了66.13% 的Java用户

思路二

获取给点数组时就计算好数组前n项的和sums[],这样[i,j]区间的和就是sums[j]-sums[i-1]

同时还需要考虑边界的问题,所以sums[]长度应比nums[]大1,sum[n]表示前n项的和

class NumArray {
    private int sums[];
    public NumArray(int[] nums) {
        int length = nums.length+1;
        sums=new int[length];
        for (int i = 1; i < length; i++) {
            sums[i] = sums[i-1]+nums[i-1];
        }
    }
    public int sumRange(int left, int right) {
        return sums[right+1]-sums[left];
    }
}
//执行耗时:7 ms,击败了100.00% 的Java用户
//内存消耗:41.2 MB,击败了60.66% 的Java用户

边界这个问题还是依靠了讨论区的大佬们才解决TAT

【304】二维区域和检索 - 矩阵不可变

304. 二维区域和检索 - 矩阵不可变 - 力扣(LeetCode) (leetcode-cn.com)

与上一道很像,但在这里,思路一会导致超时

对思路二进行拓展,sums[i][j]表示矩阵左上角i行j列的矩阵求和。考虑边界问题,sums[i][j]为从matrix[0][0]开始到matrix[i-1][j-1]的求和。

class NumMatrix {
    private int[][] sums;
    public NumMatrix(int[][] matrix) {
        int m=matrix.length, n = matrix[0].length;
        sums = new int[m+1][n+1];
        for (int i = 1; i < m+1; i++) {
            for (int j = 1; j < n+1; j++) {
                sums[i][j] = sums[i-1][j]+sums[i][j-1]-sums[i-1][j-1]+matrix[i-1][j-1];
            }
        }
    }
    public int sumRegion(int row1, int col1, int row2, int col2) {
        return sums[row2+1][col2+1] - sums[row1][col2+1] - sums[row2+1][col1] + sums[row1][col1];
    }
}

由于扩展了一圈,所以在下标的处理上要注意!

官方题解的另一个思路是计算每行的求和

【238】除自身以外数组的乘积

Link:238. 除自身以外数组的乘积 - 力扣(LeetCode) (leetcode-cn.com)

思路一

不用除法想不到,所以就先写了用除法的:遍历一次求出所有数的乘积mul,某下标处的结果就是mul除这个位置的数。但需要考虑0的情况:

  1. 存在两个以上0,那么返回的结果就全是0;
  2. 存在一个0,那么求乘积时要将其跳过,在计算结果时除0所在下标为mul外均为0。

在做出来后测试了下,果然有这些情况

class Solution {
    public int[] productExceptSelf(int[] nums) {
        int length = nums.length, hasZero = -1, mul=1;
        int[] res = new int[length];
        for (int i = 0; i < length; i++) {
            if (nums[i] == 0 && hasZero<0) {
                hasZero=i;
                continue;
            }
            if (nums[i] == 0 && hasZero>=0) {
                hasZero=-2;
                break;
            }
            mul*=nums[i];
        }
        if (hasZero == -2) return res;
        if (hasZero>=0){
            for (int i = 0; i < length; i++)
                res[i] = i==hasZero? mul: 0;
        }else{
            for (int i = 0; i < length; i++)
                res[i] = mul/nums[i];
        }
        return res;
    }
}
//执行耗时:1 ms,击败了100.00% 的Java用户
//内存消耗:48.9 MB,击败了72.95% 的Java用户

思路二

考虑还是不够全面,只想着正序遍历,没想到加上倒序遍历。

第一次遍历,对于每个下标,计算得到其左边元素的乘积并记录,此时最后一个下标得到的就是最终结果;第二次遍历,从倒数第二个开始,用一个数mul记录其右边的元素乘积,与第一次结果相乘后就是最终结果,然后对mul进行更新供下一个下标使用。

class Solution {
    public int[] productExceptSelf(int[] nums) {
        int length = nums.length;
        int[] res = new int[length];
        res[0] = 1;
        for (int i = 1; i < length; i++) {
            res[i] = res[i-1] * nums[i-1];
        }
        int mul = nums[length-1];
        for (int i = length-2; i >=0; i--) {
            res[i] = res[i] * mul;
            mul *= nums[i];
        }
        return res;
    }
}
//执行耗时:2 ms,击败了47.47% 的Java用户
//内存消耗:49 MB,击败了55.19% 的Java用户

脑子应该还不至于太差吧,一点就通,直接跳过官方题解一的思路想到题解二的思路。

不过。。。emmmm。。。思路更高级,但是消耗都大了呢

终于,数组篇刷完了,泪目。

但自己也很明显能够感觉到,光靠这些题还远远不够掌握好数组,之后还是需要再找些题来做一做。

你可能感兴趣的:(leetcode,java,数组)