455.分发饼干
假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。
对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值
示例 1:
输入: g = [1,2,3], s = [1,1]
输出: 1
解释:
你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。
虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。
所以你应该输出1。
public int findContentChildren(int[] g, int[] s) {
// g 孩子们的胃口值 s 饼干重量
Arrays.sort(g);
Arrays.sort(s);
int num = 0;
int sIdx = 0;
int gIdx = 0;
while (sIdx < s.length && gIdx < g.length) {
if (s[sIdx] >= g[gIdx]) {
num++;
sIdx++;
gIdx++;
} else {
sIdx++;
}
}
return num;
}
时间复杂度:O(mlogm + nlogn),其中m和n分别是数组g和s的长度,对两个数组排序的时间复杂度是O(mlogm + nlogn),遍历数组的时间复杂度是O(m+n),因此总的时间复杂度是O(mlogm + nlogn)
空间复杂度:O(logm+logn),其中m和n分别是数组g和s的长度,空间复杂度主要是排序的额外空间开销
376. 摆动序列
53. 最大子数组和
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6
暴力搜超出时间限制
public int maxSubArray(int[] nums) {
int res = Integer.MIN_VALUE;
for (int i = 0; i < nums.length; i++) {
int path = 0;
for (int j = i; j < nums.length; j++) {
path += nums[j];
res = Math.max(path, res);
}
}
return res;
}
贪心,在如果一个局部的连续的子段和小于0了,那就舍弃这个子段,count=0从下一个开始重新算,res用来记录全局最大子段和
public int maxSubArray(int[] nums) {
int res = Integer.MIN_VALUE;
int count = 0;
for (int i = 0; i < nums.length; i++) {
count += nums[i];
res = Math.max(res, count);
if (count < 0) {
count = 0;
}
}
return res;
}
121.买卖股票的最佳时机
输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
class Solution {
public int maxProfit(int[] prices) {
int max = Integer.MIN_VALUE;
int count = Integer.MAX_VALUE;
for (int i = 0; i < prices.length; i++) {
count = Math.min(count, prices[i]);
max = Math.max(max, prices[i] - count);
}
return max;
}
}
时间复杂度O(n),空间复杂度O(1)
122.买卖股票的最佳时机II
示例 1:
输入:prices = [7,1,5,3,6,4]
输出:7
解释:在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6 - 3 = 3 。
总利润为 4 + 3 = 7 。
class Solution {
public int maxProfit(int[] prices) {
int count = Integer.MAX_VALUE;
int max = Integer.MIN_VALUE;
int res = 0;
for (int i = 0; i < prices.length; i++) {
count = Math.min(count, prices[i]);
if (prices[i] - count < max) {
res += max;
count = prices[i];
max = 0;
} else {
max = Math.max(max, prices[i] - count);
if (i == prices.length - 1) {
res += max;
}
}
}
return res == 0 ? max : res;
}
}
求局部最优解的思路和I题基本一样,但是在局部最优解区间之间,比如1,3,2中,1,3就是一个局部最优解,1买入3卖出,所以在2处,如果prices[i]-count小于当前的max了,那就是找到一个局部最优解的有边界了,从新开始找就好。注意的是每次遇到边界的时候就把当前的max累加起来,但是也有的数据的最后边的局部区间之后没有下降得了,例如[6,1,3,2,4,7],所以要单独把它们加上去。时间复杂度O(n),空间复杂度O(1)。
卡哥解法:
// 贪心思路
class Solution {
public int maxProfit(int[] prices) {
int result = 0;
for (int i = 1; i < prices.length; i++) {
result += Math.max(prices[i] - prices[i - 1], 0);
}
return result;
}
}
假设第一天买入,第三天卖出,则利润为p[2]-p[0],相当于p[2]-p[1]+ p[1]-p[0],所以只要算出相邻两天的差价把正值都加起来就好。真妙啊!
55.跳跃游戏
给定一个非负整数数组 nums ,你最初位于数组的 第一个下标 。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个下标。
示例 1:
输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。
class Solution {
public boolean canJump(int[] nums) {
int range = 0;
for (int i = 0; i <= range; ) {
range = Math.max(nums[i] + i, range);
if (range >= nums.length - 1) {
return true;
}
i++;
}
return false;
}
}
每走一步确定一个可达范围
45.跳跃游戏II
给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。
每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说,如果你在 nums[i] 处,你可以跳转到任意 nums[i + j] 处:
0 <= j <= nums[i]
i + j < n
返回到达 nums[n - 1] 的最小跳跃次数。生成的测试用例可以到达 nums[n - 1]。
示例 1:
输入: nums = [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。
从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。
public int jump(int[] nums) {
if (nums == null || nums.length == 0 || nums.length == 1) {
return 0;
}
int curRnge = 0;
int maxRange = 0;
int count = 0;
for (int i = 0; i < nums.length; i++) {
maxRange = Math.max(maxRange, nums[i] + i);
if (maxRange >= nums.length - 1) {
count++;
return count;
}
if (i == curRnge) {
curRnge = maxRange;
count++;
}
}
return -1;
}
为了达到最小步数,每一步都尽量迈的大一点,每遍历到一个数字时,记录下能到达的最远的边界,从这个数字依次向后走看看走到新的位置的时候能不能达到最后一个,能的话count+1,要走到这个数值上,不能的话当走到边界的时候,count+1,继续向后走。这个题没做出来,二刷还得再多想想。
1005.K次取反后最大化的数组和
给你一个整数数组 nums 和一个整数 k ,按以下方法修改该数组:
选择某个下标 i 并将 nums[i] 替换为 -nums[i] 。
重复这个过程恰好 k 次。可以多次选择同一个下标 i 。
以这种方式修改数组后,返回数组 可能的最大和 。
示例 1:
输入:nums = [4,2,3], k = 1
输出:5
解释:选择下标 1 ,nums 变为 [4,-2,3] 。
示例 2:
输入:nums = [3,-1,0,2], k = 3
输出:6
解释:选择下标 (1, 2, 2) ,nums 变为 [3,1,0,2] 。
class Solution {
public int largestSumAfterKNegations(int[] nums, int k) {
Arrays.sort(nums);
for (int i = 0; i < nums.length && k > 0; i++) {
if (nums[i] < 0) {
nums[i] = -nums[i];
k--;
}
}
if (k > 0) {
boolean zero = zero(nums);
if (zero) {
return sum(nums);
} else {
int min = min(nums);
int m = min;
while (k > 0) {
min = -min;
k--;
}
return sum(nums) - m + min;
}
}
return sum(nums);
}
private boolean zero(int[] nums) {
for (int n : nums) {
if (n == 0) {
return true;
}
}
return false;
}
private int sum(int[] nums) {
int sum = 0;
for (int n : nums) {
sum += n;
}
return sum;
}
private int min(int[] nums) {
int min = Integer.MAX_VALUE;
for (int n : nums) {
min = Math.min(min, n);
}
return min;
}
}
134.加油站
在一条环路上有 n 个加油站,其中第 i 个加油站有汽油 gas[i] 升。
你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。
给定两个整数数组 gas 和 cost ,如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1 。如果存在解,则 保证 它是 唯一 的。
示例 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 tmpSum = 0;
int sum = 0;
int idx = 0;
for (int i = 0; i < gas.length; i++) {
int chazhi = gas[i] - cost[i];
tmpSum += chazhi;
sum += chazhi;
if (tmpSum < 0) {
tmpSum = 0;
idx = i + 1;
}
}
if (sum < 0) {
return -1;
}
return idx;
}
}
卡哥解法得精髓是想明白从0开始累加rest[i],如果和小于0了,整个[0, i]区间都不能作为起始位置了,因为不管从这个区间的哪个位置出发,到i处都会断油,所以起始位置就从i+1重新算。
135.分发糖果
n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。
你需要按照以下要求,给这些孩子分发糖果:
每个孩子至少分配到 1 个糖果。
相邻两个孩子评分更高的孩子会获得更多的糖果。
请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目 。
示例 1:
输入:ratings = [1,0,2]
输出:5
解释:你可以分别给第一个、第二个、第三个孩子分发 2、1、2 颗糖果。
示例 2:
输入:ratings = [1,2,2]
输出:4
解释:你可以分别给第一个、第二个、第三个孩子分发 1、2、1 颗糖果。
第三个孩子只得到 1 颗糖果,这满足题面中的两个条件。
class Solution {
public int candy(int[] ratings) {
if (ratings.length == 0) {
return 0;
}
int[] candy = new int[ratings.length];
candy[0] = 1;
for (int i = 1; i < ratings.length; i++) {
if (ratings[i] > ratings[i - 1]) {
candy[i] = candy[i - 1] + 1;
} else {
candy[i] = 1;
}
}
for (int i = ratings.length - 2; i >= 0; i--) {
if (ratings[i] > ratings[i + 1]) {
candy[i] = Math.max(candy[i + 1] + 1, candy[i]);
} else {
continue;
}
}
int sum = 0;
for (int n : candy) {
sum += n;
}
return sum;
}
}
860.柠檬水找零
在柠檬水摊上,每一杯柠檬水的售价为 5 美元。顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。
每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。
注意,一开始你手头没有任何零钱。
给你一个整数数组 bills ,其中 bills[i] 是第 i 位顾客付的账。如果你能给每位顾客正确找零,返回 true ,否则返回 false 。
示例 1:
输入:bills = [5,5,5,10,20]
输出:true
解释:
前 3 位顾客那里,我们按顺序收取 3 张 5 美元的钞票。
第 4 位顾客那里,我们收取一张 10 美元的钞票,并返还 5 美元。
第 5 位顾客那里,我们找还一张 10 美元的钞票和一张 5 美元的钞票。
由于所有客户都得到了正确的找零,所以我们输出 true。
class Solution {
public boolean lemonadeChange(int[] bills) {
Map<Integer, Integer> map = new TreeMap<>();
map.put(5, 0);
map.put(10, 0);
map.put(20, 0);
for (int i = 0; i < bills.length; i++) {
int zhao = bills[i] - 5;
if (zhao == 5) {
if (map.get(5) <= 0) {
return false;
} else {
map.put(5, map.get(5) - 1);
}
} else if (zhao == 15) {
if (map.get(10) >= 1 && map.get(5) >= 1) {
map.put(10, map.get(10) - 1);
map.put(5, map.get(5) - 1);
} else if (map.get(5) >= 3) {
map.put(5, map.get(5) - 3);
} else {
return false;
}
}
map.put(bills[i], map.getOrDefault(bills[i], 0) + 1);
}
return true;
}
}
看了卡哥的解答,自己写的时候忽视了一个地方,就是给20块钱找零的时候,要优先给找10块和一个5块,如果没有再找3个5块,因为5块既可以给10块找零,又可以给20块找零,5块是更万能的。
406.根据身高重建队列
假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高大于或等于 hi 的人。
请你重新构造并返回输入数组 people 所表示的队列。返回的队列应该格式化为数组 queue ,其中 queue[j] = [hj, kj] 是队列中第 j 个人的属性(queue[0] 是排在队列前面的人)。
示例 1:
输入:people = [[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]]
输出:[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]
解释:
编号为 0 的人身高为 5 ,没有身高更高或者相同的人排在他前面。
编号为 1 的人身高为 7 ,没有身高更高或者相同的人排在他前面。
编号为 2 的人身高为 5 ,有 2 个身高更高或者相同的人排在他前面,即编号为 0 和 1 的人。
编号为 3 的人身高为 6 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
编号为 4 的人身高为 4 ,有 4 个身高更高或者相同的人排在他前面,即编号为 0、1、2、3 的人。
编号为 5 的人身高为 7 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
因此 [[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]] 是重新构造后的队列。
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];
});
LinkedList<int[]> queue = new LinkedList<>();
for (int[] p : people) {
queue.add(p[1], p);
}
return queue.toArray(new int[people.length][]);
}
和给一队孩子分发糖果是一样的思路,两个维度的要先确定下来一个维度,再排另一个维度