
贪心算法(Greedy Algorithm)是一种在解决问题时总是做出在当前看来最好选择的算法。也就是说,贪心算法并不从整体最优上加以考虑,它所做出的选择只是在某种意义上的局部最优选择。
贪心算法的核心思想是:通过局部最优的选择,期望能够导致全局最优的结果。虽然贪心算法不能保证在所有情况下都能得到全局最优解,但在许多问题中,贪心算法能够产生全局最优解,或者在可以接受的时间复杂度内得到一个较好的近似解。
贪心算法具有以下特点:
贪心算法适用于具有以下性质的问题:
常见的贪心算法应用场景包括:
贪心算法与其他算法的主要区别如下:
贪心算法的关键在于如何选择贪心策略,即如何在每一步做出局部最优选择。常见的贪心策略包括:
要证明贪心算法能够得到全局最优解,通常需要证明以下两个性质:
使用贪心算法解决问题的一般步骤如下:
题目描述:假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。对每个孩子 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 class Solution {
public int findContentChildren(int[] g, int[] s) {
// 将孩子的胃口值和饼干的尺寸都排序
Arrays.sort(g);
Arrays.sort(s);
int childIndex = 0; // 当前考虑的孩子的索引
int cookieIndex = 0; // 当前考虑的饼干的索引
// 遍历所有的饼干,尝试满足尽可能多的孩子
while (childIndex < g.length && cookieIndex < s.length) {
// 如果当前饼干可以满足当前孩子的胃口,给这个孩子,并考虑下一个孩子
if (s[cookieIndex] >= g[childIndex]) {
childIndex++;
}
// 无论是否满足当前孩子,都考虑下一块饼干
cookieIndex++;
}
// 返回满足的孩子的数量
return childIndex;
}
}题目描述:给定一个数组 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 。
解题思路:我们可以使用贪心算法来解决这个问题。贪心策略是:只要今天的价格比昨天高,就进行一次买卖交易。这样可以保证每次交易都能获得正利润,从而最大化总利润。
public class Solution {
public int maxProfit(int[] prices) {
int maxProfit = 0;
// 遍历价格数组,只要今天的价格比昨天高,就进行一次买卖交易
for (int i = 1; i < prices.length; i++) {
// 如果今天的价格比昨天高,就可以获利
if (prices[i] > prices[i - 1]) {
maxProfit += prices[i] - prices[i - 1];
}
}
return maxProfit;
}
}题目描述:给定一个非负整数数组 nums ,你最初位于数组的第一个位置。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个位置。
示例: 输入:nums = [2,3,1,1,4] 输出:true 解释:可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 1 跳 3 步到达最后一个位置。
解题思路:我们可以使用贪心算法来解决这个问题。贪心策略是:维护一个最远可到达的位置,每次更新这个最远可到达的位置。如果最远可到达的位置大于等于数组的最后一个位置,则说明可以到达最后一个位置。
public class Solution {
public boolean canJump(int[] nums) {
int maxReach = 0; // 最远可到达的位置
// 遍历数组中的每个位置
for (int i = 0; i < nums.length; i++) {
// 如果当前位置已经超过了最远可到达的位置,说明无法到达最后一个位置
if (i > maxReach) {
return false;
}
// 更新最远可到达的位置
maxReach = Math.max(maxReach, i + nums[i]);
// 如果最远可到达的位置已经大于等于数组的最后一个位置,说明可以到达最后一个位置
if (maxReach >= nums.length - 1) {
return true;
}
}
// 遍历完数组后,如果最远可到达的位置大于等于数组的最后一个位置,说明可以到达最后一个位置
return maxReach >= nums.length - 1;
}
}题目描述:给定一个非负整数数组,你最初位于数组的第一个位置。数组中的每个元素代表你在该位置可以跳跃的最大长度。你的目标是使用最少的跳跃次数到达数组的最后一个位置。假设你总是可以到达数组的最后一个位置。
示例: 输入:nums = [2,3,1,1,4] 输出:2 解释:跳到最后一个位置的最小跳跃数是 2。 从位置 0 跳到位置 1,跳 1 步,然后跳 3 步到达数组的最后一个位置。
解题思路:我们可以使用贪心算法来解决这个问题。贪心策略是:在每一步,我们都选择能够到达最远位置的下一步。具体来说,我们维护当前可以到达的最远位置和下一次可以到达的最远位置,当我们到达当前可以到达的最远位置时,跳跃次数加1,并更新当前可以到达的最远位置为下一次可以到达的最远位置。
public class Solution {
public int jump(int[] nums) {
int jumps = 0; // 跳跃次数
int currentEnd = 0; // 当前可以到达的最远位置
int currentMax = 0; // 下一次可以到达的最远位置
// 遍历数组中的每个位置(除了最后一个位置,因为一旦到达最后一个位置,就不需要再跳跃了)
for (int i = 0; i < nums.length - 1; i++) {
// 更新下一次可以到达的最远位置
currentMax = Math.max(currentMax, i + nums[i]);
// 如果到达了当前可以到达的最远位置,跳跃次数加1,并更新当前可以到达的最远位置
if (i == currentEnd) {
jumps++;
currentEnd = currentMax;
// 如果当前可以到达的最远位置已经大于等于数组的最后一个位置,提前结束循环
if (currentEnd >= nums.length - 1) {
break;
}
}
}
return jumps;
}
}题目描述:老师想给孩子们分发糖果,有 N 个孩子站成了一条直线,老师会根据每个孩子的表现,预先给他们评分。你需要按照以下要求,帮助老师给这些孩子分发糖果:
示例: 输入:[1,0,2] 输出:5 解释:你可以分别给这三个孩子分发 2、1、2 颗糖果。
解题思路:我们可以使用贪心算法来解决这个问题。贪心策略是:分两次遍历数组,第一次从左到右,确保每个孩子的糖果数不小于其左侧孩子的糖果数(如果评分更高);第二次从右到左,确保每个孩子的糖果数不小于其右侧孩子的糖果数(如果评分更高)。这样可以保证所有的约束条件都得到满足。
public class Solution {
public int candy(int[] ratings) {
int n = ratings.length;
int[] candies = new int[n];
// 第一次遍历,从左到右,确保每个孩子的糖果数不小于其左侧孩子的糖果数(如果评分更高)
for (int i = 0; i < n; i++) {
// 每个孩子至少分配到1个糖果
candies[i] = 1;
// 如果当前孩子的评分高于左侧孩子的评分,当前孩子的糖果数应比左侧孩子多1
if (i > 0 && ratings[i] > ratings[i - 1]) {
candies[i] = candies[i - 1] + 1;
}
}
// 第二次遍历,从右到左,确保每个孩子的糖果数不小于其右侧孩子的糖果数(如果评分更高)
for (int i = n - 2; i >= 0; i--) {
// 如果当前孩子的评分高于右侧孩子的评分,当前孩子的糖果数应比右侧孩子多1
// 注意:这里需要取较大的值,以确保同时满足左侧和右侧的约束条件
if (ratings[i] > ratings[i + 1]) {
candies[i] = Math.max(candies[i], candies[i + 1] + 1);
}
}
// 计算需要准备的最小糖果数目
int total = 0;
for (int candy : candies) {
total += candy;
}
return total;
}
}题目描述:给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。
示例: 输入:[[1,2],[2,3],[3,4],[1,3]] 输出:1 解释:移除 [1,3] 后,剩下的区间没有重叠。
解题思路:我们可以使用贪心算法来解决这个问题。贪心策略是:按照区间的结束时间排序,然后每次选择结束时间最早的区间,这样可以给后面的区间留下更多的空间。具体来说,我们维护一个当前选择的区间的结束时间,然后遍历排序后的区间,如果当前区间的开始时间大于等于当前选择的区间的结束时间,说明这两个区间不重叠,我们选择当前区间,并更新当前选择的区间的结束时间。最终,需要移除的区间的最小数量等于总区间数减去选择的区间数。
public class Solution {
public int eraseOverlapIntervals(int[][] intervals) {
if (intervals == null || intervals.length == 0) {
return 0;
}
// 按照区间的结束时间排序
Arrays.sort(intervals, (a, b) -> a[1] - b[1]);
int count = 1; // 选择的区间数,至少有一个区间
int end = intervals[0][1]; // 当前选择的区间的结束时间
// 遍历排序后的区间
for (int i = 1; i < intervals.length; i++) {
// 如果当前区间的开始时间大于等于当前选择的区间的结束时间,说明这两个区间不重叠
if (intervals[i][0] >= end) {
count++;
end = intervals[i][1];
}
}
// 需要移除的区间的最小数量等于总区间数减去选择的区间数
return intervals.length - count;
}
}题目描述:在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。由于它是水平的,所以纵坐标并不重要,因此只要知道开始和结束的横坐标就足够了。开始坐标总是小于结束坐标。一支弓箭可以沿着x轴从不同点完全垂直地射出。在坐标x处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足 xstart ≤ x ≤ xend,则该气球会被引爆。可以射出的弓箭的数量没有限制。弓箭一旦被射出之后,可以无限地前进。我们想找到使得所有气球全部被引爆,所需的弓箭的最小数量。
示例: 输入:points = [[10,16],[2,8],[1,6],[7,12]] 输出:2 解释:对于该样例,x = 6 可以引爆 [2,8],[1,6] 两个气球,以及 x = 11 可以引爆另外两个气球。
解题思路:我们可以使用贪心算法来解决这个问题。这个问题与无重叠区间问题类似,贪心策略是:按照气球的结束坐标排序,然后每次选择结束坐标最小的气球,并在该气球的结束坐标处射出一支箭,这样可以引爆尽可能多的气球。具体来说,我们维护一个当前射出的箭的位置,然后遍历排序后的气球,如果当前气球的开始坐标大于当前射出的箭的位置,说明需要射出一支新的箭,并更新当前射出的箭的位置为当前气球的结束坐标。
public class Solution {
public int findMinArrowShots(int[][] points) {
if (points == null || points.length == 0) {
return 0;
}
// 按照气球的结束坐标排序
// 注意:这里使用Integer.compare来避免溢出
Arrays.sort(points, (a, b) -> Integer.compare(a[1], b[1]));
int count = 1; // 射出的箭的数量,至少需要一支箭
int arrowPos = points[0][1]; // 当前射出的箭的位置
// 遍历排序后的气球
for (int i = 1; i < points.length; i++) {
// 如果当前气球的开始坐标大于当前射出的箭的位置,说明需要射出一支新的箭
if (points[i][0] > arrowPos) {
count++;
arrowPos = points[i][1];
}
}
return count;
}
}题目描述:在一条环路上有 N 个加油站,其中第 i 个加油站有汽油 gas[i] 升。你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。如果你可以绕环路行驶一周,则返回出发时的加油站的编号,否则返回 -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 可为起始索引。
解题思路:我们可以使用贪心算法来解决这个问题。贪心策略是:如果总的汽油量大于等于总的消耗量,那么一定存在一个起点,可以绕环路行驶一周。具体来说,我们维护两个变量,一个是当前的剩余油量,另一个是总的剩余油量。如果当前的剩余油量小于0,说明从当前起点到当前位置的路径不可行,我们将起点设置为下一个位置,并重置当前的剩余油量。遍历完数组后,如果总的剩余油量大于等于0,返回起点,否则返回-1。
public class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
int n = gas.length;
int totalSurplus = 0; // 总的剩余油量
int currentSurplus = 0; // 当前的剩余油量
int startStation = 0; // 起始加油站的编号
// 遍历所有的加油站
for (int i = 0; i < n; i++) {
// 计算当前加油站的剩余油量
int surplus = gas[i] - cost[i];
// 更新总的剩余油量和当前的剩余油量
totalSurplus += surplus;
currentSurplus += surplus;
// 如果当前的剩余油量小于0,说明从startStation到i的路径不可行
if (currentSurplus < 0) {
// 将起点设置为下一个位置
startStation = i + 1;
// 重置当前的剩余油量
currentSurplus = 0;
}
}
// 如果总的剩余油量大于等于0,说明存在一个起点,可以绕环路行驶一周
return totalSurplus >= 0 ? startStation : -1;
}
}题目描述:假设有打乱顺序的一群人站成一个队列,数组 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]] 是重新构造后的队列。
解题思路:我们可以使用贪心算法来解决这个问题。贪心策略是:按照身高降序排序,如果身高相同,则按照前面的人数升序排序。然后,我们按照排序后的顺序,依次将每个人插入到队列的指定位置。具体来说,对于每个人 [h, k],我们将其插入到队列的第 k 个位置,这样可以保证前面正好有 k 个身高大于或等于 h 的人。
public class Solution {
public int[][] reconstructQueue(int[][] people) {
if (people == null || people.length == 0) {
return new int[0][0];
}
// 按照身高降序排序,如果身高相同,则按照前面的人数升序排序
Arrays.sort(people, (a, b) -> {
if (a[0] != b[0]) {
return b[0] - a[0]; // 身高降序
} else {
return a[1] - b[1]; // 前面的人数升序
}
});
// 构建队列
List<int[]> queue = new ArrayList<>();
for (int[] person : people) {
// 将每个人插入到队列的第k个位置
queue.add(person[1], person);
}
// 将队列转换为数组并返回
return queue.toArray(new int[queue.size()][]);
}
}题目描述:当且仅当每个相邻位数上的数字 x 和 y 满足 x <= y 时,我们称这个整数是单调递增的。给定一个非负整数 N,找出小于或等于 N 的最大的整数,且数字呈单调递增。
示例: 输入:N = 10 输出:9
解题思路:我们可以使用贪心算法来解决这个问题。贪心策略是:从右向左遍历数字的每一位,如果当前位的数字大于下一位的数字,就将当前位的数字减1,并将下一位及之后的所有位的数字都设置为9。这样可以保证得到的数字是小于或等于N的最大的单调递增数字。
public class Solution {
public int monotoneIncreasingDigits(int N) {
// 将数字转换为字符数组,方便操作
char[] digits = String.valueOf(N).toCharArray();
int n = digits.length;
int flag = n; // 标记从哪一位开始需要设置为9
// 从右向左遍历数字的每一位
for (int i = n - 1; i > 0; i--) {
// 如果当前位的数字大于下一位的数字,需要调整
if (digits[i - 1] > digits[i]) {
digits[i - 1]--; // 当前位的数字减1
flag = i; // 更新标记,从当前位的下一位开始需要设置为9
}
}
// 将标记位置及之后的所有位的数字都设置为9
for (int i = flag; i < n; i++) {
digits[i] = '9';
}
// 将字符数组转换为整数并返回
return Integer.parseInt(new String(digits));
}
}题目描述:给你一个以字符串表示的非负整数 num 和一个整数 k ,移除这个数中的 k 位数字,使得剩下的数字最小。请你以字符串形式返回这个最小的数字。
示例: 输入:num = “1432219”, k = 3 输出:“1219” 解释:移除掉三个数字 4, 3, 和 2 形成一个新的最小的数字 1219。
解题思路:我们可以使用贪心算法来解决这个问题。贪心策略是:使用一个栈来维护当前的数字序列,遍历数字的每一位,如果当前位的数字小于栈顶的数字,且还可以移除数字(k > 0),就弹出栈顶的数字,并将k减1。遍历完所有数字后,如果k仍然大于0,就继续弹出栈顶的数字,直到k等于0。最后,将栈中的数字转换为字符串,并移除前导零。
public class Solution {
public String removeKdigits(String num, int k) {
// 特殊情况处理
if (num.length() == k) {
return "0";
}
// 使用栈来维护当前的数字序列
Stack<Character> stack = new Stack<>();
// 遍历数字的每一位
for (char digit : num.toCharArray()) {
// 如果当前位的数字小于栈顶的数字,且还可以移除数字,就弹出栈顶的数字
while (!stack.isEmpty() && stack.peek() > digit && k > 0) {
stack.pop();
k--;
}
// 将当前位的数字压入栈中
stack.push(digit);
}
// 如果k仍然大于0,继续弹出栈顶的数字
while (k > 0) {
stack.pop();
k--;
}
// 将栈中的数字转换为字符串
StringBuilder result = new StringBuilder();
while (!stack.isEmpty()) {
result.append(stack.pop());
}
// 反转字符串,因为栈是后进先出的
result.reverse();
// 移除前导零
while (result.length() > 1 && result.charAt(0) == '0') {
result.deleteCharAt(0);
}
return result.toString();
}
}
// 更高效的实现,使用StringBuilder代替Stack
public class Solution {
public String removeKdigits(String num, int k) {
// 特殊情况处理
if (num.length() == k) {
return "0";
}
// 使用StringBuilder来模拟栈,维护当前的数字序列
StringBuilder stack = new StringBuilder();
// 遍历数字的每一位
for (char digit : num.toCharArray()) {
// 如果当前位的数字小于栈顶的数字,且还可以移除数字,就删除栈顶的数字
while (stack.length() > 0 && stack.charAt(stack.length() - 1) > digit && k > 0) {
stack.deleteCharAt(stack.length() - 1);
k--;
}
// 将当前位的数字添加到栈中
stack.append(digit);
}
// 如果k仍然大于0,继续删除栈顶的数字
while (k > 0) {
stack.deleteCharAt(stack.length() - 1);
k--;
}
// 移除前导零
int start = 0;
while (start < stack.length() && stack.charAt(start) == '0') {
start++;
}
// 如果所有数字都是零,返回"0"
if (start == stack.length()) {
return "0";
}
return stack.substring(start);
}
}题目描述:给定长度分别为 m 和 n 的两个数组,其元素由 0-9 构成,表示两个自然数各位上的数字。现在从这两个数组中选出 k (k <= m + n) 个数字拼接成一个新的数,要求从同一个数组中取出的数字保持其在原数组中的相对顺序。求满足该条件的最大数。结果返回一个表示该最大数的长度为 k 的数组。
示例: 输入: nums1 = [3, 4, 6, 5] nums2 = [9, 1, 2, 5, 8, 3] k = 5 输出:[9, 8, 6, 5, 3]
解题思路:我们可以使用贪心算法来解决这个问题。贪心策略是:枚举从第一个数组中选取的数字的个数 i(i的取值范围是 max(0, k - n) 到 min(m, k)),然后从第二个数组中选取的数字的个数是 k - i。对于每个 i,我们从第一个数组中选取 i 个数字,保持相对顺序,使得拼接后的数字最大;从第二个数组中选取 k - i 个数字,保持相对顺序,使得拼接后的数字最大。然后,将这两个子数组合并成一个最大的数组。最后,在所有可能的 i 中,选择合并后的最大的数组作为答案。
public class Solution {
public int[] maxNumber(int[] nums1, int[] nums2, int k) {
int m = nums1.length;
int n = nums2.length;
int[] result = new int[0];
// 枚举从第一个数组中选取的数字的个数i
for (int i = Math.max(0, k - n); i <= Math.min(m, k); i++) {
int j = k - i; // 从第二个数组中选取的数字的个数
// 从第一个数组中选取i个数字,保持相对顺序,使得拼接后的数字最大
int[] subsequence1 = maxSubsequence(nums1, i);
// 从第二个数组中选取j个数字,保持相对顺序,使得拼接后的数字最大
int[] subsequence2 = maxSubsequence(nums2, j);
// 合并这两个子数组成一个最大的数组
int[] merged = merge(subsequence1, subsequence2);
// 更新结果,选择合并后的最大的数组
if (compare(merged, 0, result, 0) > 0) {
result = merged;
}
}
return result;
}
// 从数组nums中选取k个数字,保持相对顺序,使得拼接后的数字最大
private int[] maxSubsequence(int[] nums, int k) {
if (k == 0) {
return new int[0];
}
int n = nums.length;
int[] stack = new int[k];
int top = -1;
int remain = n - k; // 可以删除的数字的个数
for (int i = 0; i < n; i++) {
// 如果当前数字大于栈顶的数字,且还可以删除数字,就弹出栈顶的数字
while (top >= 0 && stack[top] < nums[i] && remain > 0) {
top--;
remain--;
}
// 如果栈还没有满,将当前数字压入栈中
if (top < k - 1) {
stack[++top] = nums[i];
} else {
remain--;
}
}
return stack;
}
// 合并两个子数组成一个最大的数组,保持各自的相对顺序
private int[] merge(int[] nums1, int[] nums2) {
int m = nums1.length;
int n = nums2.length;
int[] result = new int[m + n];
int i = 0, j = 0, k = 0;
while (i < m && j < n) {
// 比较两个子数组的剩余部分,选择较大的那个的当前数字
if (compare(nums1, i, nums2, j) > 0) {
result[k++] = nums1[i++];
} else {
result[k++] = nums2[j++];
}
}
// 处理剩余的数字
while (i < m) {
result[k++] = nums1[i++];
}
while (j < n) {
result[k++] = nums2[j++];
}
return result;
}
// 比较两个子数组的剩余部分,从索引i和j开始
private int compare(int[] nums1, int i, int[] nums2, int j) {
int m = nums1.length;
int n = nums2.length;
while (i < m && j < n) {
if (nums1[i] != nums2[j]) {
return nums1[i] - nums2[j];
}
i++;
j++;
}
// 如果一个子数组是另一个子数组的前缀,较长的子数组更大
return (m - i) - (n - j);
}
}设计贪心策略是解决贪心问题的关键,以下是一些常用的设计技巧:
证明贪心算法能够得到全局最优解是使用贪心算法的重要步骤,以下是一些常用的证明技巧:
在使用贪心算法时,需要注意以下常见的陷阱:
贪心算法是一种重要的算法思想,它通过在每一步做出局部最优选择,期望能够导致全局最优的结果。贪心算法具有简单、高效的特点,在许多问题中都有广泛的应用。
在本文中,我们介绍了贪心算法的基本概念、特点、适用场景,以及与其他算法的区别。我们还介绍了贪心策略的选择、贪心算法的证明方法和解题步骤,并通过LeetCode中的题目详细解析了贪心算法的应用。
贪心算法的关键在于如何设计贪心策略,以及如何证明该贪心策略能够得到全局最优解。常见的贪心策略包括排序后选择、优先级队列选择和贪心条件判断等。常见的证明方法包括反证法、数学归纳法和交换论证等。
随着计算机科学的发展,贪心算法的应用也在不断扩展。例如,在人工智能领域,贪心算法被用于启发式搜索;在网络领域,贪心算法被用于路由选择;在调度领域,贪心算法被用于任务调度等。