贪心
1,贪心的算法思想
贪心本质是求解局部最优,从而全局最优。
assert:动规的原因是两个限制条件是互相干扰的,而贪心的子条件相互独立,所以才能确保局部最优到全局最优。因为有很多情况,局部最优不一定是全局最优,这个前提就是全局可以等价于局部的累加。
2,力扣题型
376,摆动序列
https://leetcode-cn.com/problems/wiggle-subsequence/
摆动序列就是一个上下波动的序列,如果不符合就要采用子序列的定义删掉这个波值,只不过这里的删掉不是真正的删掉数组元素,而是跳过不将其统计即可。所以我们的值是可以继续累加的,因为不符合的元素可以删掉。
(下面据说是一个优化的dp)
class Solution {
public int wiggleMaxLength(int[] nums) {
if(nums.length < 2) return nums.length;
if(nums.length == 2 && nums[0]!=nums[1]) return 2;
// 上坡和下坡计数
int up=1,down = 1;
for(int i=1;i<nums.length;i++){
// down没变就不增,down变了其实在down上增
if(nums[i]>nums[i-1]) up = down +1;
// up没变就不增
if(nums[i]<nums[i-1]) down = up + 1;
}
return Math.max(up,down);
}
}
贪心思路:
class Solution {
public int wiggleMaxLength(int[] nums) {
// 贪心思路:局部最优累加即全局最优
if(nums.length < 2) return nums.length;
// 分别定义当前差值和前一个差值区分是上坡还是下坡
int prediff = 0;
int currdiff = 0;
int count = 1;
for(int i=1;i<nums.length;i++){
currdiff = nums[i] - nums[i-1];
// 边界问题,等于0表示为了满足初始时的情况
if((currdiff < 0 && prediff >=0) || (currdiff >0 && prediff <=0)){
count++;
prediff = currdiff;
}
}
return count;
}
}
53,最大子序和
https://leetcode-cn.com/problems/maximum-subarray/
class Solution {
public int maxSubArray(int[] nums) {
// sum初始最小,目的一定要取到值
// sum,count分别记录全局和局部最优
int sum = Integer.MIN_VALUE;
int count = 0;
for(int i=0;i<nums.length;i++){
count += nums[i];
sum = Math.max(count,sum);
// 当count小于0时,立马置位0,表示下次直接跳过之前的累积和
// 放在最后判断,也是为了sum能够记录到之前的局部最优
if(count <= 0) count = 0;
}
return sum;
}
}
122,买卖股票的最佳时机II
https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/
两天为一个交易日。不要想的复杂,简化:第一天买入和第三天卖出没有绝对关系,因为prices[1]-prices[2]+prices[2]-prices[3];所以其实等价于前后两天的一个收益,也就是局部收益最高全局就最高。
class Solution {
public int maxProfit(int[] prices) {
// 多次买卖一支股票:即当天卖出的当天可以买入
// 所以不需要考虑当天卖出的是否最大,因为当天还可以买
int in = 0, out = 0,result = 0;
for(int i=1;i<prices.length;i++){
if(prices[i-1]<prices[i]){
in = prices[i-1];
out = prices[i];
result += (out -in);
}
}
return result;
}
}
714,买卖股票的最佳时机含手续费
https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/
class Solution {
public int maxProfit(int[] prices, int fee) {
// 因为有了手续费,不能简单的买了再卖
// 需要记录最小值
int min = prices[0];
int res = 0;
for(int i=1;i<prices.length;i++){
if(prices[i]<min){
min = prices[i];
}
// 卖出,可能会多次计算利润
else if(prices[i]-fee>min){
res += prices[i]-fee-min;
// 但是每次计算利润都减去了手续费,确保卖出只减一次
min = prices[i]-fee;
}
//还有第三种情况:不买不卖,可省略
else if(prices[i]>=min && prices[i]-fee<=min){
continue;
}
}
return res;
}
}
55,跳跃游戏
https://leetcode-cn.com/problems/jump-game/
class Solution {
public boolean canJump(int[] nums) {
// 贪心算法
// 每个位置能够到达的最远距离,更新位置的最大值,当最终到达的距离大于最长距离(length-1)时则能到达
int s = nums.length;
int maxDis = nums[0];
if(s<=1) return true;
// 尝试从当前位置一步一步走,最多走s-1步!
for(int i=1;i<s-1;i++){
// 走的步数要小于当前最远距离,则更新最远距离为当前位置值(下次能够走的)+已走的距离
if(i<=maxDis){
maxDis = Math.max(maxDis,nums[i]+i);
}else{
break;
}
}
return maxDis >= s-1;
}
}
class Solution {
public boolean canJump(int[] nums) {
int s = nums.length;
if (s == 1) {
return true;
}
//覆盖范围
int coverRange = nums[0];
//在覆盖范围内更新最大的覆盖范围,i表示走的步数
for (int i = 0; i <= coverRange; i++) {
coverRange = Math.max(coverRange, i + nums[i]);
if (coverRange >= s - 1) {
return true;
}
}
return false;
}
}
45,跳跃游戏 Ⅱ
https://leetcode-cn.com/problems/jump-game-ii/
class Solution {
public int jump(int[] nums) {
int s = nums.length;
int count = 0;
int curDistance = 0;
int nextDistance = 0;
for(int i=0;i<s-1;i++){
nextDistance = Math.max(nextDistance,nums[i]+i);
// 当当前下标等于当前最远覆盖距离时,说明走完了还没到达,则步数加1
// 同时将最远距离覆盖当前距离
if(i==curDistance){
curDistance = nextDistance;
count++;
}
}
return count;
}
}
1005,K次取反后的最大数组和
https://leetcode-cn.com/problems/maximize-sum-of-array-after-k-negations/
class Solution {
public int largestSumAfterKNegations(int[] nums, int k) {
int s = nums.length;
int sum = 0;
// 记录最小值
int min = Integer.MAX_VALUE;
Arrays.sort(nums);
// 先反转再求和
for(int i=0;i<s;i++){
if(nums[i]<0 && k>0){
nums[i] = -nums[i];
k--;
}
sum += nums[i];
min = Math.min(nums[i],min);
}
// 判断k是否消耗完
// 是则直接返回,否则减掉剩余的k%2个的2倍最小值,还要把之前的减掉
return k<=0 ? sum:(sum-(k%2)*2*min);
}
}
134,加油站
https://leetcode-cn.com/problems/gas-station/
暴力,过程模拟:(但是JAVA提交超时)
for循环适合模拟从头到尾的遍历,而while循环适合模拟环形遍历,要善于使用while
顺序遍历中取模运算并没有什么区别,环形遍历中可以实现循环顺序遍历的功能。
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
// 暴力过程模拟
int s = gas.length;
for(int i=0;i<s;i++){
int res = (gas[i]-cost[i]);
int index = (i+1)%s;
//到头了开始走环了,回到起点,此时index!=i;
while(res>0 && index != i){
res += (gas[i]-cost[i]);
index = (i+1)%s;
}
// 如果走了一圈且还有油,返回
if(index == i && res>=0) return i;
}
return -1;
}
}
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
// 题意:返回能够环路一周的出发地
// 为此:要省下最多的汽油,才能走得远
// 折返的过程该怎么体现?需要模拟吗,不需要!一趟遍历的过程即可记录能否走了
int s = gas.length;
int run = 0,res = 0,start = 0;
// 找到一个出发点
for(int i = 0;i<s;i++){
run += (gas[i]-cost[i]);
res += (gas[i]-cost[i]);
if(run < 0){
start = i+1;
run = 0;
}
}
return res>=0?start:-1;
}
}
135,分发糖果
https://leetcode-cn.com/problems/candy/submissions/
提示:当有两个维度时,一定要先确定一个维度;再确定另一个维度。
class Solution {
public int candy(int[] ratings) {
int s = ratings.length;
// 为什么一个用数组一个用变量?方便后面遍历时通过数组下标访问对应的值比较
int result = 0,right = 0;
int[] left = new int[s];
// 从前往后遍历识别左规则
for(int i=0;i<s;i++){
if(i>0 && ratings[i]>ratings[i-1]){
left[i] = left[i-1]+1;
}else{
left[i] = 1; //初始化相当于
}
}
// 从后往前遍历识别右规则
for(int i=s-1;i>=0;i--){
if(i<s-1 && ratings[i]>ratings[i+1]){
right++;
}else{
right = 1; //初始化相当于
}
// 取最大的确保左右规则能满足条件
result += Math.max(left[i],right);
}
return result;
}
}
406,根据身高重建队列
https://leetcode-cn.com/problems/queue-reconstruction-by-height/
class Solution {
public int[][] reconstructQueue(int[][] people) {
// 按照身高h从大到小排序,h相同时按照人数k从小到大排序
Arrays.sort(people, new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
// 升序是前一个减后一个,降序是后一个减前一个
return o1[0]==o2[0] ? o1[1]-o2[1]:o2[0]-o1[0];
}
});
LinkedList<int[]> queue = new LinkedList<>();
for (int[] person : people) {
// API:按照指定index插入element;在位置插入且不会覆盖!
// person的位置由k决定,此时会不断地覆盖,身高最小的会覆盖到最前面
queue.add(person[1], person);
}
// queue.toArray传入参数为数组维度即可
return queue.toArray(new int[people.length][]);
}
}
435,重叠子区间问题
https://leetcode-cn.com/problems/non-overlapping-intervals/submissions/
class Solution {
public int eraseOverlapIntervals(int[][] intervals) {
// 从左到右排序
Arrays.sort(intervals,(a,b)->Integer.compare(a[0],b[0]));
int count = 0;
// 起始待比较位置
int end = intervals[0][1];
for(int i=1;i<intervals.length;i++){
//前一个的尾部和后一个的头部比较,重叠,取最小的尾部以便后面有更多的区间容错
if(end>intervals[i][0]){
count++;
end = Math.min(intervals[i][1],end);
}else{
end = intervals[i][1];
}
}
return count;
}
}
类似题目:用最少的数量引爆气球https://leetcode-cn.com/problems/minimum-number-of-arrows-to-burst-balloons/comments/
736,划分区间
https://leetcode-cn.com/problems/partition-labels/solution/
class Solution {
public List<Integer> partitionLabels(String s) {
// 过程模拟
ArrayList res = new ArrayList();
char[] chars = s.toCharArray();
// 标记每个字符(字符用ASCII码区分)出现的最远位置
int[] words = new int[26];
for(int i=0;i<chars.length;i++){
words[chars[i]-'a'] = i;
}
int idx = 0;
int last = -1;
for(int i=0;i<chars.length;i++){
// 依据字母表,更新当前字符的最远位置,注意这里其实最后是取最远位置的字符
idx = Math.max(idx,words[chars[i]-'a']);
// 如果遍历到了最远位置
if(idx==i){
res.add(i-last);
last = i;
}
}
return res;
}
}
56,合并区间
https://leetcode-cn.com/problems/merge-intervals/submissions/
class Solution {
public int[][] merge(int[][] intervals) {
Arrays.sort(intervals,(a,b)->Integer.compare(a[0],b[0]));
ArrayList<int[]> res = new ArrayList();
int start = intervals[0][0];
int end = intervals[0][1];
for(int i=1;i<intervals.length;i++){
// 后一个和前一个相比,没重叠,直接添加并更新
if(intervals[i][0]>end){
res.add(new int[]{start,end});
start = intervals[i][0];
end = intervals[i][1];
}else{
// 有重叠更新最大的尾部
end = Math.max(end,intervals[i][1]);
}
}
// 会漏掉最后一个元素这里手动添加上
res.add(new int[]{start,end});
return res.toArray(new int[res.size()][]);
}
}
3,总结
题目 | 链接 | 难度 |
---|---|---|