一、贪心简单题
1.1 分发饼干
class Solution {
public int findContentChildren(int[] g, int[] s) {
//排序
Arrays.sort(g);
Arrays.sort(s);
int i = 0,j = 0;
while(i<s.length && j<g.length){
if(s[i]>=g[j]){
i++;
j++;
}else{
i++;
}
}
return j;
}
}
1.2 K次取反后最大化的数组和
class Solution {
public int largestSumAfterKNegations(int[] nums, int k) {
//有负数先取反
Arrays.sort(nums);
for(int i = 0;i<nums.length;i++){
if(nums[i]<0 && k>0){
nums[i] = -nums[i];
k--;
}else{
break;
}
}
//k还大于0,并且为奇数
if(k>0 && k%2!=0){
//重新排序
Arrays.sort(nums);
nums[0] = -nums[0];
}
//遍历求和
int sum = 0;
for(int i = 0;i<nums.length;i++){
sum += nums[i];
}
return sum;
}
}
1.3 柠檬水找零
注意20找15的时候,应优先找一张5一张10,有助于保留更多的5,用于后续交易
class Solution {
public boolean lemonadeChange(int[] bills) {
int fiveCount = 0;
int tenCount = 0;
for(int i = 0;i<bills.length;i++){
if(bills[i]==5){
fiveCount++;
}else if(bills[i]==10){
if(fiveCount<1){
return false;
}
fiveCount--;
tenCount++;
}else if(bills[i]==20){
if(fiveCount>0 && tenCount>0){
fiveCount--;
tenCount--;
}else if(fiveCount>=3){
fiveCount -= 3;
}else{
return false;
}
}
}
return true;
}
}
二、贪心中等题
2.1 摆动序列
class Solution {
public int wiggleMaxLength(int[] nums) {
if(nums.length<=1){
return nums.length;
}
int res = 1;
int curDiff = 0;
int preDiff = 0;
for(int i = 1;i<nums.length;i++){
curDiff = nums[i]-nums[i-1];
//preDiff等于0时,是初始状态
//若没有进入if中,res未改变,preDiff也没有被赋新值
//本题只要求返回子序列长度,故可以不删除数组元素
if(curDiff>0 && preDiff<=0 || curDiff<0 && preDiff>=0){
res++;
preDiff = curDiff;
}
}
return res;
}
}
2.2 单调递增的数字
注意点:整数如何转换为字符串数组 --- 利用+"" 以及 split("")
字符串如何转换为整数 --- Integer.parseInt()
字符串数组如何连接成一个字符串 --- String.join("",str)
class Solution {
public int monotoneIncreasingDigits(int n) {
//+""的作用,将前面的表达式转换成字符串类型
//split("拆分字符串的符号")
String[] str = (n+"").split("");
int index = str.length;
for(int i = str.length-1;i>0;i--){
//Integer.parseInt():字符串转换为整数
if(Integer.parseInt(str[i])<Integer.parseInt(str[i-1])){
str[i-1] = (Integer.parseInt(str[i-1])-1)+"";
index = i;
}
}
for(int i = index;i<str.length;i++){
str[i] = "9";
}
//String.join("用来连接的符号",需连接起来的字符)
return Integer.parseInt(String.join("",str));
}
}
2.3 买卖股票的最佳时机 II
class Solution {
//重点:把整理利润拆分成每天的利润
//贪心:只收集每天的正利润
public int maxProfit(int[] prices) {
int res = 0;
for(int i = 1;i<prices.length;i++){
if(prices[i]-prices[i-1]>0){
res += prices[i]-prices[i-1];
}
}
return res;
}
}
2.4 两个维度权衡问题
重点:不要顾此失彼,先确定一个维度,再考虑另一个维度
2.4.1 分发糖果
class Solution {
public int candy(int[] ratings) {
//先从左到右
int sum = 0;
int[] surger = new int[ratings.length];
surger[0] = 1;
for(int i = 1;i<ratings.length;i++){
if(ratings[i]>ratings[i-1]){
surger[i] = surger[i-1]+1;
}else{
surger[i] = 1;
}
}
//从右到左
for(int i = ratings.length-1;i>0;i--){
if(ratings[i-1]>ratings[i] && surger[i-1]<=surger[i]){
surger[i-1] = surger[i]+1;
}
}
//遍历
for(int i = 0;i<surger.length;i++){
sum += surger[i];
}
return sum;
}
}
2.4.2 根据身高重建队列
关键步骤:res.add(p[1],p);
class Solution {
public int[][] reconstructQueue(int[][] people) {
//身高从大到小排,遇到相同身高的,k值小的在前面
//a-b升序 b-a降序
Arrays.sort(people,(a,b)->{
if(a[0]==b[0]){
return a[1]-b[1];
}else{
return b[0]-a[0];
}
});
//便于动态插入元素
ArrayList<int[]> res = new ArrayList<>();
//LinkedList<int[]> res = new LinkedList<>();
for(int[] p:people){
res.add(p[1],p);
}
return res.toArray(new int[people.length][2]);
}
}
三、贪心难题
3.1 区间问题
3.1.1 跳跃游戏
注意:在目前覆盖范围中更新!for循环的边界要明确,不是整个数组的边界,而应该是目前覆盖范围的边界!
class Solution {
public boolean canJump(int[] nums) {
//在目前覆盖范围中更新最大覆盖范围
int coverRange = 0;
for(int i = 0;i<=coverRange;i++){
coverRange = Math.max(coverRange,i+nums[i]);
if(coverRange>=nums.length-1){
return true;
}
}
return false;
}
}
3.1.2 跳跃游戏II
class Solution {
//一步尽可能多走,到不了就多走一步
public int jump(int[] nums) {
int curCoverRange = 0;
int maxCoverRange = 0;
int ans = 0;
if(nums.length<=1 || nums==null ){
return 0;
}
for(int i = 0;i<nums.length;i++){
//更新最大范围
maxCoverRange = Math.max(maxCoverRange,i+nums[i]);
if(maxCoverRange>=nums.length-1){
ans++;
break;
}
//走到了当前范围的最后,更新下一步可达的最大范围
if(i==curCoverRange){
ans++;
curCoverRange = maxCoverRange;
}
}
return ans;
}
}
3.1.3 用最少数量的箭引爆气球
class Solution {
public int findMinArrowShots(int[][] points) {
Arrays.sort(points,(a,b)->Integer.compare(a[0],b[0]));
int count = 1;
for(int i = 1;i<points.length;i++){
if(points[i][0]<=points[i-1][1]){
//重叠了
//更新重叠气球最小右边界
points[i][1] = Math.min(points[i][1],points[i-1][1]);
}else{
count++;
}
}
return count;
}
}
3.1.4 无重叠区间
class Solution {
public int eraseOverlapIntervals(int[][] intervals) {
Arrays.sort(intervals,(a,b)->Integer.compare(a[0],b[0]));
int count = 0;
for(int i = 1;i<intervals.length;i++){
if(intervals[i-1][1]>intervals[i][0]){
//有重叠
intervals[i][1] = Math.min(intervals[i][1],intervals[i-1][1]);
count++;
}
}
return count;
}
}
3.1.5 划分字母区间
class Solution {
public List<Integer> partitionLabels(String s) {
char[] chars = s.toCharArray();
int[] record = new int[26];
//记录该字母最后出现的索引位置
for(int i = 0;i<chars.length;i++){
record[chars[i]-'a'] = i;
}
List<Integer> res = new ArrayList<>();
int index = 0;
int last = -1;
for(int i = 0;i<chars.length;i++){
index = Math.max(index,record[chars[i]-'a']);
if(index==i){
res.add(i-last);
last = i;
}
}
return res;
}
}
3.1.6 合并区间
注意:此时不是i和i-1进行比较了,因为这里返回的是int[][],需要更新上一个区间的记录,出现重叠,则上一个区间不再是i-1了!
class Solution {
public int[][] merge(int[][] intervals) {
Arrays.sort(intervals,(a,b)->Integer.compare(a[0],b[0]));
LinkedList<int[]> res = new LinkedList<>();
int start = intervals[0][0];
int rightRange = intervals[0][1];
for(int i = 1;i<intervals.length;i++){
if(rightRange>=intervals[i][0]){
//重叠了
rightRange = Math.max(intervals[i][1],rightRange);
}else{
res.add(new int[]{start,rightRange});
start = intervals[i][0];
rightRange = intervals[i][1];
}
}
res.add(new int[]{start,rightRange});
return res.toArray(new int[res.size()][2]);
}
}
3.2 其它难题
3.2.1 最大子数组和
class Solution {
public int maxSubArray(int[] nums) {
//加起来为负数,对后面是减小的效果,舍去
int sum = Integer.MIN_VALUE;
int tmp = 0;
for(int i = 0;i<nums.length;i++){
tmp += nums[i];
sum = Math.max(sum,tmp);
if(tmp<0){
tmp=0;
}
}
return sum;
}
}
3.2.2 加油站
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
int curSum = 0;
int totalSum = 0;
int index = 0;
for(int i = 0;i<gas.length;i++){
curSum += gas[i]-cost[i];
totalSum += gas[i]-cost[i];
if(curSum<0){
index = (i+1)%gas.length;
curSum = 0;
}
}
if(totalSum<0){
return -1;
}
return index;
}
}
3.2.3 监控二叉树
从下往上;
局部最优:给叶子节点的父节点装摄像头
注意:
1、最后的节点若为空,则需要额外加个摄像头
2、设置三个状态:0无覆盖、1有摄像头、2有覆盖
3、存在三种情景:左右节点均有覆盖---说明该父节点无覆盖且无摄像头
左右节点至少一个无覆盖---说明该父节点应加一个摄像头
左右节点至少存在一个摄像头---说明该父节点被覆盖
4、遇到空节点应设置为什么状态:若为0,则需要在叶子节点加一个摄像头
若为1,则叶子节点被覆盖了,叶子节点的父节点就不需要装摄像头了
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
int res = 0;
public int minCameraCover(TreeNode root) {
//特殊情况:根节点为未覆盖,额外加个摄像头
if(minCamera(root)==0){
res++;
}
return res;
}
private int minCamera(TreeNode root){
//0:未覆盖
//1:有摄像头
//2:已覆盖
//后序遍历:左右中
if(root==null){
return 2;//空节点不能是0,这样叶子节点上就需要有摄像头;不能是1,这样叶子节点的父节点就没必要放摄像头了
}
int left = minCamera(root.left);
int right = minCamera(root.right);
//左右节点都被覆盖,则该节点上是无摄像头、无覆盖状态
if(left==2 && right==2){
return 0;
}else if(left==0 || right==0){
//至少有一个左右节点没被覆盖,则该节点需要加个摄像头
res++;
return 1;
}else{
//其他情况:左右节点至少存在一个摄像头,所以该节点被覆盖
return 2;
}
}
}