LeetCode 134 加油站
思路
暴力法
从每个起点开始模拟一圈,时间复杂度
贪心法
- 如果总油量大于等于总消耗,那么一定可以跑完一圈,反之则不能。把问题转换为一定能跑完,寻找起点的问题
- 假设每个加油站的剩余量rest[i]为gas[i] - cost[i]。
- 从0开始累加rest[i]作为currSum,一旦currSum小于0,那么[0,i]区间内都不能作为起点,走到i都会没有油(可以反证法)
- 此时就要更新currSum=0,从i+1的位置重新开始计算
- 如果最后走回来了,那么找到了起点
解法
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
int[] rest = new int[cost.length];
int sum = 0;
for (int i = 0; i < rest.length; i++) {
rest[i] = gas[i] - cost[i];
sum += rest[i];
}
if (sum < 0) {
return -1;
}
int start = 0;
int currSum = 0;
int i = 0;
while (true) {
currSum += rest[i];
i = (i+1) % rest.length;
if (currSum < 0) {
currSum = 0;
start = i;
}
else if (start == i) {
break;
}
}
return start;
}
}
LeetCode 135 分发糖果
思路
重要思想:确定一边的关系(右边大于左边),再考虑另一边(左边大于右边) 首先考虑右边大于左边的情况,需要从前向后遍历
- 局部最优:只要右边的大于左边的,右边就比左边多给一个。否则就给一个
- 全局最优:所有大于左侧小孩评分的小孩拿到的糖都比左侧小孩多
再考虑左边大于右边的情况,需要从后向前遍历
- 局部最优:只要左边的大于右边的,左边就比右边多给一个/不变(这两个值取最大的,因为各自都是满足对应方向的最小个数)。否则就不变。
- 全局最优:所有大于右侧小孩评分的小孩拿到的糖都比右侧小孩多 结合两个全局最优,满足题目条件
解法
class Solution {
public int candy(int[] ratings) {
int[] candy = new int[ratings.length];
candy[0] = 1;
for (int i = 1; i < candy.length; i++) {
if (ratings[i] > ratings[i-1]) {
candy[i] = 1 + candy[i-1];
}
else {
candy[i] = 1;
}
}
for (int i = ratings.length-1; i > 0; i--) {
if (ratings[i-1] > ratings[i]) {
candy[i-1] = Math.max(candy[i] + 1, candy[i-1]);
}
}
int sum = 0;
for (int i : candy) {
sum += i;
}
return sum;
}
}
LeetCode 860 柠檬水找零
思路
每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。
由于5美元可以找零所有情况,而10美元只能用于找零20美元,所以我们的局部最优策略是尽可能使用10美元找零,达到全局最优尽可能找开所有情况。
具体来说,我们需要记录手头5美元和10美元的数量,因为只有他们能用于找零,20美元无法出手。
解法
class Solution {
public boolean lemonadeChange(int[] bills) {
int five = 0;
int ten = 0;
for (int i = 0; five >= 0 && ten >= 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 -= 3;
}
}
}
if (five < 0 || ten < 0) {
return false;
}
return true;
}
}
LeetCode 406 根据身高重建队列
思路
本题有两个维度h和k要考虑,和分发糖果一样,先确定一个维度h,再确定一个维度k k的含义是前面有k个人比他高,所以我们先对数组的h从大到小排序,这样我们就知道前面的人一定都比他高。如果h相同,显然k小的应该靠前。
现在要考虑k,我们可以发现,k一定小于等于比他高的元素个数,所以满足k的操作一定是把元素从后面的位置插入前面的某个位置。而且前面的元素都比他高,所以只要插入在第k+1个位置即可。
在这个过程中,应该从前向后遍历数组,这样可以保证遍历到第i个人时,前i-1个人都比他高,因为元素只会前移。
解法
先使用的数组,时间复杂度
class Solution {
public int[][] reconstructQueue(int[][] people) {
Arrays.sort(people, new Comparator<int[]>() {
@Override
public int compare(int[] a, int[] b) {
if (b[0] != a[0]) {
return b[0] - a[0];
}
else {
return a[1] - b[1];
}
}
});
for (int i = 0; i < people.length; i++) {
if (i > people[i][1]) {
int[] tmp = {people[i][0], people[i][1]};
for (int j = i; j > tmp[1]; j--) {
people[j] = people[j-1];
}
people[tmp[1]] = tmp;
}
}
return people;
}
}
数组跑的太慢了,换成链表。时间复杂度
class Solution {
public int[][] reconstructQueue(int[][] people) {
Arrays.sort(people, new Comparator<int[]>() {
@Override
public int compare(int[] a, int[] b) {
if (b[0] != a[0]) {
return b[0] - a[0];
}
else {
return a[1] - b[1];
}
}
});
List<List<Integer>> list = new LinkedList<>();
for (int i = 0; i < people.length; i++) {
if (i > people[i][1]) {
list.add(people[i][1], Arrays.asList(people[i][0], people[i][1]));
}
else {
list.add(Arrays.asList(people[i][0], people[i][1]));
}
}
for (int i = 0; i < people.length; i++) {
people[i][0] = list.get(i).get(0);
people[i][1] = list.get(i).get(1);
}
return people;
}
}
今日收获总结
今日学习3小时,贪心大部分时候真的挺难,没头绪