代码随想录算法训练营Day34|贪心part03

114 阅读4分钟

LeetCode 134 加油站

题目链接:leetcode.cn/problems/ga…

文档讲解:programmercarl.com/0134.加油站.ht…

视频讲解:www.bilibili.com/video/BV1jA…

思路

暴力法

从每个起点开始模拟一圈,时间复杂度O(n2)O(n^2)

贪心法

  1. 如果总油量大于等于总消耗,那么一定可以跑完一圈,反之则不能。把问题转换为一定能跑完,寻找起点的问题
  2. 假设每个加油站的剩余量rest[i]为gas[i] - cost[i]。
  3. 从0开始累加rest[i]作为currSum,一旦currSum小于0,那么[0,i]区间内都不能作为起点,走到i都会没有油(可以反证法)
  4. 此时就要更新currSum=0,从i+1的位置重新开始计算
  5. 如果最后走回来了,那么找到了起点

解法

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 分发糖果

题目链接:leetcode.cn/problems/ca…

文档讲解:programmercarl.com/0135.分发糖果.h…

视频讲解:www.bilibili.com/video/BV1ev…

思路

重要思想:确定一边的关系(右边大于左边),再考虑另一边(左边大于右边) 首先考虑右边大于左边的情况,需要从前向后遍历

  • 局部最优:只要右边的大于左边的,右边就比左边多给一个。否则就给一个
  • 全局最优:所有大于左侧小孩评分的小孩拿到的糖都比左侧小孩多

再考虑左边大于右边的情况,需要从后向前遍历

  • 局部最优:只要左边的大于右边的,左边就比右边多给一个/不变(这两个值取最大的,因为各自都是满足对应方向的最小个数)。否则就不变。
  • 全局最优:所有大于右侧小孩评分的小孩拿到的糖都比右侧小孩多 结合两个全局最优,满足题目条件

解法

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 柠檬水找零

题目链接:leetcode.cn/problems/le…

文档讲解:programmercarl.com/0860.柠檬水找零.…

视频讲解:www.bilibili.com/video/BV12x…

思路

每位顾客只买一杯柠檬水,然后向你付 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 根据身高重建队列

题目链接:leetcode.cn/problems/qu…

文档讲解:programmercarl.com/0406.根据身高重建…

视频讲解:www.bilibili.com/video/BV1EA…

思路

本题有两个维度h和k要考虑,和分发糖果一样,先确定一个维度h,再确定一个维度k k的含义是前面有k个人比他高,所以我们先对数组的h从大到小排序,这样我们就知道前面的人一定都比他高。如果h相同,显然k小的应该靠前。

现在要考虑k,我们可以发现,k一定小于等于比他高的元素个数,所以满足k的操作一定是把元素从后面的位置插入前面的某个位置。而且前面的元素都比他高,所以只要插入在第k+1个位置即可。

在这个过程中,应该从前向后遍历数组,这样可以保证遍历到第i个人时,前i-1个人都比他高,因为元素只会前移。

解法

先使用的数组,时间复杂度O(n2)O(n^2)

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;		
	}
}

数组跑的太慢了,换成链表。时间复杂度O(nlogn)O(nlogn)

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小时,贪心大部分时候真的挺难,没头绪