134. 加油站
本题有点难度,不太好想,推荐大家熟悉一下方法二
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
int sum = 0;
int index = 0;
int star = 0;
int totalgas = 0;
int totalcost = 0;
for(int i = 0; i < gas.length; i++){
totalgas += gas[i];
totalcost += cost[i];
int diff = gas[i] - cost[i];
sum += diff;
if(sum < 0){
sum = 0;
star = i + 1;
}
}
if(totalgas < totalcost){
return -1;
}
return star;
}
}
总结
1.这个方法太巧妙了。核心思想就是计算油量-油耗,得到剩余油量。然后从头开始累加剩余油量,如果位置i出现sum为负数,说明前面随便哪个为起点都不能走到i的下一个。那么我们就以i+1为新的起点,把sum置为0。
2.这个方法就是把sum为负数的下一个坐标当作新的起点,也没有去统计把这个当作起点到底行不行。因为我们只要遵循上面的原则。把出现负数的下一个当作起点,那么就相当于在验证这个起点到底行不行,因为前面不行的起点,都被这个这个准则给淘汰了。
3.那sum为负数的下下个值就不能作为起点吗?答案是不行的,因为本题就只有1个答案,如果sum为负数的下一个值可以,说明后面的值都不行。如果sum为负数,更新为0后,加上下一个值还是负数,那就会再次更新起点。由于只有一个正确答案,那么这个答案一定是出现在负数的下一个。
4.然后如果出现总油量不够总的消耗量的话,那就返回-1。
5.总的来说,这道题坚持的原则就是出现sum为负数,说明[0, i]区间都不能作为起始位置,因为这个区间选择任何一个位置作为起点,到i这里都会断油,那么起始位置从i+1算起,再从0计算sum。
135. 分发糖果
本题涉及到一个思想,就是想处理好一边再处理另一边,不要两边想着一起兼顾,后面还会有题目用到这个思路
class Solution {
public int candy(int[] ratings) {
int[] result = new int[ratings.length];
Arrays.fill(result, 1);
//从前往后遍历,往右边看
for(int i = 0; i < ratings.length - 1; i++){
if(ratings[i+1] > ratings[i]){
result[i+1] = result[i] + 1;
}
}
//从后往前遍历,往左边看
for(int i = ratings.length - 1; i > 0; i--){
if(ratings[i-1] > ratings[i]){
result[i-1] = Math.max(result[i] + 1, result[i-1]);
}
}
return Arrays.stream(result).sum();
}
}
总结
1.这道题需要先确定一边,再确定另一边。如果同时考虑两边,就容易顾此失彼。具体来说就是,我们先从前往后遍历,这时候就向右边看,如果右边孩子比当前孩子高,那么右边孩子就在当前孩子的基础上+1。这样遍历一般后,我们就确保了往右边看,只要评分更高,那么糖果肯定就越多。然后我们再从后往前遍历一般,这时候往左边看,如果左边的孩子比当前孩子高,那么就在当前孩子的基础上+1。然后最后取得值应该是既要满足从前往后遍历,也要满足从后往前遍历,取一个最大值就可以了。
2.这样本题我采用了两次贪心的策略:
- 一次是从左到右遍历,只比较右边孩子评分比左边大的情况。(往右边看,确保只要右边孩子评分高,就有更多的糖果)
- 一次是从右到左遍历,只比较左边孩子评分比右边大的情况。(往左边看,确保只要左边孩子评分高,就有更多的糖果)
经过前后两次遍历,取最大值之后,就可以从局部最优推出了全局最优,即:相邻的孩子中,评分高的孩子获得更多的糖果。
3.这道题千万不能既往左边看,又往右边看,这样只会顾此失彼。我们分开来考虑,先往左边看,然后再往右边看。这样就不会出错。
4.注意for循环的边界。
860.柠檬水找零
本题看上好像挺难,其实很简单,大家先尝试自己做一做。
class Solution {
public boolean lemonadeChange(int[] bills) {
int five = 0;
int ten = 0;
int twenty = 0;
for(int i = 0; i < bills.length; i++){
if(bills[i] == 5){
five++;
}
if(bills[i] == 10){
ten++;
if(five > 0){
five--;
}else{
return false;
}
}
if(bills[i] == 20){
twenty++;
if(ten > 0 && five > 0){
ten--;
five--;
} else if(five >= 3){
five -= 3;
} else{
return false;
}
}
}
return true;
}
}
总结
1.这道题其实挺简单的,但一开始没有做出来主要是没有想明白该怎么表示收到5,或者找出5的数量呢?想的是把数组里面的元素删除的方法。但其实直接定义三个变量表示5元,10元,15元的数量就可以了。
2.这道题对billis数组遍历。无非就是三种情况,第一种是5,那么就直接收下,增加一张5元纸钞。第二种是10,那么就要消耗一张5元纸钞,增加一张10元纸钞。第三种是20,这时候就要用到贪心算法了,我们要先使用10+5的组合,因为5元更加灵活,如果前面一种不行,然后再判断 5x3的组合。
3.遇到感觉没有思路的题目,可以静下心来把能遇到的情况分析一下,只要分析到具体情况了,一下子就豁然开朗了。像这道题碰到5元和10元纸钞,策略是固定的。碰到20的,很自然想到要先把10元给花掉,因为5元花起来更加灵活。
406.根据身高重建队列
本题有点难度,和分发糖果类似,不要两头兼顾,处理好一边再处理另一边。
class Solution {
public int[][] reconstructQueue(int[][] people) {
Arrays.sort(people, (a,b) -> {
if(a[0] == b[0]){
return a[1] - b[1];
}
return b[0] - a[0]; //降序排列
});
List<int[]> que = new LinkedList<>();
for(int[] row : people){
que.add(row[1], row);
}
int[][] result = que.toArray(new int[people.length][]);
//int[][] result = que.toArray(new int[0][]);
return result;
}
}
总结
1.这道题还是先考虑一个维度,然后再考虑另一个维度。我们可以先按照升高按降序排列,然后再对排序后的数组进行遍历,按照k值直接作为下标add到集合就行。
2.难点有三个。一个是一定是要先考虑一个维度,那到底是先考虑身高还是k值,可以都试一下。本题是先考虑身高,然后通过K值来调整。另一个是怎么对二维数组进行排序,这里记住Arrays.sort()默认是升序的。然后就是一维数组和二维数组都是引用数据类型,可以直接使用Arrays.sort(T[] t, Comparator<? super T> c),不需要转为Stream处理。
3.第三个就是 集合的使用。一开始没做出来就是不知道怎么动态的调整元素的顺序,这里我们可以先使用LinkedList,因为LinkedList<int[]>
是基于 双向链表 实现的,适合插入、删除的操作,ArrayList<int[]>
是基于 动态数组 实现的,适合查询的操作。List集合插入操作不会覆盖元素,而是将元素插入指定位置,之后的元素会向后移动。在下标为 0
插入元素时,所有原本在 0
及之后的元素都会依次向后移动,确保它们不丢失。但在Map
中,如果你插入的 key
已经存在,那么该 key
对应的值会被新值覆盖。Map
中的 key
是唯一的,不允许多个相同的 key
存在。如果插入相同的 key
,则会更新(覆盖)其对应的 value
。
4.最后我们要把List集合转为数组。toArray()
:无参方法,将集合转换为 Object[]
数组。toArray(T[] array)
:带类型参数的方法,将集合转换为指定类型的数组,推荐使用这种方法。如果是一维数组:必须指定数组的长度,new int[5]
会创建一个长度为 5 的整数数组。如果是二维数组:可以只指定第一维的大小,例如 new int[5][]
,表示创建一个具有 5 行的二维数组,但列数可以动态分配。
5. list.toArray(new String[0]) ; list.toArray(new int[0][ ]) ; 为什么传入 new String[0]
?这是一个常见的技巧,因为它允许 toArray
根据集合的大小来创建合适大小的数组。传入一个零长度数组不会对性能产生影响,反而能确保返回的数组是正确的类型。