新手Leetcode 数组简单篇(附解题思路及JAVA代码)

知识点预览

(拜托拜托!一定要去看文末的总结,是我每次刷完题后的反思回顾)


  1. Java怎么返回数组 ------ 具体见0001 两数之和 暴力法下面的补充

  1. 用哈希表降低双重遍历的时间复杂度(代价是空间复杂度的增加)------- 具体见0001 两数之和 哈希表法思路和算法二

  1. 用快慢指针遍历比较数组元素及修改数组元素 ------- 具体见0026 删除有序数组中的重复项的思路和算法三 , 0027 移除元素的思路和算法四

  1. 二分法查找目标数组元素 ------- 具体见0035 搜索插入位置的思路和算法五 , 0034 在排序数组中查找元素的第一个和最后一个位置的思路和算法六

1、0001 两数之和


题目描述

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那两个整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

示例 1:

输入:nums = [2,7,11,15], target = 9输出:[0,1]解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。示例 2:

输入:nums = [3,2,4], target = 6输出:[1,2]示例 3:

输入:nums = [3,3], target = 6输出:[0,1]

提示:

2 <= nums.length <= 104-109 <= nums[i] <= 109-109 <= target <= 109只会存在一个有效答案

进阶:你可以想出一个时间复杂度小于 O(n2) 的算法吗?

解题思路

暴力法:
复杂度分析

时间复杂度:O(N^2),其中 N是数组中的元素数量。最坏情况下数组中任意两个数都要被匹配一次。

空间复杂度:O(1)。

思路和算法一

暴力法匹配

代码

classSolution {

public int[] twoSum(int[] nums, inttarget) {
    int n = nums.length;
    for (int i = 0; i < n; ++i) {
        for (int j = i + 1; j < n; ++j) {
            if (nums[i] + nums[j] == target) {
                return new int[]{i, j};
            }
        }
    }
    return new int[0];
}
补充

理解以下两行代码:

return new int[]{1, 2};

return new int[0];

return new int[]{1, 2}; 其实就是返回了一个长度为2的数组。

结合代码理解:我们找到了和为目标值的两个元素,因此返回包含他们的下标值的数组。

return new int[0]; 中new int[0]代表着创建了一个长度为0的int数组,

与int[] arr = new int[0]不同,后者是创建了数组并将其引用赋值给变量arr,return new int[0];

return new int[0] ;并没有赋值操作,而是直接返回一个长度为0的数组。

注意:返回return new int[0],定义在此处没有实际的意义,只是为了编译器编译过程中不出错。

哈希表法:
复杂度分析

时间复杂度:O(N),其中 N 是数组中的元素数量。对于每一个元素 x,我们可以 O(1)地寻找 target - x。

空间复杂度:O(N),其中 N是数组中的元素数量。主要为哈希表的开销。

思路和算法二

把要遍历的数组存入哈希表,可以将寻找 target - x 的时间复杂度降低到从 O(N) 降低到 O(1)。为什么可以做到呢?

具体一点就是:哈希表存入了数组的值,在遍历的时候,随着i的增加,指针往后移,每次当遍历到这个数组元素的时候,我们可以让数组元素判断哈希表里是否存在target-x这个元素。不需要像暴力法一样,用 i 和 j 完成双重遍历。相当于第二重 j 的遍历(寻找 target - x 的时间)被隐含在了遍历元素中。

所以整体时间复杂度降为O(N),但是空间复杂度升为了O(N)。经典的用空间换时间。

代码
classSolution {
    public int[] twoSum(int[] nums, int target) {
        Map<Integer, Integer> hashtable =new HashMap<Integer, Integer>();//新建一个哈希表
        for (int i = 0; i < nums.length; ++i) {//遍历数组nums
            if (hashtable.containsKey(target - nums[i])) {//哈希表存在key值 = 目标值target - 数组值num[i]
                return new int[]{hashtable.get(target - nums[i]), i}; 
                //hashtable.get返回key对应的value(其中一个数组下标) , i(另一个数组下标)
            }
            hashtable.put(nums[i], i); //如果没有找到的话,把数组的值和下标存入哈希表
        }
        return new int[0];//返回题目要求的数组
    }
}

2、0026 删除有序数组中的重复项


题目描述

给你一个 升序排列的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。

由于在某些语言中不能改变数组的长度,所以必须将结果放在数组nums的第一部分。更规范地说,如果在删除重复项之后有 k 个元素,那么 nums 的前 k 个元素应该保存最终结果。

将最终结果插入 nums 的前 k 个位置后返回 k 。

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

判题标准:

系统会用下面的代码来测试你的题解:

int[] nums = [...]; // 输入数组int[] expectedNums = [...]; // 长度正确的期望答案

int k = removeDuplicates(nums); // 调用

assert k == expectedNums.length;for (int i = 0; i < k; i++) { assert nums[i] == expectedNums[i];}如果所有断言都通过,那么您的题解将被 通过。

示例 1:

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

提示:

1 <= nums.length <= 3 * 104-104 <= nums[i] <= 104nums 已按 升序 排列

解题思路

指针法:
复杂度分析

时间复杂度:O(n),其中 n 是数组的长度。快指针和慢指针最多各移动 n 次。

空间复杂度:O(1),只需要使用常数的额外空间。

思路和算法三

分为快慢指针,我在此称为读写指针。

快指针(fast指针)即读指针,指向要与当前数组值比较的元素,主要负责寻找与当前元素不相等的元素。

慢指针(slow指针)即写指针,指向要被修改的元素。主要负责将找到的元素写入数组。

直接上图吧!可能看图比较清晰。

第一次遍历:比较nums[0]和后续数组元素fast指针和slow指针都指向nums[1]。因为fast指针现在指的值和nums[1]相等,所以fast指针向后移,一直向后移,直至移到fast指针指向的值不等于nums[0]。将fast指针指向的值赋给slow指针(如图2),slow指针向后移一位。

后续遍历:fast指针接着和fast-1指针比较,直到找到与前面不同的值,将fast指针值写入slow指针值。

第一次:(图片是力扣官方题解的,侵删)

后续遍历:

代码

class Solution {
    public int removeDuplicates(int[] nums) {
        int n = nums.length;
        if ( n == 0) {
            return 0;
        }
        int fast = 1, slow = 1;
        while (fast < n) {
            if (nums[fast] != nums[fast-1]) {
                nums[slow] = nums[fast];
                ++slow;
            }
            ++fast;
        }
        return slow;
    }
}
补充

你看懂代码了吗?如果看懂的话,可以试试0027题哦!!

他们的实现方式大同小异。

3、0027 移除元素


题目描述

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

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

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

说明:

为什么返回数值是整数,但输出的答案是数组呢?

请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。

你可以想象内部操作如下:

// nums 是以“引用”方式传递的。也就是说,不对实参作任何拷贝int len = removeElement(nums, val);

// 在函数里修改输入数组对于调用者是可见的。// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。for (int i = 0; i < len; i++) { print(nums[i]);}

示例 1:

输入:nums = [3,2,2,3], val = 3输出:2, nums = [2,2]解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。示例 2:

输入:nums = [0,1,2,2,3,0,4,2], val = 2输出:5, nums = [0,1,4,0,3]解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。

提示:

0 <= nums.length <= 1000 <= nums[i] <= 500 <= val <= 100

解题思路

指针法:
复杂度分析

时间复杂度:O(n),其中 n 是数组的长度。快指针和慢指针最多各移动 n 次。

空间复杂度:O(1),只需要使用常数的额外空间。

思路和算法四

和0026题很像!!!

分为左右指针,我在此称为读写指针。

右指针(right指针)即读指针,指向要与题目给的val比较的元素,主要负责寻找与val不相等的元素。

左指针(left指针)即写指针,指向要被修改的元素。主要负责将找到的元素写入数组。

代码
class Solution {
    public int removeElement(int[] nums, intval) {
        int n = nums.length;
        if (n == 0){
            return 0;
        }
        int left = 0, right = 0;
        while(right < n){
            if(nums[right] != val){
                nums[left] = nums[right];
                left++;
            }
            right ++;
        }
        return left;
    }
}

4、0035 搜索插入位置


题目描述

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

请必须使用时间复杂度为 O(log n) 的算法。

示例 1:

输入: nums = [1,3,5,6], target = 5输出: 2示例 2:

输入: nums = [1,3,5,6], target = 2输出: 1示例 3:

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

提示:

1 <= nums.length <= 104-104 <= nums[i] <= 104nums 为 无重复元素 的 升序 排列数组-104 <= target <= 104

解题思路

二分法:
复杂度分析

时间复杂度:O(logn),其中 n为数组的长度。二分查找所需的时间复杂度为 O(logn)。

空间复杂度:O(1)。我们只需要常数空间存放若干变量。

思路和算法五

如果该题目暴力解决的话需要 O(n)的时间复杂度,但是如果二分的话则可以降低到 O(logn) 的时间复杂度。

普通的二分查找比大小:先设定左侧下标 left 和右侧下标 right,再计算中间下标 mid。

每次根据 nums[mid] 和 target 之间的大小进行判断:

  • 相等则直接返回下标

  • nums[mid] < target 则 left 右移

  • nums[mid] > target 则 right 左移

此方法的话,要加上这个:查找结束如果没有相等值则返回 left,该值为插入位置。

代码
class Solution {
    public int searchInsert(int[] nums, int target) {
        int n = nums.length;
        int left = 0,right = n-1;
        while (left <= right) { //二分查找
            int mid =  (left+right) /2; //求中间指针值
            if(nums[mid] == target){ //相等返回下标
                return mid;
            }
            elseif(nums[mid] < target){ //小于,left指针往右移
                left = mid + 1;
            }
            else{ //大于,right指针往左移
                right = mid - 1;
            }
        }
        return left;//没有找到相等的值,返回最后一次查找的left指针
    }
}

5、0034 在排序数组中查找元素的第一个和最后一个位置


题目描述

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]。

你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

输入:nums = [5,7,7,8,8,10], target = 8输出:[3,4]示例 2:

输入:nums = [5,7,7,8,8,10], target = 6输出:[-1,-1]示例 3:

输入:nums = [], target = 0输出:[-1,-1]

提示:

0 <= nums.length <= 105-109 <= nums[i] <= 109nums 是一个非递减数组-109 <= target <= 109

解题思路

二分法:
复杂度分析

时间复杂度:O(logn),其中 n为数组的长度。二分查找所需的时间复杂度为 O(logn)。

空间复杂度:O(1)。我们只需要常数空间存放若干变量。

思路和算法六

我们可以分别求左边界和右边界,也可以二分求左边界之后接着遍历计数,两种情况对应在真实场景下连续相等的数据一般有多长。如果经常出现很长一串连续相等的数据,就用二分法求右边界,否则容易使算法退化到o(n)。

代码
class Solution {
    public int searchInsert(int[] nums, inttarget) {
        int n = nums.length;
        int left = 0,right = n-1;
        while (left <= right) { //二分查找
            int mid = (left+right) /2; //求中间指针值
            if(nums[mid] == target){ //相等返回下标
                return mid;
            }
            elseif(nums[mid] < target){ //小于,left指针往右移
                left = mid + 1;
            }
            else{ //大于,right指针往左移
                right = mid - 1;
            }
        }
        return left;//没有找到相等的值,返回最后一次查找的left指针
    }
}
指针法:
复杂度分析

时间复杂度:O(n),其中 n为数组的长度。

空间复杂度:O(1)。我们只需要常数空间存放若干变量。

思路和算法七

主要就是利用指针遍历,Id控制数组遍历,right=右边界,right-leftss+1=左边界

代码
class Solution {
    public int[] searchRange(int[] nums, inttarget) {
        int Id = 0,leftss = 0,right = 0;//Id控制数组遍历,right=右边界
        //因为左边界不能直接记录,所以用leftss记录元素个数,再用right-leftss+1计算左边界
        while(Id < nums.length){
            if(nums[Id] == target){
                right = Id;
                leftss++;
            }   
            Id++;
        }
        if(right != 0)
            return new int[]{right-leftss+1,right};
        return new int[]{-1, -1};
    }
}


总结

通过上述题目,学习到了指针法和二分法的原理及其应用范围。

指针法!!!!也算暴力法,时间复杂度 O(n)

真的非常通用,我之前学的时候只把它理解为快慢指针,也就是读写指针。但是用多了之后,

发现这两个指针的本质是一个控制数组下标++,方便遍历数组;另一个可以对数组元素进行任意操作,例如修改值,比较值等等。

所以,一切要求对数组遍历,同时对数组的元素进行操作的题目,都可以用指针法解决!

而且这个方法不仅作用于数组,也可以用于顺序表(其实两者很像了),如下图是我最近刷数据结构的题目写的代码。

二分法就没有太多想说的,时间复杂度O(logn)。

每次做的时候比较left mid right的值,进行相应操作,直到三者指向同一个元素停止。


欧克欧克~~ 码字完毕

leetcode新手总结,欢迎大家批评指正!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

听酱-

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值