【代码随想录】贪心算法刷题

贪心的本质是:选择每一阶段的局部最优解,从而达到全局最优。

贪心没有固定的套路,比较好的策略是举反例,想不到反例,就试试贪心。

分发饼干

题目:455. 分发饼干 - 力扣(LeetCode)

假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。
对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。

输入: g = [1,2,3], s = [1,1]
输出: 1
解释: 
你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。
虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。
所以你应该输出1。

贪心 + 双指针:

public int findContentChildren(int[] g, int[] s) {
    
    
	Arrays.sort(g);
	Arrays.sort(s);
	int res = 0;
	for (int i = 0, j = 0; i < s.length && j < g.length; i++) {
    
    
		if (s[i] >= g[j]) {
    
     // 饼干能喂饱孩子
			res++;
			j++; // 下一个孩子
		}
	}
	return res;
}

优化以上代码:可以发现 res 和 j 其实是一样的,只需要一个变量即可

public int findContentChildren(int[] g, int[] s) {
    
    
    Arrays.sort(g);
    Arrays.sort(s);
    int res = 0;
    for (int i = 0; i < s.length && res < g.length; i++) 
        if (g[res] <= s[i]) res++;
    return res;
}

摆动序列*

题目:376. 摆动序列 - 力扣(LeetCode)

如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为 摆动序列。第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。

  • 例如, [1, 7, 4, 9, 2, 5] 是一个 摆动序列 ,因为差值 (6, -3, 5, -7, 3) 是正负交替出现的。
  • 相反,[1, 4, 7, 2, 5][1, 7, 4, 5, 5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。
    子序列 可以通过从原始序列中删除一些(也可以不删除)元素来获得,剩下的元素保持其原始顺序。
    给你一个整数数组 nums ,返回 nums 中作为 摆动序列最长子序列的长度
输入:nums = [1,7,4,9,2,5]
输出:6
解释:整个序列均为摆动序列,各元素之间的差值为 (6, -3, 5, -7, 3) 。

模拟:

public int wiggleMaxLength(int[] nums) {
    
    
    if (nums.length == 1) return 1;
    int res = 1;
    Boolean flag = null; // 控制方向
    for (int i = 1; i < nums.length; i++) {
    
    
        if (nums[i] == nums[i - 1]) continue; // 0
        if (nums[i] - nums[i - 1] > 0) {
    
     // 正值
            if (flag != null && flag) continue; 
            flag = true;
        } else {
    
     // 负值
            if (flag != null && !flag) continue; 
            flag = false;
        }
        res++;
    }
    return res;
}

动态规划 一维:

public int wiggleMaxLength(int[] nums) {
    
    
	int n = nums.length;
	if (n == 1) return 1;
	
	int[] up = new int[n]; // 以 i 升序的最长子序列的长度
	int[] down = new int[n]; // 以 i 降序的最长子序列的长度
	up[0] = down[0] = 1;
	
	for (int i = 1; i < n; i++) {
    
    
		if (nums[i] > nums[i - 1]) {
    
     // 升序
			up[i] = Math.max(up[i - 1], down[i - 1] + 1);
			down[i] = down[i - 1];
		} else if (nums[i] < nums[i - 1]) {
    
     // 降序
			up[i] = up[i - 1];
			down[i] = Math.max(up[i - 1] + 1, down[i - 1]);
		} else {
    
     // 不变
			up[i] = up[i - 1];
			down[i] = down[i - 1];
		}
	}
	return Math.max(up[n - 1], down[n - 1]);
}

动态规划 二维:

public int wiggleMaxLength(int[] nums) {
    
    
    int n = nums.length;
    if (n <= 1) return n;
    // dp[i][0] 表示 nums[0] 到 nums[i] 的最长摆动序列长度, 且 nums[i] < nums[i - 1], 往下
    // dp[i][1] 表示 nums[0] 到 nums[i] 的最长摆动序列长度, 且 nums[i] > nums[i - 1], 往上
    int[][] dp = new int[n][2];
    dp[0][0] = dp[0][1] = 1;
    for (int i = 1; i < n; i++) {
    
    
        if (nums[i] < nums[i - 1]) {
    
    
            dp[i][0] = dp[i - 1][1] + 1;
            dp[i][1] = dp[i - 1][1];
        } else if (nums[i] > nums[i - 1]) {
    
    
            dp[i][0] = dp[i - 1][0];
            dp[i][1] = dp[i - 1][0] + 1;
        } else {
    
    
            dp[i][0] = dp[i - 1][0];
            dp[i][1] = dp[i - 1][1];
        }
    }
    return Math.max(dp[n - 1][0], dp[n - 1][1]);
}

最大子序和

题目:53. 最大子数组和 - 力扣(LeetCode)

给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。

输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。

动态规划:

public int maxSubArray(int[] nums) {
    
    
    // dp[i] 以 i 结尾的最大连续子数组和
    int[] dp = new int[nums.length];
    int max = dp[0] = nums[0];
    for (int i = 1; i < nums.length; i++) {
    
    
        dp[i] = Math.max(dp[i - 1] + nums[i], nums[i]);
        max = Math.max(max, dp[i]);
    }    
    return max;
}

贪心:

public int maxSubArray(int[] nums) {
    
    
    int max = nums[0];
    int tmp = 0; // 保存临时值
    for (int num : nums) {
    
    
        tmp += num;
        if (tmp > max) max = tmp;
        if (tmp < 0) tmp = 0;
    }
    return max;
}

买卖股票的最佳时机

题目:122. 买卖股票的最佳时机 II - 力扣(LeetCode)

给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。
在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。
返回 你能获得的 最大 利润

输入: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 。

一维 DP * 2:

public int maxProfit(int[] prices) {
    
    
	int n = prices.length;
	int[] buy = new int[n]; // buy[i] 第 i 天买能获得的最大利益
	int[] sell = new int[n]; // sell[i] 第 i 天卖能获得的最大利益
	buy[0] = -prices[0];
	int max = 0;
	for (int i = 1; i < n; i++) {
    
    
		buy[i] = Math.max(buy[i - 1], sell[i - 1] - prices[i]);
		sell[i] = Math.max(sell[i - 1], buy[i - 1] + prices[i]);
		max = Math.max(max, sell[i]); // 最大利益只会产生在卖的时候
	}
	return max;
}

二维 DP:

public int maxProfit(int[] prices) {
    
    
    int n = prices.length;
    // dp[i][0] - 第 i 天[买]能获得的最大利益
    // dp[i][1] - 第 i 天[卖]能获得的最大利益
    int[][] dp = new int[n][2];
    dp[0][0] = -prices[0];
    dp[0][1] = 0;
    int max = 0;
    for (int i = 1; i < n; i++) {
    
    
        dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
        dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] + prices[i]);
        max = Math.max(max, dp[i][1]);
    }
    return max;
}

贪心:今天比昨天大就直接卖出

public int maxProfit(int[] prices) {
    
    
    int res = 0;
    for (int i = 1; i < prices.length; i++) 
        if (prices[i] > prices[i - 1])
            res += prices[i] - prices[i - 1];
    return res;
}

跳跃游戏

题目:55. 跳跃游戏 - 力扣(LeetCode)

给定一个非负整数数组 nums ,你最初位于数组的 第一个下标
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个下标。

输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。

动态规划:

public boolean canJump(int[] nums) {
    
    
    int[] dp = new int[nums.length]; // dp[i] i 能跳到的最远下标
    dp[0] = nums[0];
    for (int i = 1; i < nums.length; i++) {
    
    
        if (dp[i - 1] < i) return false; // 前一个位置无法到达当前位置, 直接结束
        dp[i] = Math.max(dp[i - 1], i + nums[i]); // 计算当前位置能跳到的最远位置
    }
    return true;
}

贪心:

public boolean canJump(int[] nums) {
    
    
    int cover = 0; // 覆盖范围
    for (int i = 0; i <= cover; i++) {
    
    
        cover = Math.max(cover, i + nums[i]); // 每一步的最大覆盖范围
        if (cover >= nums.length - 1) return true; // 能覆盖到终点
    }
    return false;
}

跳跃游戏 II*

题目:45. 跳跃游戏 II - 力扣(LeetCode)

给你一个非负整数数组 nums ,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
你的目标是使用最少的跳跃次数到达数组的最后一个位置。
假设你总是可以到达数组的最后一个位置。

输入: nums = [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。
     从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。

DP:

public int jump(int[] nums) {
    
    
	int n = nums.length;
	int[] dp = new int[n]; // dp[i] 跳到 i 位置使用的最少跳跃次数
	Arrays.fill(dp, 10001);
	dp[0] = 0;
	for (int i = 1; i < n; i++)
		for (int j = 0; j < i; j++)
			if (nums[j] + j >= i)
				dp[i] = Math.min(dp[i], dp[j] + 1);
	return dp[n - 1];
}

贪心:

public int jump(int[] nums) {
    
    
    int res = 0;
    int curDis = 0; // 当前的跳跃次数下最远能达到的距离
    int maxDis = nums[0]; // 当前的跳跃次数下再加一次最远能达到的距离
    for (int i = 1; i < nums.length; i++) {
    
    
        // 能在当前跳跃次数下到达就不会多跳
        if (i > curDis) {
    
     // 无法在当前跳跃次数下到达
            res++;
            curDis = maxDis;
        }
        // 更新当前次数下再跳一次能到达的最远距离
        maxDis = Math.max(maxDis, i + nums[i]);
    }
    return res;
}

K 次取反后最大化的数组*

题目:1005. K 次取反后最大化的数组和 - 力扣(LeetCode)

给你一个整数数组 nums 和一个整数 k ,按以下方法修改该数组:

  • 选择某个下标 i 并将 nums[i] 替换为 -nums[i]
    重复这个过程恰好 k 次。可以多次选择同一个下标 i
    以这种方式修改数组后,返回数组 可能的最大和

优先级队列:

public int largestSumAfterKNegations(int[] nums, int k) {
    
    
	PriorityQueue<Integer> pq = new PriorityQueue<>(); // 小根堆
	for (int n : nums) pq.add(n);

	int res = 0;
	// 每次只将最小的值取反
	while (k-- > 0) pq.add(-pq.poll()); 
	while (!pq.isEmpty()) res += pq.poll();
	return res;
}

模拟 + 贪心:

public int largestSumAfterKNegations(int[] nums, int k) {
    
    
    Arrays.sort(nums);

    int idx = 0; // 遍历下标
    while (k-- > 0) {
    
     
        // 当前数 < 0, 取反后和下一位进行比较
        if (idx < nums.length - 1 && nums[idx] < 0) {
    
    
            nums[idx] = -nums[idx]; // 取反
            //
            if (nums[idx] >= Math.abs(nums[idx + 1])) idx++;
            continue;
        }
        // 当前数 >= 0
        nums[idx] = -nums[idx]; // 取反
    }

    int res = 0;
    for (int i = 0; i < nums.length; i++) res += nums[i];
    return res;
}

加油站*

题目:134. 加油站 - 力扣(LeetCode)

在一条环路上有 n 个加油站,其中第 i 个加油站有汽油 gas[i] 升。
你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。
给定两个整数数组 gascost ,如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -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 可为起始索引。

贪心思想:

  • 如果 gas 的总和小于 cost 总和,那么无论从哪里出发,肯定跑不了一圈
  • 当前油量的累加一旦小于 0,起始位置至少是 i + 1
public int canCompleteCircuit(int[] gas, int[] cost) {
    
    
	int totalNum = 0; // 总油量
	int curNum = 0; // 当前油量
	int start = 0; // 开始位置
	for (int i = 0; i < gas.length; i++) {
    
    
		curNum += gas[i] - cost[i];
		totalNum += gas[i] - cost[i];
		// 遇到油量不够的请求, 从当前开始重新计算
		if (curNum < 0) {
    
    
			start = i + 1; // 更新起始位置为 i + 1
			curNum = 0;
		}
	} 
	return totalNum < 0 ? -1 : start;
}

分发糖果*

题目:135. 分发糖果 - 力扣(LeetCode)

n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。
你需要按照以下要求,给这些孩子分发糖果:

  • 每个孩子至少分配到 1 个糖果。
  • 相邻两个孩子评分更高的孩子会获得更多的糖果。
    请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目
输入:ratings = [1,0,2]
输出:5
解释:你可以分别给第一个、第二个、第三个孩子分发 2、1、2 颗糖果。

每个孩子的评分有 2 次比较,左比较和右比较,对于碰到类似这种左右比较或者相邻比较,都可以考虑两次遍历。

需要考虑多个维度情况的题目,尝试分别考虑每个维度,不要想着同时考虑两个维度。

public int candy(int[] ratings) {
    
    
	int[] candies = new int[ratings.length];
	Arrays.fill(candies, 1);
	// 从左往右: 只比较右边孩子评分比左边大的情况
	for (int i = 1; i < candies.length; i++)
		if (ratings[i] > ratings[i - 1]) 
			candies[i] = candies[i - 1] + 1;
	// 从右往左: 只比较左边孩子评分比右边大的情况
	for (int i = candies.length - 2; i >= 0; i--)
		if (ratings[i] > ratings[i + 1])
			// 考虑这个维度要在第一个维度的基础上考虑, 即还得维护满足第一个维度的条件
			candies[i] = Math.max(candies[i], candies[i + 1] + 1);
	// 以上两个维度综合 => 相邻的孩子评分高的可以拿更多的糖果
	int res = 0;
	for (int i = 0; i < candies.length; i++) res += candies[i];
	return res;
}

柠檬水找零

题目:860. 柠檬水找零 - 力扣(LeetCode)

在柠檬水摊上,每一杯柠檬水的售价为 5 美元。顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。
每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。
注意,一开始你手头没有任何零钱。
给你一个整数数组 bills ,其中 bills[i] 是第 i 位顾客付的账。如果你能给每位顾客正确找零,返回 true ,否则返回 false

输入:bills = [5,5,5,10,20]
输出:true
解释:
前 3 位顾客那里,我们按顺序收取 3 张 5 美元的钞票。
第 4 位顾客那里,我们收取一张 10 美元的钞票,并返还 5 美元。
第 5 位顾客那里,我们找还一张 10 美元的钞票和一张 5 美元的钞票。
由于所有客户都得到了正确的找零,所以我们输出 true。
public boolean lemonadeChange(int[] bills) {
    
    
    int[] money = new int[5];
    for (int i = 0; i < bills.length; i++) {
    
    
        if (bills[i] == 5) money[5]++;
        else if (bills[i] == 10) {
    
    
            if (money[5] < 0) return false;
            money[10]++;
            money[5]--;
        } else if (bills[i] == 20) {
    
    
            if (money[5] > 0 && money[10] > 0) {
    
    
                money[5]--;
                money[10]--;
            } else if (money[5] >= 3) {
    
    
                money[5] -= 3;
            } else return false;
        }
    }
    return true;
}

根据身高重建队列

题目:406. 根据身高重建队列 - 力扣(LeetCode)

假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好ki 个身高大于或等于 hi 的人。
请你重新构造并返回输入数组 people 所表示的队列。返回的队列应该格式化为数组 queue ,其中 queue[j] = [hj, kj] 是队列中第 j 个人的属性(queue[0] 是排在队列前面的人)。

输入: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) {
    
    
    // 先按身高从高到低排序, 身高一样再按 k 从低到高排序
    Arrays.sort(people, (a, b) -> {
    
    
        if (a[0] == b[0]) return a[1] - b[1];
        return b[0] - a[0];
    });

    LinkedList<int[]> q = new LinkedList<>();
    for (int[] p : people) q.add(p[1], p); // 往指定位置插入元素

    return q.toArray(new int[people.length][]);
}

----- 区间问题 -----

用最少数量的箭引爆气球*

题目:452. 用最少数量的箭引爆气球 - 力扣(LeetCode)

有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points ,其中points[i] = [x_start, x_end] 表示水平直径在 x_startx_end之间的气球。你不知道气球的确切 y 坐标。
一支弓箭可以沿着 x 轴从不同点 完全垂直 地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 x_startx_end, 且满足 x_start ≤ x ≤ x_end,则该气球会被 引爆 。可以射出的弓箭的数量 没有限制 。 弓箭一旦被射出之后,可以无限地前进。
给你一个数组 points返回引爆所有气球所必须射出的 最小 弓箭数

输入:points = [[10,16],[2,8],[1,6],[7,12]]
输出:2
解释:气球可以用2支箭来爆破:
- 在 x = 6 处射出箭,击破气球 [2,8] 和 [1,6]。
- 在 x = 11 处发射箭,击破气球 [10,16] 和 [7,12]。

贪心:这题本质上是找最大重叠区间

  • 以左边界升序排序
  • 重叠时,上一气球和当前气球视为一个区间,更新该区间的最小右边界
  • 不重叠时,上一气球和当前气球为两个不同的区间,箭的数量 + 1,最小右边界更新为新区间的

public int findMinArrowShots(int[][] points) {
    
    
	// 以左边界升序排序
	Arrays.sort(points, (a, b) -> Integer.compare(a[0], b[0]));

	int res = 1; // 至少需要一支箭
	int end = points[0][1]; // 折叠气球的最小右边界
	for (int i = 1; i < points.length; i++) {
    
    
		// 当前气球的左边界 > 最小右边界, 说明不重叠, 箭 + 1
		if (points[i][0] > end) {
    
    
			res++;
			end = points[i][1];
		}
		// 当前气球的左边界 <= 最小右边界, 说明重叠, 更新最小右边界
		else end = Math.min(end, points[i][1]);
	}
	return res;
}

无重叠区间*

题目:435. 无重叠区间 - 力扣(LeetCode)

给定一个区间的集合 intervals ,其中 intervals[i] = [starti, endi] 。返回 需要移除区间的最小数量,使剩余区间互不重叠

输入: intervals = [[1,2],[2,3],[3,4],[1,3]]
输出: 1
解释: 移除 [1,3] 后,剩下的区间没有重叠。

贪心:

  • 以右边界升序排序
  • 计算最多能组成的不重叠的区间个数,然后用区间总个数减去不重叠区间的个数

public int eraseOverlapIntervals(int[][] intervals) {
    
    
	// 以右边界升序排序
    Arrays.sort(intervals, (a, b) -> Integer.compare(a[1], b[1]));

	int cnt = 1; // 不重叠的区间个数
    int end = intervals[0][1]; // 
    for (int i = 0; i < intervals.length; i++) {
    
    
        // 重叠
        if (intervals[i][0] < end) continue;
        // 未重叠
        end = intervals[i][1];
        cnt++;
    }

    return intervals.length - cnt;
}

划分字母区间

题目:763. 划分字母区间 - 力扣(LeetCode)

字符串 S 由小写字母组成。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。返回一个表示每个字符串片段的长度的列表。

输入:S = "ababcbacadefegdehijhklij"
输出:[9,7,8]
解释:
划分结果为 "ababcbaca", "defegde", "hijhklij"。
每个字母最多出现在一个片段中。
像 "ababcbacadefegde", "hijhklij" 的划分是错误的,因为划分的片段数较少。

哈希 + 贪心:

  • 记录每个小写字母 最后出现的位置
  • 遍历时更新区间能到达的最远位置,如果当前遍历位置即为最远,则找到一个划分

public List<Integer> partitionLabels(String s) {
    
    
    List<Integer> res = new ArrayList<>();
    
    int[] idx = new int[128]; // 记录每个小写字母最后出现的位置
    for (int i = 0; i < s.length(); i++) idx[s.charAt(i)] = i;

    int start = 0, end = 0; // 当前区间的开始与结束位置
    for (int i = 0; i < s.length(); i++) {
    
    
        end = Math.max(end, idx[s.charAt(i)]); // 更新当前区间的能到的最远距离
        if (end == i) {
    
     // 到达当前区间能到的最远距离, 算是找到一个划分
            res.add(end - start + 1);
            start = end + 1; // 当前区间变为一个新的区间
        }
    }
    return res;
}

合并区间

题目:56. 合并区间 - 力扣(LeetCode)

以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间

输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].

遍历时判断当前区间能否和上一个区间合并:

public int[][] merge(int[][] intervals) {
    
    
    Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0])); 
    List<int[]> list = new ArrayList<>();
    // 每个区间的 起点 和 终点
    int start = intervals[0][0], end = intervals[0][1];
    for (int i = 1; i < intervals.length; i++) {
    
    
        if (intervals[i][0] <= end) {
    
     // 当前区间可以和上一个区间合并
            end = Math.max(end, intervals[i][1]);
        } else {
    
     // 当前区间无法和上一个区间合并
            list.add(new int[] {
    
     start, end }); // 添加当前区间
            start = intervals[i][0]; // 更新起点
            end = intervals[i][1]; // 更新终点
        }
    }
    list.add(new int[] {
    
     start, end }); // 最后一个区间

    return list.toArray(new int[list.size()][]);
}

----------

单调递增的数字

题目:738. 单调递增的数字 - 力扣(LeetCode)

思路:从右往左扫描,若当前数字比其左边一位小,则把左边一位数字减1,并将该位及其右边所有位改成9

public int monotoneIncreasingDigits(int n) {
    
    
	char[] cs = String.valueOf(n).toCharArray();
	// 从右往左扫描, 若当前数字比其左边一位小
	// 
	int start = cs.length; // 标记哪一位开始要变成9
	for (int i = cs.length - 1; i >= 1; i--) {
    
    
		if (cs[i] < cs[i - 1]) {
    
    
			cs[i - 1]--;
			start = i;
		}
	}
	for (int i = start; i < cs.length; i++) cs[i] = '9';
	return Integer.parseInt(new String(cs));
}

买卖股票的最佳时机含手续费

题目:714. 买卖股票的最佳时机含手续费 - 力扣(LeetCode)

public int maxProfit(int[] prices, int fee) {
    
    
	int n = prices.length;
	int[] sell = new int[n]; // 第 i 天卖股票的最高利润
	int[] buy = new int[n]; // 第 i 天买股票的最高利润 
	buy[0] = -prices[0];
	for (int i = 1; i < n; i++) {
    
    
		// 今天卖股票的最高利润: [卖] 或 [不卖] 两种行为做比较
		// [卖]: 之前买的最高利润 + 今天卖的钱 - 手续费
		// [不卖]: 之前卖过的最高利润
		sell[i] = Math.max(buy[i - 1] + prices[i] - fee, sell[i - 1]);
		// 今天买股票的最高利润: [买] 或 [不买] 两种行为做比较
		// [买]: 之前卖的最高利润 - 今天买的钱
		// [不买]: 之前买过的最高利润
		buy[i] = Math.max(sell[i - 1] - prices[i], buy[i - 1]);
	}
	return sell[n - 1];
}

监控二叉树

题目:968. 监控二叉树 - 力扣(LeetCode)

此题先跳过。

猜你喜欢

转载自blog.csdn.net/weixin_43734095/article/details/126852947