如果找出局部最优并可以推出全局最优,就是贪心,如果局部最优都没找出来,就不是贪心,可能是单纯的模拟。
11. 盛最多水的容器
双指针,有贪心的思想
var maxArea = function(height) {let left=0,right=height.length-1;let result=0;while(left<right){let ans=Math.min(height[left],height[right])*(right-left)result=Math.max(result,ans)if(height[left]<=height[right]){left++}else{right--;}}return result;};
455. 分发饼干
这题有点常识判断。
排序,尽可能小饼干给小胃口的人吃
var findContentChildren = function(g, s) {//将g,s排序g.sort((a,b)=>a-b);s.sort((a,b)=>a-b);let gi=0;;for(let n of s){if(n>=g[gi]){gi++;}}return gi;};
1005. K 次取反后最大化的数组和
这题有点常识判断。
贪心的思路,局部最优:让绝对值大的负数变为正数,当前数值达到最大,整体最优:整个数组和达到最大。
那么如果将负数都转变为正数了,K依然大于0,此时的问题是一个有序正整数序列,如何转变K次正负,让 数组和达到最大。那么又是一个贪心:局部最优:只找数值最小的正整数进行反转,当前数值可以达到最大。
var largestSumAfterKNegations = function(nums, k) {let newNums=nums.sort((a,b)=> a-b);let count=k;let sum=0;for(let i=0;i<newNums.length;i++){if(count>0 && newNums[i]<0){newNums[i]*=-1count--}sum+=newNums[i]}if(count>0 && count%2!==0){newNums=newNums.sort((a,b)=> a-b);;sum-=2*newNums[0];}return sum;};
860. 柠檬水找零
这题有点常识判断。
针对不同付钱5/10/20有不同找零策略,20时运用到贪心。
局部最优:遇到账单20,优先消耗美元10,如果不够,再消耗三个5,完成本次找零。全局最优:完成全部账单的找零。
因为美元10只能给账单20找零,而美元5可以给账单10和账单20找零,美元5更万能!
/*** @param {number[]} bills* @return {boolean}*/var lemonadeChange = function(bills) {let five=0,ten=0;for(let i=0;i<bills.length;i++){const bill=bills[i];if(bill===5) five++;if(bill===10){if(five>0){five--;ten++;}else{return false;}}if(bill===20){if(ten>0 && five>0){ten--;five--;}else if(five>2){five=five-3;}else{return false;}}}return true;};
376. 摆动序列
局部最优:删除单调坡度上的节点(不包括单调坡度两端的节点),那么这个坡度就可以有两个局部峰值。
整体最优:整个序列有最多的局部峰值,从而达到最长摆动序列。
感觉与买卖股票类似, 还可以用动态规划来做
var wiggleMaxLength = function(nums) {let count=0;let pre=0,cur=0;for(let i=1;i<nums.length;i++){let cur=nums[i]-nums[i-1]if(cur===0 || (cur>0 && pre>0 )|| (cur<0 && pre<0)) {//与前一个值作比较,不满足则跳过坡度上节点count++;}else{pre=cur; //如果满足摆动,则更新pre}}return nums.length-count;};
738. 单调递增的数字
局部最优:遇到strNum[i - 1] > strNum[i]的情况,让strNum[i - 1]—,然后strNum[i]给为9,可以保证这两位变成最大单调递增整数。
全局最优:得到小于等于N的最大单调递增的整数。
还需要考虑从前往后还是从后往前的遍历顺序
var monotoneIncreasingDigits = function (n) {let array = n.toString().split('').map((i) => {return +i;})let flag = Infinityfor (let i = array.length - 2; i >= 0; i--) {if (array[i] > array[i + 1]) {flag = i + 1array[i] = array[i] - 1;}}for (let i = flag; i < array.length; i++) { //100的情况array[i] = 9}return +array.join('')};
134. 加油站
局部最优:当前累加rest[j]的和curSum一旦小于0,起始位置至少要是j+1,因为从j开始一定不行。全局最优:找到可以跑一圈的起始位置。
var canCompleteCircuit = function(gas, cost) {let allSum=0;let curSum=0;let start=0;for(let i=0;i<gas.length;i++){curSum+=(gas[i]-cost[i])allSum+=(gas[i]-cost[i])if(curSum<0){start=i+1curSum=0;}}if(allSum<0) return -1;return start;};
两个维度权衡问题
在出现两个维度相互影响的情况时,两边一起考虑一定会顾此失彼,要先确定一个维度,再确定另一个一个维度。
135. 分发糖果
有点类似前后缀乘积那道题
这道题目一定是要确定一边之后,再确定另一边,例如比较每一个孩子的左边,然后再比较右边,如果两边一起考虑一定会顾此失彼。
先确定右边评分大于左边的情况(也就是从前向后遍历)
此时局部最优:只要右边评分比左边大,右边的孩子就多一个糖果,全局最优:相邻的孩子中,评分高的右孩子获得比左边孩子更多的糖果。
再确定左孩子大于右孩子的情况(从后向前遍历)
最终全局最优:相邻的孩子中,评分高的孩子获得更多的糖果。
/*** @param {number[]} ratings* @return {number}*/var candy = function (ratings) {const length = ratings.lengthlet result = Array(length).fill(1);for (let i = 1; i < length; i++) { //从前向后遍历,如果右边的比左边的大if (ratings[i] > ratings[i - 1]) {result[i] = result[i - 1] + 1}}let sum = result[length - 1];for (let i = length - 2; i >= 0; i--) { //从后向前遍历,如果左边边的比右边的大if (ratings[i] > ratings[i + 1]) {result[i] = Math.max(result[i + 1] + 1, result[i]) //max为了防止[1,3,4,5,2] 11 这种情况}sum += result[i]}return sum;};
406. 根据身高重建队列
有身高和排序两个维度,简化成一个维度分别比较
局部最优:优先按身高高的people的k来插入。插入操作过后的people满足队列属性
全局最优:最后都做完插入操作,整个队列满足题目队列属性
/*** @param {number[][]} people* @return {number[][]}*/var reconstructQueue = function (people) {people.sort((a, b) => {if (a[0] !== b[0]) {return b[0] - a[0]} else {return a[1] - b[1]}})let result = []for (let i = 0; i < people.length; i++) {result.splice(people[i][1], 0, people[i])}return result;};
区间问题
55. 跳跃游戏
贪心算法局部最优解:每次取最大跳跃步数(取最大覆盖范围),整体最优解:最后得到整体最大覆盖范围,看是否能到终点。
var canJump = function(nums) {if(nums.length===1) return true;let cover=0;for(let i=0;i<nums.length;i++){cover=Math.max(i+nums[i],cover);if(cover>=nums.length-1) return true;if(cover===i)return false;}return false;};
45. 跳跃游戏 II
局部最优:当前可移动距离尽可能多走,如果还没到终点,步数再加一。整体最优:一步尽可能多走,从而达到最小步数。
var jump = function(nums) {if(nums.length<2) return 0;let count=0;let curCover=0,nextCover=0;for(let i=0;i<nums.length-1;i++){//i<nums.length-1,因为一定能到终点nextCover=Math.max(i+nums[i],nextCover);if(curCover===i){//走到实在没油了curCover=nextCover//因为一定能到终点,续油count++;}}return count;};
452. 用最少数量的箭引爆气球
画图,找共同区间
局部最优:当气球出现重叠,一起射,所用弓箭最少。全局最优:把所有气球射爆所用弓箭最少。
//更新重叠气球最小右边界var findMinArrowShots = function (points) {points.sort((a, b) => {return a[0] - b[0]})let result = 1;for (let i = 1; i < points.length; i++) {if (points[i][0] <= points[i - 1][1]) {points[i][1] = Math.min(points[i - 1][1], points[i][1])//缩小右边界,尽可能一起射} else {result++;}}return result;};//射一个,气球数组就remove一个元素var findMinArrowShots = function (points) {let res = 0;points.sort((a,b)=>{if(a[0] !== b[0]){return a[0]-b[0];}else{return a[1]-b[1];}})while(points.length){const tmp = points.pop()[0];while(points.length && tmp <= points[points.length-1][1]){points.pop();}res++;}return res;};
435. 无重叠区间
与用最少数量的箭引爆气球类似
为了保证区间不重合,移除数最少的情况下,即剩余数最多;
为了保证剩余数最多,在区间不重合的情况下,尽量让前面的右边界最小,因为前面占用越少,后面余量越多,数量就可能越多;
时间复杂度度:排序O(nlogn),遍历O(n),综合O(nlogn)
空间复杂度:O(logn),为排序占用
var eraseOverlapIntervals = function (intervals) {intervals.sort((a, b) => {return a[0] - b[0]})let result = 0;for (let i = 1; i < intervals.length; i++) {if (intervals[i][0] < intervals[i - 1][1]) {intervals[i][1] = Math.min(intervals[i - 1][1], intervals[i][1])result++;}}return result;};
56. 合并区间
// 按照左边界排序,排序之后局部最优:每次合并都取最大的右边界,这样就可以合并更多的区间了,整体最优:合并所有重叠的区间。var merge = function (intervals) {let result = [];intervals.sort((a, b) => a[0] - b[0]);let pre = intervals[0]for (let i = 1; i < intervals.length; i++) {const cur = intervals[i];if (cur[0] <= pre[1]) {pre[1] = Math.max(cur[1], pre[1])} else {result.push(pre);pre = cur}}result.push(pre);return result;};
763. 划分字母区间
利用该字母最后出现位置
其实这种方法不算贪心没有用到局部全局,可以得到字母出现的区间,然后使用合并区间。
/*** @param {string} s* @return {number[]}*/var partitionLabels = function (s) {let left = 0,right = 0;let result = [];let map = Array(26).fill(0);for (let i = 0; i < s.length; i++) {map[s[i].charCodeAt() - 'a'.charCodeAt()] = i;}for (let i = 0; i < s.length; i++) {right = Math.max(map[s[i].charCodeAt() - 'a'.charCodeAt()], right);if (right === i) {result.push(right - left + 1);left = i + 1;}}return result;};
