leetcode刷题-贪心
保证每次操作都是局部最优的,并且最后得到的结果是全局最优的
1. 分发饼干
题目
假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。
对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
思路
典型的贪心算法
证明:假设在某次选择中,贪心策略选择给当前满足度最小的孩子分配第M个饼干,第M个饼干为可以满足该孩子的最小饼干; 假设存在一种最优策略,给该孩子分配第N个饼干,并且M<N,可以发现,经过这一轮的分配,贪心策略分配后剩下的饼干一定有一个比最优策略来得大。因此,在后续的分配中,贪心策略一定能够满足更多的孩子,也就是说贪心策略也就是最优策略。
var findContentChildren = function(g, s) {
g.sort((a, b) => a - b);
s.sort((a, b) => a - b);
let gi = 0, si = 0;
while(gi < g.length && si < s.length) {
if (g[gi] <= s[si]) {
gi++;
}
si++;
}
return gi;
};
2. 无重叠区间
题目
给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。
注意:
可以认为区间的终点总是大于它的起点。 区间 [1,2] 和 [2,3] 的边界相互“接触”,但没有相互重叠。
思路
- 先计算最多能组成的不重叠的区间个数,然后用区间总个数减去不重叠的区间的个数
- 如何计算出最多的不重叠区间的个数? 贪心:每次选择结尾最小的区间,因为选择的区间结尾越小,留给后面的区间的空间越大,后面能够选择的区间个数也越大
var eraseOverlapIntervals = function(intervals) {
intervals.sort((a, b) => a[1] - b[1]);
let count = 1;
let end = intervals[0][1];
for (let i = 1; i < intervals.length; i++) {
const item = intervals[i];
if (item[0] < end) {
continue;
}
count++;
end = item[1];
}
return intervals.length - count;
};
3. 用最少数量的箭引爆气球
题目
在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。由于它是水平的,所以纵坐标并不重要,因此只要知道开始和结束的横坐标就足够了。开始坐标总是小于结束坐标。
一支弓箭可以沿着 x 轴从不同点完全垂直地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足 xstart ≤ x ≤ xend,则该气球会被引爆。可以射出的弓箭的数量没有限制。 弓箭一旦被射出之后,可以无限地前进。我们想找到使得所有气球全部被引爆,所需的弓箭的最小数量。
给你一个数组 points ,其中 points [i] = [xstart,xend] ,返回引爆所有气球所必须射出的最小弓箭数。
思路
第二题的变种
var findMinArrowShots = function(points) {
points.sort((a, b) => a[1] - b[1]);
let count = 1;
let minEnd = points[0][1];
for (let i = 1; i < points.length; i++) {
const item = points[i];
if (item[0] <= minEnd) {
continue;
}
count++;
minEnd = item[1];
}
return count;
};
4. 根据身高重建队列
题目
假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高大于或等于 hi 的人。
请你重新构造并返回输入数组 people 所表示的队列。返回的队列应该格式化为数组 queue ,其中 queue[j] = [hj, kj] 是队列中第 j 个人的属性(queue[0] 是排在队列前面的人)。
思路
身高降序,K值升序,然后按排好序的顺序插入队列的第K个位置中
var reconstructQueue = function(people) {
people.sort((a, b) => a[0] === b[0] ? a[1] - b[1] : b[0] - a[0])
const resArr = [];
for (let i = 0; i < people.length; i++) {
const item = people[i];
resArr.splice(item[1], 0, item);
}
return resArr;
};
5. 划分字母区间
题目
字符串 S 由小写字母组成。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。返回一个表示每个字符串片段的长度的列表。
var partitionLabels = function (s) {
const letterArr = initArr(s);
letterArr.sort((a, b) => a[0] - b[0]);
let index = 0;
for (let i = 0; i < letterArr.length; i++) {
if (letterArr[i][0] !== -1) {
index = i;
break;
}
}
let begin = letterArr[index][0];
let end = letterArr[index][1];
const res = [];
for (let i = index + 1; i < letterArr.length; i++) {
const item = letterArr[i];
if (item[0] < end) {
end = Math.max(end, item[1]);
} else {
res.push(end - begin + 1);
begin = item[0];
end = item[1];
}
}
res.push(end - begin + 1);
return res;
};
function initArr(s) {
const letterArr = new Array(26);
for (let i = 0; i < 26; i++) {
letterArr[i] = [-1, -1];
}
const lettera = 'a'.charCodeAt();
const sArr = s.split('');
for (let i = 0; i < s.length; i++) {
const code = sArr[i].charCodeAt() - lettera;
if (letterArr[code][0] === -1) {
letterArr[code][0] = i;
}
letterArr[code][1] = i;
}
return letterArr;
}
6、种花问题
题目
假设有一个很长的花坛,一部分地块种植了花,另一部分却没有。可是,花不能种植在相邻的地块上,它们会争夺水源,两者都会死去。
给你一个整数数组 flowerbed 表示花坛,由若干 0 和 1 组成,其中 0 表示没种植花,1 表示种植了花。另有一个数 n ,能否在不打破种植规则的情况下种入 n 朵花?能则返回 true ,不能则返回 false。
var canPlaceFlowers = function(flowerbed, n) {
if (n === 0) return true;
for (let i = 0, len = flowerbed.length; i < len; i++) {
if (flowerbed[i] === 1) { // 等于1的时候,直接跳2格
i += 1;
} else {
// 等于0且能够种花
if ((i + 1 === len) || (i + 1 < len && flowerbed[i + 1] === 0)) {
n--;
if (n === 0) return true;
i += 1;
} else {
// 等于0的时候但是不能种花,直接跳2个格
i += 2;
}
}
}
return n === 0;
};
7、判断子序列
给定字符串 s 和 t ,判断 s 是否为 t 的子序列。
字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。
var isSubsequence = function(s, t) {
const m = s.length, n = t.length;
let i = 0, j = 0;
while(i < m && j < n) {
if (s[i] === t[j]) {
i++;
}
j++;
}
return i === m;
};
8、非递减数列(重要)
给你一个长度为 n 的整数数组,请你判断在 最多 改变 1 个元素的情况下,该数组能否变成一个非递减数列。
我们是这样定义一个非递减数列的: 对于数组中任意的 i (0 <= i <= n-2),总满足 nums[i] <= nums[i + 1]。
var checkPossibility = function(nums) {
const n = nums.length;
let count = 0;
for (let i = 0; i < n; i++) {
const x = nums[i], y = nums[i + 1];
if (x > y) {
count++;
if (count > 1) return false;
if (i > 0 && y < nums[i - 1]) {
nums[i + 1] = x;
}
}
}
return true;
};
9、买卖股票的最佳时机 II
给定一个数组 prices ,其中 prices[i] 是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
var maxProfit = function(prices) {
let sum = 0;
for (let i = 0; i < prices.length; i++) {
if (i + 1 && prices[i + 1] > prices[i]) {
sum += (prices[i + 1] - prices[i]);
}
}
return sum;
};
10、最大子序和
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
思路
动态规划:f(i)表示以i元素为结尾的连续子数组的最大和
f(i) = Math.max(f(i-1) + x, x);
var maxSubArray = function(nums) {
let res = nums[0];
let arr = new Array();
arr[0] = nums[0];
for (let i = 1; i < nums.length; i++) {
arr[i] = Math.max(arr[i - 1] + nums[i], nums[i]);
res = Math.max(res, arr[i]);
}
return res;
};
11、买卖股票的最佳时机
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
var maxProfit = function(prices) {
let res = 0;
let minArr = new Array(prices.length).fill(10001);
minArr[0] = prices[0];
for(let i = 1; i < prices.length; i++) {
minArr[i] = Math.min(minArr[i - 1], prices[i]);
}
for (let i = 0; i < prices.length; i++) {
res = Math.max(prices[i] - minArr[i], res);
}
return res;
};