周一-周五刷题打卡
4.25-周一
1005.K次取反后最大化数组和
思路分析
- 对某一个点进行取反操作,那么我们需要注意:同一个位置取反,k为奇数,才起到效果,k为偶数时,相当于没有取反
- 需要求得最大和,如果有负数,那么可以将负数取反,达到sum变大得效果。如果是正数,那么考虑取反最小的正数
代码
class Solution {
public int largestSumAfterKNegations(int[] nums, int k) {
Arrays.sort(nums);
//遍历数组,将负数全部取反
int m = 0;
for(int i = 0; i < nums.length; i++){
if(nums[i] < 0 && k > 0){
nums[i] = -nums[i];
m++;
k--;
}
}
//k > 0 需要将剩下数组中正数取反了
if(k > 0){
if(k % 2 == 1){
//表明k为奇数,反转奇数次才会为负数
//因为之前数组排序过,所以此时数值最小的非负数是num[m]或者num[m - 1]
if(m == 0){
nums[m] = -nums[m];
}else if(m < nums.length){
if(nums[m] > nums[m - 1]) nums[m - 1] = -nums[m - 1];
else nums[m] = -nums[m];
}else if(m == nums.length){
nums[m-1] = -nums[m - 1];
}
}
}
int sum = 0;
for(int j = 0; j < nums.length; j++){
sum += nums[j];
}
return sum;
}
}
134.加油站
思路分析
-
首先我们已知,当途径全程可以获得的gas数值和如果大于我们全程消耗的cost数值和,这个时候是可以形成环路的。这个挺生活常识的,就是够用,因为我们不知道从哪开始出发嘛
-
关键点就在于寻找从哪个站出发了,实际上从这个站出发,就是为了填补前面的不够的油量。比如三站
gas【4,2,3】 cost【1,1,10】。从前面两站出发,明显是油量不够的。从第三站出发10-3,还剩7,然后7-4还剩3,就算不补充油量,也足够了。
-
实际上核心就是,在什么地方出发,我们可以填补前面的油量不够
代码
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
int index = 0;
int spare = 0;
int minSpare = 100000;
for(int i = 0; i < gas.length; i++){
spare += gas[i];
spare -= cost[i];
if(spare < minSpare){
minSpare = spare;
index = i;
}
}
return spare < 0 ? -1 : (index + 1) % (gas.length);
}
}
4.26-周二
135.分发糖果
思路分析
- 左右遍历模拟值。首先想到的就是每一个同学都发一颗糖。
- 按照题目描述,相邻孩子评分高的会获得更多的糖果,那么从左向右遍历,如果右边的同学比左边的同学评分多,那么就加一颗糖。这一点我们很容易想到,但是还远远不够
- 如果我们的孩子是【2,2,1】,首先每个孩子都可以拿一颗糖,但是都不满足右边的孩子比左边的孩子评分高,答案是不是3呢,显然不是,因为第二个孩子和第三个孩子都为一颗糖就不满足题意了
- 所以我们不仅仅要左遍历一遍,还需要右遍历一遍,然后取最大值,这样的话就能满足条件了
代码
class Solution {
public int candy(int[] ratings) {
int [] left = new int[ratings.length];
int [] right = new int[ratings.length];
Arrays.fill(left, 1);
Arrays.fill(right, 1);
for(int i = 1; i < ratings.length; i++){
if(ratings[i] > ratings[i - 1]){
left[i] = left[i - 1] + 1;
}
}
int count = 0;
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]);
}
//right数组开始的范围是[0,ratings.length - 2];在这次比较重,我们没有加上left的最后一个元素,也就是ratings.length - 1的位置
count += left[ratings.length - 1];
return count;
}
}
860.柠檬水找零
思路分析
- 非常舒服的简单题
- 贪心思想:能不用五元就不用五元,因为五元很通用,简单模拟交易钱币过程就好
代码
class Solution {
public boolean lemonadeChange(int[] bills) {
int ten = 0;
int five = 0;
for(int i = 0; i < bills.length; i++){
if(bills[i] == 5){
five++;
}else if(bills[i] == 10){
five--;
ten++;
}else{
if(ten > 0){
ten--;
five--;
}else{
five = five - 3;
}
}
if(five < 0 || ten < 0){
return false;
}
}
return true;
}
}
4.27-周三
406.根据身高重建队列
思路分析
- 按照题意来说,其实就是按照身高排序,但是也不完全按照身高排序,真正的排序因子,还是people[i][1]
- 那么首先我们按照升高排序,然后再通过people[i][1]来调整顺序就好了
- 调整顺序的操作通过值得大小顺序插入就行
代码
class Solution {
public int[][] reconstructQueue(int[][] people) {
Arrays.sort(people, new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
if(o1[0] != o2[0]){
//降序
return o2[0] - o1[0];
}else{
//升序
return o1[1] - o2[1];
}
}
});
List<int[]> arr = new ArrayList<>();
for(int i = 0; i < people.length; i++){
if(arr.size() > people[i][1]){
arr.add(people[i][1],people[i]);
}else{
arr.add(arr.size(),people[i]);
}
}
return arr.toArray(new int[arr.size()][]);
}
}
452.用最少数量的箭引爆气球
思路分析
- 很容易想到排序
画个图:
- 题目要求使用最少的箭,贪心:思考什么时候我们才不得已需要加一支箭
- 已知第一根箭是【1,6】,只要后面的气球范围第一个数如果是小于6,那么就说明这可以和【1,6】用于一根箭,那么就是说,只有气球范围【start,end】中,start大于6,才需要加一根箭
- 也就是【7,12】需要一根箭,此时的问题就在后续的气球是否可以和【7,12】共有箭呢?那么将箭的范围更新至12处
代码
class Solution {
public int findMinArrowShots(int[][] points) {
if(points.length == 0){
return 0;
}
Arrays.sort(points, new Comparator<int[]>(){
public int compare(int[] point1, int[] point2){
if(point1[1] > point2[1]){
return 1;
}else if(point1[1] < point2[1]){
return -1;
}else{
return 0;
}
}
});
int pos = points[0][1];
int sum = 1;
for(int[] ball : points){
if(ball[0] > pos){
pos = ball[1];
sum++;
}
}
return sum;
}
}
4.28-周四
435.无重叠区间
思路分析
-
这题和昨天的射击气球还是有一点相似的
-
依旧是很容易想到了先排序
画个图:
-
贪心:思考什么情况下,才需要移除一个区间,达到没有重复区间
-
和射气球原理一样,当发现重复区间的时候将count++,表示需要删除这个区间,那么删除区间之后需要更新区间的临界值【pos】,删除的个数要保障最少,那么我们取那个占用区间多的,比如【1,3】如果我们删除它就只用删除一个,但是如果删除【1,2】,那么我们后续【2,3】和【1,3】还是重合,这样就需要删除两个了。那么更新区间临界值pos的时候,采用
pos = Math.min(intervals[i][1], pos);
-
当没有重复区间的时候,更新pos,只需要按照范围移动就好了
pos = intervals[i][1];
代码
class Solution {
public int eraseOverlapIntervals(int[][] intervals) {
if(intervals.length == 0){
return 0;
}
Arrays.sort(intervals, new Comparator<int[]>(){
public int compare(int[] intervals1, int[] intervals2){
if(intervals1[1] > intervals2[1]){
return 1;
}else if(intervals1[1] < intervals2[1]){
return -1;
}else{
return 0;
}
}
});
int pos = intervals[0][1];
int count = 0;
for(int i = 1; i < intervals.length; i++){
if(intervals[i][0] < pos){
count++;
pos = Math.min(intervals[i][1], pos);
}else{
pos = intervals[i][1];
}
}
return count;
}
}
4.29-周五
763.划分字母区间
思路分析
- 使用一个额外数组来存储每一个字母的出现的最大下标值。
- 比如【ababa】 —> a出现的最大下标是4,b是3,那么对应ans就是这样的映射关系。
- 然后遍历由s转化的char数组,当达到某个字母的最大下标的时候,就更新分割点
代码
class Solution {
public List<Integer> res = new ArrayList<>();
public List<Integer> partitionLabels(String s) {
int [] ans = new int[26];
char[] chars = s.toCharArray();
for(int i = 0; i < chars.length; i++){
ans[chars[i] - 'a'] = i;
}
int index = 0;
int last = 0;
for(int i = 0; i < chars.length; i++){
index = Math.max(index, ans[chars[i] - 'a']);
if(i == index){
res.add(i - last + 1);//下标相减,求字母个数,所以+1
last = i + 1;//更新下标
}
}
return res;
}
}