LeetCode 热题 100 题解:普通数组部分

devtools/2024/9/22 17:10:30/

文章目录

  • 题目一:最大子数组和(No. 53)
    • 题解
  • 题目二:合并区间(No. 56)
    • 题解
  • 题目三:轮转数组(No. 189)
    • 题解
  • 题目四:除自身以外数组的乘积(No. 238)
    • 题解
  • 题目五:缺失的第一个正数(No. 41)
    • 题解

题目一:最大子数组和(No. 53)

题目链接:https://leetcode.cn/problems/maximum-subarray/description/?envType=study-plan-v2&envId=top-100-liked

题目难度:中等


给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

**子数组:**是数组中的一个连续部分。

示例 1:

输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。

示例 2:

输入:nums = [1]
输出:1

示例 3:

输入:nums = [5,4,-1,7,8]
输出:23

提示:

  • 1 <= nums.length <= 105
  • 104 <= nums[i] <= 104

题解

**前缀和:**假设有一个数组 [a1, a2, a3, a4, a5, a6]

  • 前缀和 prefix[1] 为 a1
  • 前缀和 prefix[2] 为 a1 + a2
  • 前缀和 prefix[3] 为 a1 + a2 + a3
  • 前缀和 prefix[4] 为 a1 + a2 + a3 + a4
  • 前缀和 prefix[5] 为 a1 + a2 + a3 + a4 + a5
  • 前缀和 prefix[6] 为 a1 + a2 + a3 + a4 + a5 + a6

通过前缀和,**可以很轻松的去求得一个连续子数组的和:**比如 a3 到 a6 到数组和就可以通过 prefix[6] - prefix[3] 来计算得出。

以 an(1 < n < prefix.length) 结尾的子数组的和最大的时候,就是 prefix[n] 减去 前面出现过的最小的前缀和 或者 就是这个前缀和本身,将这两个结果取一个最大值,就是以这个节点结尾的子数组最大的和。

通过上面的分析, 本题需要这几个变量:一个 prefix 数组、存储结果的 res,存储此时最小的前缀和的 minPre;当遍历到一个节点的时候,比较以下的三个变量:

  • res、prefix[i]、prefix[i] - minPre

当遍历完成后,就能得到最终的结果。

class Solution {public int maxSubArray(int[] nums) {int temp = 0;int[] prefix = new int[nums.length];for (int i = 0; i < nums.length; i++) {temp += nums[i];prefix[i] = temp;}int minPre = prefix[0];int res = prefix[0];for (int i = 1; i < prefix.length; i++) {res = Math.max(prefix[i] - minPre, res);res = Math.max(prefix[i], res);minPre = Math.min(minPre, prefix[i]);}return res;}
}

题目二:合并区间(No. 56)

题目链接:https://leetcode.cn/problems/merge-intervals/?envType=study-plan-v2&envId=top-100-liked

题目难度:中等


以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间

示例 1:

输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].

示例 2:

输入:intervals = [[1,4],[4,5]]
输出:[[1,5]]
解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。

提示:

  • 1 <= intervals.length <= 104
  • intervals[i].length == 2
  • 0 <= starti <= endi <= 104

题解

一个区间由左右区间来限定,即 [left, right],要判断两个区间能否合并需要两个条件

  • left1 ≤ left2
  • right1 ≥ left2

但是这样的判定方式显然不适合解题,因为要查找一个区间能和几个区间合并的时候,每次都需要去遍历整个数组。

但如果按照 left 的大小来进行排序,就可以减少一个判断的条件,经过排序后,前后两个区间一定满足 left1 ≤ left 所以只需要去判断右边界即可

  • 当右边界大于下一个区间的左边界的时候,这两个区间就可以合并了。
  • 但如果发现右边界小于下一个区间的左边界,则区间就无法继续合并,就需要开辟一个新的区间来继续执行合并的流程。

通过上面的分析,本题需要这些变量:目前遍历区间的左边界 currentLeft,有边界 currentRight,存储结果的集合 resList。

本题的思路遍历数组,寻找下一个区间能否和本次的区间 [currentLeft, currentRight] 合并,如果可以,就更新 currentRight,否则将当前区间添加到结果数组中,然后继续遍历。

class Solution {public int[][] merge(int[][] intervals) {if (intervals.length <= 1) return intervals;// 收集结果的集合List<int[]> resList = new ArrayList<>(); // 按照起点的顺序排序Arrays.sort(intervals, (x, y) -> Integer.compare(x[0], y[0]));int currentLeft = intervals[0][0];int currentRight = intervals[0][1];for (int i = 1; i < intervals.length; i++) {if (intervals[i][0] > currentRight) {// 前一个范围无法覆盖到这个内容resList.add(new int[]{currentLeft, currentRight});currentLeft = intervals[i][0];currentRight = intervals[i][1];} else {// 可以覆盖的情况currentRight = Math.max(intervals[i][1], currentRight) ;}}// 最后一个区间没有添加到结果中,需要额外添加一次resList.add(new int[]{currentLeft, currentRight});return resList.toArray(new int[resList.size()][]);}
}

题目三:轮转数组(No. 189)

题目链接:https://leetcode.cn/problems/rotate-array/description/?envType=study-plan-v2&envId=top-100-liked

题目难度:中等


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

示例 1:

输入: nums = [1,2,3,4,5,6,7], k = 3
输出:[5,6,7,1,2,3,4]解释:
向右轮转 1 步:[7,1,2,3,4,5,6]
向右轮转 2 步:[6,7,1,2,3,4,5]
向右轮转 3 步:[5,6,7,1,2,3,4]

示例 2:

输入:nums = [-1,-100,3,99], k = 2
输出:[3,99,-1,-100]
解释:
向右轮转 1 步: [99,-1,-100,3]
向右轮转 2 步: [3,99,-1,-100]

提示:

  • 1 <= nums.length <= 105
  • 231 <= nums[i] <= 231 - 1
  • 0 <= k <= 105

题解

设数组的长度为 l,那轮转 l 次,得到的结果和原数组是完全相同的,假如轮转 k 次,其有效的轮转次数其实就是 k 对 l 取余数,也就是 k % l 次。

当轮转 m 次后,最终得到的结果就是数组的 后 m 个 元素到了新数组的开头,而 0 到这个位置的元素向后移动 m 位。

在这里插入图片描述

那第一个思路就很简单了,首先创造一个新的数组,然后对于原数组先从 length - m 遍历到末尾,然后再遍历 0 到 length - m - 1 这部分的内容,将这些内容填充到新的数组中。

再将新数组中的内容转移到旧数组中即可。

class Solution {public void rotate(int[] nums, int k) {int l = nums.length;int realK = k % l; // 移动 l 的整数倍不会产生效果int[] res = new int[l];int newStart = nums.length - realK;int temp = 0;for (int i = newStart; i < nums.length; i++) res[temp++] = nums[i];for (int i = 0; i < newStart; i++) res[temp++] = nums[i];for (int i = 0; i < res.length; i++) nums[i] = res[i];}
}

第二种思路就是反转数组,比如数组 1 2 3 4 5 6 7,要轮转 3 位置,那最终需要得到的数字就是这样的:5 6 7 1 2 3 4

要达到这样的效果,只需要先将数组翻转:7 6 5 4 3 2 1

然后翻转前 k 位:5 6 7 4 3 2 1

然后翻转后面的部分:5 6 7 1 2 3 4

只需要经过三次翻转就可以得到最终的结果。

class Solution {public void rotate(int[] nums, int k) {int realK = k % nums.length;reverse(nums, 0, nums.length - 1);reverse(nums, 0, realK - 1);reverse(nums, realK, 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++;end--;}}
}

题目四:除自身以外数组的乘积(No. 238)

题目链接:https://leetcode.cn/problems/product-of-array-except-self/description/?envType=study-plan-v2&envId=top-100-liked

题目难度:中等


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

题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。

请 **不要使用除法,**且在 O(*n*) 时间复杂度内完成此题。

示例 1:

输入: nums =[1,2,3,4]输出:[24,12,8,6]

示例 2:

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

提示:

  • 2 <= nums.length <= 105
  • 30 <= nums[i] <= 30
  • 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内

题解

如果不考虑 0 的情况下,本题的思路其实是比较容易的,就是先计算出整个数组的乘积,然后遍历到数组中的某个元素的时候,用这个乘积去除以这个元素,得到的结果放到 answer 数组中。

但是当出现 0 的情况就需要进行特殊的处理了,出现 0 的情况也分为两种情况

  • 仅有一个 0 的情况
  • 出现了两个及以上的 0 的情况

仅有一个零的情况也比较容易处理,比如题目中的案例二:

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

其实规律也比较明显,为 0 的位置上的存储的是整个数组的乘积(除零以外),其他位置存储的全为 0。

当时当出现两个或者以上 0 的时候,结果数组中就全为 0 了。

所以本题的思路就是:先去遍历数组求出乘积(除零以外),同时统计 0 的个数,对没有 0,有一个 0 和有多个 0 的情况分别进行处理。

class Solution {public int[] productExceptSelf(int[] nums) {int zeroNum = 0;int mulNum = 1;int zeroIndex = 0; // 为 0 的位置的下标int[] res = new int[nums.length];for (int i = 0; i < nums.length; i++) {if (nums[i] == 0) {zeroNum++;zeroIndex = i;} else mulNum *= nums[i]; // 计算乘积}if (zeroNum > 1) return res;else {if (zeroNum == 1) {res[zeroIndex] = mulNum;return res;} else {for (int i = 0; i < nums.length; i++) res[i] = mulNum / nums[i];}}return res;}
}

题目五:缺失的第一个正数(No. 41)

题目链接:https://leetcode.cn/problems/first-missing-positive/description/?envType=study-plan-v2&envId=top-100-liked

题目难度:困难


给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。

请你实现时间复杂度为

O(n)

并且只使用常数级别额外空间的解决方案。

示例 1:

输入:nums = [1,2,0]
输出:3
解释:范围 [1,2] 中的数字都在数组中。

示例 2:

输入:nums = [3,4,-1,1]
输出:2
解释:1 在数组中,但 2 没有。

示例 3:

输入:nums = [7,8,9,11,12]
输出:1
解释:最小的正数 1 没有出现。

提示:

  • 1 <= nums.length <= 105
  • 231 <= nums[i] <= 231 - 1

题解

本题的基础解法还是比较好想的,先将数组进行排列;定义一个 temp 变量,从 1 开始自增,遍历数组到正数部分的时候,就去判断数字和 temp 是否相等,不想等就直接返回 temp。

其中会有一个极端的情况,假如数组的长度为 N,而数组中的元素恰好就是 1 ~ N 中的所有元素,那此时最小的正数就是 N + 1;代码中 temp 是一直自增到结束的,所以当遍历结束后,如果还没有返回的话,此时 temp 存储的就是 N + 1 的值。

class Solution {public int firstMissingPositive(int[] nums) {Arrays.sort(nums); // 排序int temp = 1;// 对下标为 0 的情况进行特殊处理if (nums[0] != temp && nums[0] > 0) return temp;else if (nums[0] == temp) temp++; for (int i = 1; i < nums.length; i++) {if (nums[i] > 0) {if (nums[i] == nums[i - 1]) continue; // 避免重复元素的干扰if (temp != nums[i]) return temp; // 得到结果else temp++;}}return temp;}
}

对于一个长度为 N 的数组,其所未包含的最小的正数,最大就是 N + 1,也就是上面提到的特殊情况,结果的范围其实就是 1 ~ N + 1;第二种方法就是对原数组进行操作,将每个元素在结果范围内的元素放到其对应位置上,然后再次遍历数组,找到位置不对的元素。

比如对案例 2 中的元素来说:[3, 4, -1, 1]

按照对应的位置排序完成是这样的:[1, -1, 3, 4]

处于 nums[i] 上的元素就是 i + 1,再次遍历发现不符合这种情况的就是最小的那个正数。

这个思路非常简单,就是对范围内的元素放到对应的位置上,然后再次遍历,但实现起来还是有很多地方需要注意的。

来梳理一下交换的条件:

  1. 首先 i 要在范围内,也就是 1 ≤ i ≤ nums.length
  2. 这个位置的元素要交换到 nums[nums[i] - 1] 的位置上去,要保证这个位置的元素不与 nums[i] 相等,如果相等就无需交换(其实还有避免死循环的因素,这个后面再说)。

写出来条件就是这样的:

nums[i] >= 1 && nums[i] <= nums.length && nums[i] != nums[nums[i] - 1]

那这个条件前面跟的是什么语句,if 还是 while 呢?

如果我们使用 if,交换了一个新的元素过来,此时去继续遍历下一个节点,那这个交换过来的新的元素是有效的元素的话,那这个元素就有可能未被处理到

比如案例 2,模拟一下使用 if 的情况:

原数组:[3, 4, -1, 1]

  • 第一次交换:[-1, 4, 3, 1]
  • 第二次交换:[-1, 1, 3, 4]

遍历结束,这里的问题就出现在第二次交换上,这里的 1 未被处理就直接放在了错误的位置上。

为了避免这样的情况,上面应该使用 while 来限制。

class Solution {public int firstMissingPositive(int[] nums) {for (int i = 0; i < nums.length; i++) {// 排列数组while (nums[i] >= 1 && nums[i] <= nums.length && nums[i] != nums[nums[i] - 1]) {swap (nums, i, nums[i] - 1);}} for (int i = 0; i < nums.length; i++) {// 检测哪个位置是错误的if (nums[i] != i + 1) return i + 1;}return nums.length + 1; // 如果都没问题,说明是特殊情况}// 交换节点的方法void swap(int[] nums, int index1, int index2) {int temp = nums[index1];nums[index1] = nums[index2];nums[index2] = temp;}
}

http://www.ppmy.cn/devtools/7848.html

相关文章

第24天:安全开发-PHP应用文件管理模块显示上传黑白名单类型过滤访问控制

第二十四天 一、PHP文件管理-显示&上传功能实现 如果被抓包抓到数据包&#xff0c;并修改Content-Type内容 则也可以绕过筛查 正常进行上传和下载 二、文件上传-$_FILES&过滤机制实现 无过滤机制 黑名单过滤机制 使用 explode 函数通过点号分割文件名&#xff0c;…

0 回归-海上风电出力预测

https://www.dcic-china.com/competitions/10098 分析一下&#xff1a;特征工程如何做。 时间特征&#xff1a; 小时、分钟、一个星期中的第几天、一个月中的第几天。这些可以作为周期特征的标识。比如周六周日的人流会有很大的波动&#xff0c;这些如果不告诉模型它是很难学习…

变频器基础原理

文章目录 0. 基本知识1.三相的电压之和为02.正弦交流相量的相量表示法(相量只是表示正弦量&#xff0c;而不等于正弦量 &#xff1b;只有正弦量才能用相量表示)引入相量表示法目的:一种正弦量的产生方式:正弦量的相量表示&#xff0c;使用欧拉公式表示复数 3.用复数表示正弦量&…

设计模式(022)行为型之解释器模式

解释器模式是一种行为型设计模式&#xff0c;用于定义一种语言的文法&#xff0c;并且在该语言中解释句子的意义。这种模式通常用于实现编程语言解释器、正则表达式引擎等场景。 在解释器模式中&#xff0c;主要有以下几个角色&#xff1a;① 抽象表达式&#xff08;AbstractEx…

基础知识集合

https://blog.csdn.net/sheng_q/category_10901984.html?spm1001.2014.3001.5482 字节流&#xff1a;面向字节的io流&#xff0c;音频 图片 歌曲 byteArray/stringbuffer/file/piped/sequence /filter/data/buffer缓冲/lineNumber/pushedbackInputStream byte/file/piped/…

数据结构之双链表的相关知识点及应用

找往期文章包括但不限于本期文章中不懂的知识点&#xff1a; 个人主页&#xff1a;我要学编程(ಥ_ಥ)-CSDN博客 所属专栏&#xff1a;数据结构 目录 双链表的实现 初始化双链表 在双链表中尾插数据 在双链表中尾删数据 在双链表中头插数据 在双链表中头删数据 在双…

能源成果3D网络三维展厅越发主流化

在这个数字化飞速发展的时代&#xff0c;我们为您带来了全新的展览形式——线上3D虚拟展厅。借助VR虚拟现实制作和web3d开发技术&#xff0c;我们能够将物品、图片、视频和图文信息等完美融合&#xff0c;通过计算机技术和3D建模&#xff0c;为您呈现一个逼真、生动的数字化展览…

Dynamic Wallpaper for Mac激活版:视频动态壁纸软件

Dynamic Wallpaper for Mac 是一款为Mac电脑量身打造的视频动态壁纸应用&#xff0c;为您的桌面带来无限生机和创意。这款应用提供了丰富多样的视频壁纸选择&#xff0c;涵盖了自然风景、抽象艺术、科幻奇观等多种主题&#xff0c;让您的桌面成为一幅活生生的艺术画作。 Dynami…