刷题顺序参考 有没有人一起从零开始刷力扣 - 力扣(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 |
Long.MIN_VALUE
,Long.MAX_VALUE
——【414】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用户
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用户
Link:414. 第三大的数 - 力扣(LeetCode) (leetcode-cn.com)
刚开始就是冲着o(n)去的,思路也很简单,遍历数组记下最大的三个就可以,但也遇到不少问题
重复数字要避免重新计数:
数据大小范围:
由于我是使用数组进行存储最大的数,其初始化值为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_VALUE
,Long.MAX_VALUE
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用户
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用户
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用户
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用户
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;
}
}
Link:41. 缺失的第一个正数 - 力扣(LeetCode) (leetcode-cn.com)
使用Arrays.sort
排序后,依次遍历,当遍历数大于0时,采用一个不断累加的数x进行比较(当两数相同,x加一)
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用户
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. 最小操作次数使数组元素相等 - 力扣(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。。。。。
对数学不好的娃来说这种简单题就是噩梦
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用户
被这两道题卡的死死的了,现在不管啥题都小心翼翼,生怕哪里没考虑到,提交都心惊胆战的
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用户
- 难得有一道一次过,虽然结果比起大佬们差太多 >…<
- 参考了下最快的方法,发现自己还是傻,把自己原来的两个思路结合一下就是了。。
- 除此之外,还看到了很多巧妙的写法,双指针的运用也很多,我用的应该是最捞的了
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用户
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用户
还是要骂一下自己大伞兵,由于第一行只有一个元素,所以我一开始单独拎出来写了,但参考了官方解答之后,意识到了就算是一个元素,但是和其他行没有区别,同样是有头和尾的数列,改完之后内存消耗瞬间上了一档。
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用户
冲冲冲!(这几道都是一遍过,希望别太膨胀了)
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
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…
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++;
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
后来花时间自己去想了不按照题解的思路去分析:循环的个数由
length
和k
或length-k
的公因数,如示例中当k=2 or 4
时,循环次数为2;同时为了缩减代码,将循环初始位作为备份单元,如示例中的nums[0] and nums[1]
作为缓存。但提交之后在一个length
和k
达上万的测试案例中崩了。最后分析了很久,最后意识到,由于思考的参数比较简单,都是采用length-k
为6-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;
}
}
}
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用户
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…我的看着简单点吧(可能吧,先自夸一手)
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用户
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判断方向,总结的比我明了好多。反思了下我的代码,应该是重复操作有点多,导致耗时较多
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
次
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用户
还简单的,用两个数组保存有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的情况。
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
修改情况就可以了,果然大道至简……
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. 二维区域和检索 - 矩阵不可变 - 力扣(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];
}
}
由于扩展了一圈,所以在下标的处理上要注意!
官方题解的另一个思路是计算每行的求和
Link:238. 除自身以外数组的乘积 - 力扣(LeetCode) (leetcode-cn.com)
不用除法想不到,所以就先写了用除法的:遍历一次求出所有数的乘积mul
,某下标处的结果就是mul
除这个位置的数。但需要考虑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。。。思路更高级,但是消耗都大了呢
终于,数组篇刷完了,泪目。
但自己也很明显能够感觉到,光靠这些题还远远不够掌握好数组,之后还是需要再找些题来做一做。