LeetCode专题学习记录

本着快速积极复习数据结构和算法基础的目的,按照LeetCode的标签,一个个过。根据标签复习总结相关的知识点,每个标签保证10道左右的刷题量(简单程度),目的是确保掌握这个知识点的基本使用方式。

数组

知识点

数组:把具有相同类型的若干元素按有序的形式组织起来,线性表的实现方式之一(另外一个是链表)

数组利用索引来记录每个元素在数组中的位置,索引从0开始,即数组A[m]的索引值范围是[0,m-1],可通过索引快速访问数组中的元素,索引指向的是内存地址

在这里插入图片描述

数组中的元素在内存中连续存储,每个元素占用相同大小的内存(一开始数组定义好之后即每个元素就有相同大小的内存,即便这个时候还没有具体的值)

在这里插入图片描述

如上图可以看到apples的内存地址就是oranges的内存地址加索引值

题867. 转置矩阵
题目

给定一个矩阵 A, 返回 A 的转置矩阵。

矩阵的转置是指将矩阵的主对角线翻转,交换矩阵的行索引与列索引。

示例 1:

输入:[[1,2,3],[4,5,6],[7,8,9]] A[3][3]
输出:[[1,4,7],[2,5,8],[3,6,9]] B[3][3]
示例 2:

输入:[[1,2,3],[4,5,6]] A[2][3]
输出:[[1,4],[2,5],[3,6]] B[3][2]

提示:

1 <= A.length <= 1000
1 <= A[0].length <= 1000

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/move-zeroes

题目解析

转置矩阵:B[i][j] = A[j][i]

此处需要用到二维数组,对于一个二维数组A[i][j]而言,

其行长度width为A.length,也称一维长度

列长度height为A[i].length,一般使用A[0].length,也称二维长度(某个一维数组的数组元素个数)

转置之后得到的也是一个二维数组B,与A相比,刚好是行长度和列长度颠倒,即B的行长度为A[0].length,列长度为A.length,例如A[2][3],那么转置后得到B[3][2]

只要用到二维数组,肯定会使用两个for循环,在两个for循环中遍历A的元素将其复制给B数组,其中有一个技巧点,当A是方阵即width==height的时候,可以简化循环,先遍历i到width,然后遍历j从i开始到height,然后直接B[i][j] = A[j][i]``B[j][i] = A[i][j]即可(因为width==height,所以无需担心数组越界问题)

代码实现
class Solution {
    public int[][] transpose(int[][] A) {
        // 行
        int width = A.length;
        // 列
        int height = A[0].length;
        // 返回第一个元素大小为A[0]的长度,第二个大小为A的长度
        int[][] B = new int[height][width];

        // 考虑到方阵的话无需循环遍历,直接ij=ji
        if (width == height) {
            for (int i = 0; i<width; i++) {
                for (int j = i; j<height; j++) {
                    B[i][j] = A[j][i];
                    B[j][i] = A[i][j];
                }
            }
        } else {
            for (int i = 0; i< height; i++) {
                for (int j = 0; j < width; j++) {
                    B[i][j] = A[j][i];
                }
            }
        }

        
        // 另一种方法
        // for (int i = 0; i< width; i++) {
        //     for (int j = 0; j < height; j++) {
        //         B[j][i] = A[i][j];
        //     }
        // }
        return B;
    }
}
总结

1,二维数组的定义

int[][] array = new int[m][n]

2,二维数组获取数组大小

array.length

3,二维数组获取二维长度大小,即每个一维数组中有几个元素(多个一维数组组成的二维数组)

array[0].length

4,二维数组的遍历使用双层for循环:时间复杂度为width * height

        
         for (int i = 0; i< width; i++) {
             for (int j = 0; j < height; j++) {
                 // ...
            }
         }

根据实际情况可以通过概念ij的范围来减少遍历次数

题283. 移动零

题目

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

示例:

输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
说明:

必须在原数组上操作,不能拷贝额外的数组。
尽量减少操作次数。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/move-zeroes

题目解析

题目很清晰,就是遍历数组将所有0元素往后挪,并且保证其他非0元素相对顺序不变。

我的思路这块想麻烦了,想的是直接二重循环,然后每次比较元素是否为0,如果不是的话,让这个元素和后面的元素进行置位,但这个里面存在多次冗余置换位置的操作,所以性能较低

class Solution {
    public void moveZeroes(int[] nums) {
        // 定义一个临时变量temp
        int temp = 0;
        for (int i=0; i<nums.length; i++) {
            for (int j=0; j<nums.length-1; j++) {
                if (nums[j] == 0) {
                    // 前后元素进行位置交换
                    temp = nums[j+1];
                    nums[j+1] = nums[j];
                    nums[j] = temp;
                }
            }

        }
    }
}

// `[0,1,0,3,12]`,第一次内循环结束后为`[1,0,3,12,0]` 遍历4次
// 第一次内循环结束为`[1,3,12,0,0]` 遍历4次
// 继续遍历一直到i=nums.length-1=4为止,每次都遍历4次,总共遍历4*5=20次
// 但是实际上不需要j从0开始遍历,0元素已经挪动了;另外也不需要每个元素都遍历
优秀题解
双指针法

给两个指针,左指向已处理好的序列的尾部,右指向待处理的头部。

不断移动右指针,当右指向非零数的时候,都将左右指针指向的数进行交换,同时左指针右移

左指针左边均为非零数;右指针左侧到左指针都是零

每次交换,都是将左指针的零与右指针的非零数交换,且非零数的相对顺序并未改变

class Solution {
    public void moveZeroes(int[] nums) {
        // 定义一个临时变量temp
        int temp = 0;
        // 定义左右指针
        int left = 0; int right = 0;
        for (int i=0; i<nums.length; i++) {
            if (nums[right] != 0) {
                temp = nums[left];
                nums[left] = nums[right];
                nums[right] = temp;
                left++;
            }
            right++;
        }
    }
}

优化:移动的肯定是一个非0数和0进行交换,可以直接将非零数赋值给前面的零数,然后直接将right位的值为0(left!=right) 时间复杂度为O(n),空间复杂度为O(1)

另外注意当数组为空的时候,直接返回即可(注意临界值)

class Solution {
    public void moveZeroes(int[] nums) {
        // 数组为空,直接返回
        if (nums == null) { //不等同于nums.length=0(如果nums为null,取length会报空指针异常)
            return;
        }
        // 定义一个临时变量temp
        int temp = 0;
        // 定义左右指针
        int left = 0; int right = 0;
        for (int i=0; i<nums.length; i++) {
            if (nums[right] != 0) {
                // 移动的肯定是一个非0数和0进行交换,可以直接将非零数赋值给前面的零数,然后直接将right位的值为0(left!=right)
                // temp = nums[left];
                // nums[left] = nums[right];
                // nums[right] = temp;
                nums[left] = nums[right];
                // 注意一定要加判断,否则如果left=right,结果为直接为0
                if (left != right) {
                    nums[right] = 0;
                }
                left++;
            }
            right++;
        }
    }
}
总结

1,即使是Java语言没有指针,但也有指针思维去解决问题

2,变量交换:给一个临时变量完成值的转移操作

int temp = a;
a = b;
b = temp;

3,数组为null和数组长度为0的区别:

int[] array = null; 数组为空,此时array不指向任何对象;

int[] array = new array[0]; 定义一个长度为0的数组;

int[] array = new array[2]; 定义一个长度为2的数组,此时如果数组没有被初始化,默认的值为null;

一般先判断 nums 是否为空,然后再判断长度是否为0,因为可能报空指针异常。

if(array == null || array.length == 0)
    // array.length == 0 || array == null 这种写法不恰当,因为可能会由于array为null而导致空指针异常
题27. 移除元素
题目

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

示例 1:

给定 nums = [3,2,2,3], val = 3,

函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。

你不需要考虑数组中超出新长度后面的元素。
示例 2:

给定 nums = [0,1,2,2,3,0,4,2], val = 2,

函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。

注意这五个元素可为任意顺序。

你不需要考虑数组中超出新长度后面的元素。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/remove-element

题目解析

遍历数组将所有非val的值返回/去除数组中值等于val的元素

仔细想想将val换为0不就是题283,只不过这个题是去除0而非移动0,但是我们可以通过移动的方式将所有需要去除的元素放在数组最后,然后记录最新的一个val元素的下标即可(数组下标从0开始,最新的一个val下标即表示移除val后数组的大小)

复盘下283题,发现变量i没啥用,right的作用同i,所以优化了下

代码实现
class Solution {
    public int removeElement(int[] nums, int val) {
        // 若nums为空,直接返回0
        if (nums == null) {
            return 0;
        }
        int left = 0;
        // 定义中间变量,用于元素互换
        int temp = 0;
        for (int right = 0; right< nums.length;) {
            if (nums[right] != val) {
                temp = nums[right];
                nums[right] = nums[left];
                nums[left] = temp;
                // left即为最新的一个val的下标
                left++;
            }
            right++;
        }
        return left;
    }
}
// 移动n次,时间复杂度为O(n);空间复杂度为O(1)
优秀题解
双指针法——移动元素法(和本人解法一致,只不过没有用temp中间变量,也无需进行元素交换,直接赋值即可)

当 nums[j]与给定的值相等时,递增 j 以跳过该元素。

只要 nums[j] 不等于 val,我们就复制 nums[j]到 nums[i]并同时递增两个索引。

重复这一过程,直到 j 到达数组的末尾,该数组的新长度为 i

class Solution {
    public int removeElement(int[] nums, int val) {
        if (nums == null) {
            return 0;
        }
        int left = 0;
        for (int right = 0; right < nums.length;) {
            if (nums[right] != val) {
                nums[left] = nums[right];
                left++;
            }
            right++;
        }
        return left;
    }
}
// 移动n次,left和right至少各遍历n次。时间复杂度为O(n);空间复杂度为O(1)
双指针法——当删除的元素很少的时候

当遇到要 nums[j] 等于 val的时候,将nums[i]与最后一个元素交换,并释放最后一个元素,实际上可使数组大小减1

当然交换过来的那个最后一个元素也有可能等于val,所以下一次迭代中还会进行检查(while循环,不断遍历数组直到数组中的每个元素都不等于val)

    public static int removeElement(int[] nums, int val) {
        // 交换元素法
        if (nums == null) {
            return 0;
        }
        int i = 0;
        int n = nums.length;
        while (i < n) {
            if (nums[i] == val) {
                // 将数组最后一个元素与其进行交换
                nums[i] = nums[n-1];
                // 减少数组大小
                n--;
                // 不改变i的大小,继续判断是否nums[i] == val,从而避免交换过来的这个最后元素也等于val
            } else {
                i++;
            }
        }
        return n;
    }
// i和n最多遍历n次,时间复杂度为O(n),赋值操作的次数==要删除元素的个数,如果删除的元素少的话效率会更高;空间复杂度为O(1)
总结

学会从283的思路解开27的题,给自己的赞!

同时通过两个优秀题解对于双指针的解法, 更深入理解双指针的应用。对于移动元素法,无需多余的交换,直接赋值即可。对于交换最后一个元素法,勿忘判断最后一个元素是否为val

题26. 删除排序数组中的重复项
题目

给定一个排序数组,你需要在 原地 删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。

不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

示例 1:

给定数组 nums = [1,1,2],

函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。

你不需要考虑数组中超出新长度后面的元素。
示例 2:

给定 nums = [0,0,1,1,1,2,2,3,3,4],

函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。

你不需要考虑数组中超出新长度后面的元素。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array

题目解析

道理同前两题,只不过val是变化的,根据left下标的值而定,left初始值为0,right初始值为1

如果right对应的值等于left对应的值,则不作任何操作,继续往后遍历

如果right对应的值不等于left对应的值,则将right对应的值覆盖掉++left对应的值

代码实现
class Solution {
    public int removeDuplicates(int[] nums) {
                int left = 0;
        for (int right = 1; right < nums.length;) {
            if (nums[right] != nums[left]) {
                // 拷贝覆盖
                nums[++left] = nums[right];
            }
            right++;
        }
        return left+1;
    }
}
总结

此题忽略了一个点:数组是个排序数组,即意味着数组中的元素是有序排列的,那么其实可以直接使用nums[right] > nums[left]进行判断

要善于从题目中发现可攻破点

题80. 删除排序数组中的重复项II
题目

给定一个增序排列数组 nums ,你需要在 原地 删除重复出现的元素,使得每个元素最多出现两次,返回移除后数组的新长度。

不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

示例 1:

输入:nums = [1,1,1,2,2,3]
输出:5, nums = [1,1,2,2,3]
解释:函数应返回新长度 length = 5, 并且原数组的前五个元素被修改为 1, 1, 2, 2, 3 。 你不需要考虑数组中超出新长度后面的元素。
示例 2:

输入:nums = [0,0,1,1,1,1,2,3,3]
输出:7, nums = [0,0,1,1,2,3,3]
解释:函数应返回新长度 length = 7, 并且原数组的前五个元素被修改为 0, 0, 1, 1, 2, 3, 3 。 你不需要考虑数组中超出新长度后面的元素。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array-ii

题目解析

一个排序数组

每个重复的元素只留两个

不重复的元素保持不变

可继续使用上述的双指针拷贝覆盖法

对于重复的元素要有两个,可使用一个计数器进行记录,当值为2的时候再遇到相同的元素就跳过

代码实现
class Solution {
    public int removeDuplicates(int[] nums) {
        int left = 0;
        // 与nums[left]相同的元素个数,最多为1
        int num = 0;
        for (int right = 1; right < nums.length;) {
            if (nums[right] == nums[left] && num != 1) {
                nums[++left] = nums[right];
                num++;
            }
            // 数组是排序数组,从小到大排序,不相同的元素即比之前的元素大
            if (nums[right] > nums[left]) {
                nums[++left] = nums[right];
                // 遇到下一个不同的元素,计数器重新置0
                num = 0;
            }
            right++;
        }
        return left+1;
    }
}
优秀题解
利用排序数组

两种情况:

  • left<2,则直接移动left
  • right-2对应的值>left对应的值,拷贝覆盖
class Solution {
    public int removeDuplicates(int[] nums) {
        int left = 0;
        // 使用foreach,避免fori中不必要的临时变量i的消耗
        for (int n: nums) {
            // left=0,1。0,1对应的值相同和不同都符合要求
            // 第三个元素之后如果不相同,即大于第一个元素,则left=第三个元素下标,left++
            if (left < 2 || n > nums[left-2]) {
                // 先赋值后++
                nums[left++] = n;
            }
        }
        return left;
    }
}
总结

擅于从题中发现技巧点

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值