文章目录
- 380.【中等】O(1) 时间插入、删除和获取随机元素
- 238.【中等】除自身以外数组的乘积
- 134.【中等】 加油站
- 135.【困难】分发糖果
- 42.【困难】接雨水
🌈你好呀!我是 山顶风景独好
💝欢迎来到我的博客,很高兴能够在这里和您见面!
💝希望您在这里可以感受到一份轻松愉快的氛围!
💝不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。
🚀 欢迎一起踏上探险之旅,挖掘无限可能,共同成长!
380.【中等】O(1) 时间插入、删除和获取随机元素
实现RandomizedSet 类:
- RandomizedSet() 初始化 RandomizedSet 对象
- bool insert(int val) 当元素 val 不存在时,向集合中插入该项,并返回 true ;否则,返回 false 。
- bool remove(int val) 当元素 val 存在时,从集合中移除该项,并返回 true ;否则,返回 false 。
- int getRandom() 随机返回现有集合中的一项(测试用例保证调用此方法时集合中至少存在一个元素)。每个元素应该有 相同的概率 被返回。
你必须实现类的所有函数,并满足每个函数的 平均 时间复杂度为 O(1) 。
示例:
- 输入
[“RandomizedSet”, “insert”, “remove”, “insert”, “getRandom”, “remove”, “insert”, “getRandom”] [[], [1], [2], [2], [], [1], [2], []]- 输出
[null, true, false, true, 2, true, false, 2]- 解释
RandomizedSet randomizedSet = new RandomizedSet();
randomizedSet.insert(1); // 向集合中插入 1 。返回 true 表示 1 被成功地插入。
randomizedSet.remove(2); // 返回 false ,表示集合中不存在 2 。
randomizedSet.insert(2); // 向集合中插入 2 。返回 true 。集合现在包含 [1,2] 。
randomizedSet.getRandom(); // getRandom 应随机返回 1 或 2 。
randomizedSet.remove(1); // 从集合中移除 1 ,返回 true 。集合现在包含 [2] 。
randomizedSet.insert(2); // 2 已在集合中,所以返回 false 。
randomizedSet.getRandom(); // 由于 2 是集合中唯一的数字,getRandom 总是返回 2 。
class RandomizedSet {
static int[] nums = new int[200010];
Random random = new Random();
// 创建一个HashMap,用于存储元素值到其索引的映射关系,以便快速查找和删除
Map<Integer, Integer> map = new HashMap<>();
// 定义一个索引变量,用于追踪nums数组中下一个要插入的位置
int idx = -1;
// 插入一个元素到集合中
public boolean insert(int val) {
// 如果map中已经包含了这个值,说明集合中已经存在这个元素,所以插入失败
if (map.containsKey(val)) return false;
// 在nums数组的下一个位置插入元素的值
nums[++idx] = val;
// 更新map,将新插入的元素值映射到其索引位置
map.put(val, idx);
// 插入成功,返回true
return true;
}
// 从集合中删除一个元素
public boolean remove(int val) {
// 如果map中不存在这个值,说明集合中没有这个元素,所以删除失败
if (!map.containsKey(val)) return false;
// 从map中获取要删除元素的索引
int loc = map.remove(val);
// 如果要删除的元素不是nums数组的最后一个元素(即idx指向的元素)
// 那么我们需要将nums数组的最后一个元素移到被删除元素的位置,并更新其索引映射
if (loc != idx) map.put(nums[idx], loc);
// 将被删除元素的位置用nums数组的最后一个元素的值覆盖
// 然后将idx减1,因为我们已经从集合中移除了一个元素
nums[loc] = nums[idx--];
// 删除成功,返回true
return true;
}
// 从集合中随机获取一个元素
public int getRandom() {
// 使用Random对象的nextInt方法生成一个介于0(包括)和idx+1(不包括)之间的随机数
// 这个随机数就是nums数组中某个元素的索引
// 然后返回这个索引对应的元素值
return nums[random.nextInt(idx + 1)];
}
}
238.【中等】除自身以外数组的乘积
- 给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。
- 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。
- 请不要使用除法,且在 O(n) 时间复杂度内完成此题。
示例 :
- 输入: nums = [1,2,3,4]
- 输出: [24,12,8,6]
class Solution {
//假如nums为[1,2,3,4],那么answer的值分别为[(2,3,4),(1,3,4),(1,2,4),(1,2,3)]
//如果吧i当前值相乘的时候看做是1那么就有如下样式
// 1, 2, 3, 4
// 1, 1, 3, 4
// 1, 2, 1, 4
// 1, 2, 3, 1
// 他的对角线1将他们分割成了两个三角形,对于answer的元素,
//我们可以先计算一个三角形每行的乘积,然后再去计算另外一个三角形每行的乘积,
//然后各行相乘,就是answer每个对应的元素
public int[] productExceptSelf(int[] nums) {
int length = nums.length;
//先初始化一个answer数组,但是很多解答都没说明的是这个answer数组,
//并不是以此计算就得出的结果,而是两次乘积之后的结果
int[] answer = new int[length];
//初始化一个初始值,作为三角乘积计算的开始
answer[0] = 1;
//先计算左边三角的乘积
for(int i = 1; i < length; i++){
answer[i] = answer[i-1] * nums[i-1];
}
//再次计算右边三角形,为什么是length-2呢?
//length-1是最后一个值的索引,但是最后一个值temp[length-1] = 1,
//也是对应对角线上的1,所以不在进行相乘处理
//temp的作用是计算右边三角形的乘积的累计值,然后再和answer相乘,
//注意!!!:不能直接nums[i+1]相乘那会在计算右三角的时候变成每行乘积与nums[i+1]的错误答案
int temp = 1;
for(int i = length - 2; i >= 0; i--){
//先将每行乘积赋予一个中间值
temp *= nums[i+1];
answer[i] *= temp;
}
return answer;
}
}
134.【中等】 加油站
- 在一条环路上有 n 个加油站,其中第 i 个加油站有汽油 gas[i] 升。
- 你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。
- 给定两个整数数组 gas 和 cost ,如果你可以按顺序绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1 。如果存在解,则 保证 它是 唯一 的。
示例 :
- 输入: gas = [1,2,3,4,5], cost = [3,4,5,1,2]
- 输出: 3
- 解释:
从 3 号加油站(索引为 3 处)出发,可获得 4 升汽油。此时油箱有 = 0 + 4 = 4 升汽油
开往 4 号加油站,此时油箱有 4 - 1 + 5 = 8 升汽油
开往 0 号加油站,此时油箱有 8 - 2 + 1 = 7 升汽油
开往 1 号加油站,此时油箱有 7 - 3 + 2 = 6 升汽油
开往 2 号加油站,此时油箱有 6 - 4 + 3 = 5 升汽油
开往 3 号加油站,你需要消耗 5 升汽油,正好足够你返回到 3 号加油站。
因此,3 可为起始索引。
//亏空最严重的一个点必须放在最后一步走,等着前面剩余的救助
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
// 获取加油站数组的长度
int len = gas.length;
// spare 变量用于记录从起点开始到当前位置为止的剩余油量
int spare = 0;
// minSpare 变量用于记录遍历过程中剩余油量的最小值(即某个位置能到达的最小剩余油量)
int minSpare = Integer.MAX_VALUE;
// minIndex 变量用于记录minSpare发生时的索引位置,即可能的起点
int minIndex = 0;
// 遍历每个加油站
for (int i = 0; i < len; i++) {
// 累加当前加油站的油量并减去当前位置汽车所需的油量
spare += gas[i] - cost[i];
// 如果当前位置的剩余油量小于之前记录的最小剩余油量
if (spare < minSpare) {
// 更新最小剩余油量和对应的索引位置
minSpare = spare;
minIndex = i;
}
}
// 如果遍历结束后总的剩余油量大于0,说明从任意位置出发都能完成一圈
// 这里之前的逻辑有误,应该检查spare而不是minSpare
if (spare > 0) return 0; // 0 表示从任意位置出发都能完成一圈
// 如果总的剩余油量小于0,则需要判断是否存在某个位置作为起点能完成一圈
// 如果存在某个位置minIndex,使得从该位置出发到末尾的剩余油量(spare - minSpare)大于等于0
// 那么从minIndex+1(因为索引从0开始,题目可能要求从1开始计数)开始就能完成一圈
// 否则无法完成一圈,返回-1
return spare < 0 ? -1 : (minIndex + 1) % len;
}
}
135.【困难】分发糖果
- n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。
- 你需要按照以下要求,给这些孩子分发糖果:
- 每个孩子至少分配到 1 个糖果。
相邻两个孩子评分更高的孩子会获得更多的糖果。
请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目 。
示例 1:
- 输入:ratings = [1,0,2]
- 输出:5
- 解释:你可以分别给第一个、第二个、第三个孩子分发 2、1、2 颗糖果。
示例 2:
- 输入:ratings = [1,2,2]
- 输出:4
- 解释:你可以分别给第一个、第二个、第三个孩子分发 1、2、1 颗糖果。
第三个孩子只得到 1 颗糖果,这满足题面中的两个条件。
解题思路:
- 规则定义: 设学生 A 和学生 B左右相邻,A 在 B 左边;
- 左规则: 当 B>A时,B的糖比A的糖数量多。
- 右规则: 当 A>B时,A 的糖比B的糖数量多。
相邻的学生中,评分高的学生必须获得更多的糖果 等价于 所有学生满足左规则且满足右规则。
class Solution {
public int candy(int[] ratings) {
// 创建一个数组left,用于记录从左到右看,每个孩子应该得到的糖果数(初始化为1)
int[] left = new int[ratings.length];
Arrays.fill(left, 1);
// 创建一个数组right,用于记录从右到左看,每个孩子应该得到的糖果数(初始化为1)
int[] right = new int[ratings.length];
Arrays.fill(right, 1);
// 从左到右遍历ratings数组,如果当前孩子的评分高于前一个孩子的评分,则当前孩子得到的糖果数应该是前一个孩子的糖果数加1
for(int i = 1; i < ratings.length; i++)
if(ratings[i] > ratings[i - 1]) left[i] = left[i - 1] + 1;
// 初始化count,从left数组的最后一个元素开始(因为left数组已经填充完毕)
int count = left[ratings.length - 1];
// 从右到左遍历ratings数组,如果当前孩子的评分高于后一个孩子的评分,则当前孩子得到的糖果数应该是后一个孩子的糖果数加1
// 同时,将当前位置左右两边得到的糖果数中较大的那个累加到count中
for(int i = ratings.length - 2; i >= 0; i--) {
if(ratings[i] > ratings[i + 1]) right[i] = right[i + 1] + 1;
count += Math.max(left[i], right[i]); // 累加较大的糖果数
}
// 返回总共需要分配的糖果数
return count;
}
}
42.【困难】接雨水
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
示例 1:
- 输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
- 输出:6
- 解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
示例 2:
- 输入:height = [4,2,0,3,2,5]
- 输出:9
解题思路:
class Solution {
public int trap(int[] height) {
// 获取数组长度
int n = height.length;
// 如果数组为空,则无法积水,直接返回0
if (n == 0) {
return 0;
}
// 创建一个数组leftMax来存储从左到右扫描时每个位置左侧的最大高度
int[] leftMax = new int[n];
// 初始化第一个位置的最大高度为其自身
leftMax[0] = height[0];
// 从左到右遍历数组,更新leftMax数组
for (int i = 1; i < n; ++i) {
leftMax[i] = Math.max(leftMax[i - 1], height[i]);
}
// 创建一个数组rightMax来存储从右到左扫描时每个位置右侧的最大高度
int[] rightMax = new int[n];
// 初始化最后一个位置的最大高度为其自身
rightMax[n - 1] = height[n - 1];
// 从右到左遍历数组,更新rightMax数组
for (int i = n - 2; i >= 0; --i) {
rightMax[i] = Math.max(rightMax[i + 1], height[i]);
}
// 初始化答案变量,用于存储可以积水的总水量
int ans = 0;
// 遍历数组的每个位置,计算当前位置可以积水的量,并累加到答案中
for (int i = 0; i < n; ++i) {
// 当前位置可以积水的量等于左侧最大高度和右侧最大高度中较小的一个减去当前位置的高度
ans += Math.min(leftMax[i], rightMax[i]) - height[i];
}
// 返回总水量
return ans;
}
}