大厂算法面试之leetcode精讲4.贪心

视频教程(高效学习):点击学习

目录:

1.开篇介绍

2.时间空间复杂度

3.动态规划

4.贪心

5.二分查找

6.深度优先&广度优先

7.双指针

8.滑动窗口

9.位运算

10.递归&分治

11剪枝&回溯

12.堆

13.单调栈

14.排序算法

15.链表

16.set&map

17.栈

18.队列

19.数组

20.字符串

21.树

22.字典树

23.并查集

24.其他类型题

什么是贪心算法

贪心法,又称贪心算法,贪婪算法,在对问题求解时,总是做出在当前看来最好的选择,期望通过每个阶段的局部最优选择达到全局最优,但结果不一定最优

适用场景:简单的说,问题能够分解成子问题来解决,子问题的最优解能递推到最终问题的最优解,就能用贪心算法的到最后的最优解,这种子问题最优解称为最优子结构

贪心算法与动态规划的不同点在于它对每个子问题的解决方案都做出当前的最优选择,不能回退,而动态规划会保留之前的运算结果,并根据之前的结果进行选择,有回退的功能,贪心是动态规划的理想化的情况。

122. 买卖股票的最佳时机 II(medium)

方法1.动态规划

  • 思路:根据题意只能持有一只股票,不限制交易次数,我们可以用动态规划来做,首先定义状态,题中有两个状态,一个是天数,一个是是否持有股票,所以我们定义dp[i][0]表示第 i天交易完后手里没有股票的最大利润,dp[i][1] 表示第 i天交易完后手里持有一支股票的最大利润,接下来就是定义状态转移方程:

    1. 假如当前的状态是dp[i][0],表示手中没股票,则可由前一天的两种情况转移过来,第一种是dp[i-1][0],表示前一天手里没股票,而且今天没做任何操作。第二种是dp[i-1][1],表示前一天持有股票,但是今天卖了,所以收益是dp[i-1][1]+prices[i],我们需要求出这两种情况下的最大值就是最大利润,状态转移方程就是:

      dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);

    2. 假如当前的状态是dp[i][1],表示手中有股票,则可由前一天的两种情况转移过来,第一种是dp[i−1][1],表示前一天手中有股票,即是今天没做任何操作。第二种是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]);

    由上面的状态转移方程我们知道,当前天的最大收益,只与前一天的状态相关,所以我们可以不用定义二维数组来存放状态,只需要将dp[i - 1][0]dp[i - 1][1]存放在变量中。

  • 复杂度分析:时间复杂度:O(n),n是数组长度,每天有持有股票或者没持有两种状态,一共2n的状态转移次数,时间复杂度就是O(2n),时间复杂度和常系数无关,所以时间复杂度就是O(n)。空间复杂度O(n),因为要开辟n的空间存放状态,虽然是二维数组,但是第二维是常数。如果进行了状态压缩,空间复杂度可以优化到O(1)

js:

var maxProfit = function (prices) {const n = prices.length;const dp = new Array(n).fill(0).map((v) => new Array(2).fill(0)); //初始化状态数组(dp[0][0] = 0), (dp[0][1] = -prices[0]); //3.定义初始值for (let i = 1; i < n; ++i) {//1.确定状态//2.推导状态转移方程//当前没持有股票,可由前一天的两种状态转移过了,//1是前一天没持有,今天不动,2是前一天持有,今天卖掉,求这两种情况的较大值dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);//当前持有股票,可由前一天的两种状态转移过了,//1是前一天持有,今天不动,2是前一天没持有,今天买入,求这两种情况的较大值dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);}//4.确定输出值return dp[n - 1][0]; //返回第n-1天的最大值
};//空间压缩
var maxProfit = function (prices) {const n = prices.length;let dp0 = 0,dp1 = -prices[0];for (let i = 1; i < n; ++i) {let newDp0 = Math.max(dp0, dp1 + prices[i]);let newDp1 = Math.max(dp1, dp0 - prices[i]);dp0 = newDp0;dp1 = newDp1;}return dp0;
};

Java:

class Solution {public int maxProfit(int[] prices) {int n = prices.length;int[][] dp = new int[n][2];dp[0][0] = 0;dp[0][1] = -prices[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]);}return dp[n - 1][0];}
}//空间压缩
class Solution {public int maxProfit(int[] prices) {int n = prices.length;int dp0 = 0, dp1 = -prices[0];for (int i = 1; i < n; ++i) {int newDp0 = Math.max(dp0, dp1 + prices[i]);int newDp1 = Math.max(dp1, dp0 - prices[i]);dp0 = newDp0;dp1 = newDp1;}return dp0;}
}
方法2.贪心

  • 思路:因为不限制交易次数,只要今天价格比昨天高,就交易,利润为正累加,最后的和就是最大的利润,注意第一天是没有利润的,这道题之所以可以用贪心是因为局部最优:收集每天的正利润,可以推导出,全局最优:求得最大利润
  • 复杂度分析:时间复杂度O(n),n是数组的长度。空间复杂度是O(1)

js:

var maxProfit = function (prices) {let ans = 0;let n = prices.length;for (let i = 1; i < n; ++i) {//今天价格和昨天的差值是否为正,如果为正累加进去,为负则加0ans += Math.max(0, prices[i] - prices[i - 1]);}return ans;
};

Java:

class Solution {public int maxProfit(int[] prices) {int ans = 0;int n = prices.length;for (int i = 1; i < n; ++i) {ans += Math.max(0, prices[i] - prices[i - 1]);}return ans;}
}

455. 分发饼干 (easy)

  • 思路:大尺寸的饼干既可以满足胃口大的孩子也可以满足胃口小的孩子,那么就应该优先满足胃口大的。排序两个数组,从右到左遍历,用大饼干首先满足胃口大的小孩
  • 复杂度:时间复杂度O(mlogm + nlogn)。空间复杂度O(logm + logn)

js:

var findContentChildren = function (g, s) {g = g.sort((a, b) => a - b);s = s.sort((a, b) => a - b); //排序数组let result = 0;let index = s.length - 1;for (let i = g.length - 1; i >= 0; i--) {//从胃口大的小孩开始满足if (index >= 0 && s[index] >= g[i]) {result++; //结果加1index--;}}return result;
};

java:

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

435. 无重叠区间 (medium)

方法1.动态规划

  • 思路:dp[i]表示前i个区间中最大不重合区间的个数,首先将区间数组按左边界排序,找出intervals中最多有多少个不重复的区间,动态规划方程dp[i] = Math.max(dp[i], dp[j] + 1)。intervals的长度减去最多的不重复的区间 就是最少删除区间的个数
  • 复杂度:时间复杂度O(n^2),两层嵌套循环leetcode执行超时 复杂度过高。空间复杂度O(n),dp数组的空间

js:

//leetcode执行超时 复杂度过高
var eraseOverlapIntervals = function (intervals) {if (!intervals.length) {return 0;}intervals.sort((a, b) => a[0] - b[0]); //按左边界排序const n = intervals.length;const dp = new Array(n).fill(1); //初始化dp数组for (let i = 1; i < n; i++) {for (let j = 0; j < i; j++) {//循环i,j找出intervals中最多有多少个不重复的区间//j的右边界小于i的左边界 相当于多出了一个不重合区间if (intervals[j][1] <= intervals[i][0]) {dp[i] = Math.max(dp[i], dp[j] + 1); //更新dp[i]}}}return n - Math.max(...dp); //n减去最多的不重复的区间 就是最少删除区间的个数
};

java:

class Solution {public int eraseOverlapIntervals(int[][] intervals) {if (intervals.length == 0) {return 0;}Arrays.sort(intervals, new Comparator<int[]>() {public int compare(int[] interval1, int[] interval2) {return interval1[0] - interval2[0];}});int n = intervals.length;int[] dp = new int[n];Arrays.fill(dp, 1);for (int i = 1; i < n; ++i) {for (int j = 0; j < i; ++j) {if (intervals[j][1] <= intervals[i][0]) {dp[i] = Math.max(dp[i], dp[j] + 1);}}}return n - Arrays.stream(dp).max().getAsInt();}
}
方法2.贪心

  • 思路:intervals按右边界排序,然后从左往右遍历,右边界结束的越早,留给后面的区间的空间就越大,不重合的区间个数就越多,intervals的长度减去最多的不重复的区间 就是最少删除区间的个数
  • 复杂度:时间复杂度O(nlogn),数组排序O(nlogn),循环一次数组O(n)。空间复杂度O(logn),排序需要的栈空间

js:

var eraseOverlapIntervals = function (intervals) {if (!intervals.length) {return 0;}//按右边界排序,然后从左往右遍历,右边界结束的越早,留给后面的区间的空间就越大,不重合的区间个数就越多intervals.sort((a, b) => a[1] - b[1]);const n = intervals.length;let right = intervals[0][1]; //right初始化为第一个区间的右边界let ans = 1; //最多的不重合区间的个数for (let i = 1; i < n; ++i) {//循环区间数组if (intervals[i][0] >= right) {//当区间的左边界大于上一个区间的右边界的时候 说明是一对不重合区间++ans; //ans加1right = intervals[i][1]; //更新right}}return n - ans; //intervals的长度减去最多的不重复的区间 就是最少删除区间的个数
};

java:

class Solution {public int eraseOverlapIntervals(int[][] intervals) {if (intervals.length == 0) {return 0;}Arrays.sort(intervals, new Comparator<int[]>() {public int compare(int[] interval1, int[] interval2) {return interval1[1] - interval2[1];}});int n = intervals.length;int right = intervals[0][1];int ans = 1;for (int i = 1; i < n; ++i) {if (intervals[i][0] >= right) {++ans;right = intervals[i][1];}}return n - ans;}
}

能不能用贪心算法需要满足贪心选择性,贪心算法正确的的证明可以用反证法

以这一题为例:

  • 我们的思路是保留最多的不重合的区间,所以按照区间结尾排序,区间结尾结束的越早且和前一个区间不重叠的,就加入最多不重复的区间中,我们称为算法a,假如算法a中的某一个步骤是选择区间[a, b],我们称为区间A。
  • 假设这个选择不正确,也就是说算法a得到的不是最优解。
  • 我们假设存在另一个算法c能得到最优解,算法c中的一个步骤是选择区间[c, d],我们称为区间C,使得它是最优解中的一个区间,其中d>b,因为算法a选择的是结尾最先结束且不重合的区间,如果算法a不正确,又因为区间数组中的区间是固定的,则其他算法c肯定存在d>b的情况。
  • 我们用区间A替换区间C完全不影响算法c的结果,因为b<d,所以不影响区间C后面区间的结果。所以我们选择了区间A也构成了一个最优解。而我们假设的是选择区间A不是最优解,所以和之前的假设矛盾,所以算法a是正确的贪心算法

55. 跳跃游戏 (medium)

方法1.动态规划
  • 思路:dp[i]表示能否到达位置i,对每个位置i判断能否通过前面的位置跳跃过来,当前位置j能达到,并且当前位置j加上能到达的位置如果超过了i,那dp[i]更新为ture,便是i位置也可以到达。
  • 复杂度:时间复杂度O(n^2),空间复杂度O(n)

js:

function canJump(nums) {let dp = new Array(nums.length).fill(false); //初始化dpdp[0] = true; //第一项能到达for (let i = 1; i < nums.length; i++) {for (let j = 0; j < i; j++) {//当前位置j能达到,并且当前位置j加上能到达的位置如果超过了i,那dp[i]更新为ture,便是i位置也可以到达if (dp[j] && nums[j] + j >= i) {dp[i] = true;break;}}}return dp[nums.length - 1];
}

java:

class Solution {public boolean canJump(int[] nums) {boolean[] dp = new boolean[nums.length];dp[0] = true;for (int i = 1; i < nums.length; i++) {for (int j = 0; j < i; j++) {if (dp[j] && nums[j] + j >= i) {dp[i] = true;break;}}}return dp[nums.length - 1];}
}
方法2.贪心

  • 思路:不用考虑每一步跳跃到那个位置,而是尽可能的跳跃到最远的位置,看最多能覆盖的位置,不断更新能覆盖的距离。
  • 复杂度:时间复杂度O(n),遍历一边。空间复杂度O(1)

js:

var canJump = function (nums) {if (nums.length === 1) return true; //长度为1 直接就是终点let cover = nums[0]; //能覆盖的最远距离for (let i = 0; i <= cover; i++) {cover = Math.max(cover, i + nums[i]); //当前覆盖距离cover和当前位置加能跳跃的距离中取一个较大者if (cover >= nums.length - 1) {//覆盖距离超过或等于nums.length - 1 说明能到达终点return true;}}return false; //循环完成之后 还没返回true 就是不能达到终点
};

java:

class Solution {public boolean canJump(int[] nums) {if (nums.length == 1) {return true;}int cover = nums[0];for (int i = 0; i <= cover; i++) {cover = Math.max(cover, i + nums[i]);if (cover >= nums.length - 1) {return true;}}return false;}
}

881. 救生艇 (medium)

js:

var numRescueBoats = function (people, limit) {people.sort((a, b) => (a - b));let ans = 0,left = 0,//左指针初始化在0的位置right = people.length - 1 //右指针初始化在people.length - 1的位置while (left <= right) {//两指针向中间靠拢 遍历//当people[left] + people[right--]) <= limit 表示左右两边的人可以一起坐船 然后让left++ right--//如果两人坐不下,那只能让重的人先坐一条船 也就是让right--if ((people[left] + people[right--]) <= limit) {left++}ans++}return ans
};

java:

class Solution {public int numRescueBoats(int[] people, int limit) {Arrays.sort(people);int ans = 0,left = 0,right = people.length - 1;while (left <= right) {if ((people[left] + people[right--]) <= limit) {left++;}ans++;}return ans;}
}

452. 用最少数量的箭引爆气球 (medium)

js:

var findMinArrowShots = function (points) {if (!points.length) {return 0;}points.sort((a, b) => a[1] - b[1]); //按照区间结尾排序let pos = points[0][1];let ans = 1;for (let balloon of points) {if (balloon[0] > pos) {//如果后面一个区间的开始大于前一个区间的结尾 就需要新增一支箭pos = balloon[1]; //更新pos为新的区间的结尾ans++;}}return ans;
};

java:

class Solution {public int findMinArrowShots(int[][] points) {if (points.length == 0) {return 0;}Arrays.sort(points, new Comparator<int[]>() {public int compare(int[] point1, int[] point2) {if (point1[1] > point2[1]) {return 1;} else if (point1[1] < point2[1]) {return -1;} else {return 0;}}});int pos = points[0][1];int ans = 1;for (int[] balloon: points) {if (balloon[0] > pos) {pos = balloon[1];++ans;}}return ans;}
}

134. 加油站(medium)

js:

var canCompleteCircuit = function (gas, cost) {let totalGas = 0;let totalCost = 0;for (let i = 0; i < gas.length; i++) {totalGas += gas[i];totalCost += cost[i];}if (totalGas < totalCost) {//总油量小于总油耗 肯定不能走一圈return -1;}let currentGas = 0;let start = 0;for (let i = 0; i < gas.length; i++) {currentGas = currentGas - cost[i] + gas[i];if (currentGas < 0) {//如果到达下一站的时候油量为负数 就以这个站为起点 从新计算currentGas = 0;start = i + 1;}}return start;
};

java:

class Solution {public int canCompleteCircuit(int[] gas, int[] cost) {int n = gas.length;int sum = 0;for(int i = 0;i < n;i++){sum += gas[i] - cost[i];}if(sum < 0){return -1;}int currentGas = 0;int start = 0;for(int i = 0;i < n;i++){currentGas += gas[i] - cost[i];if(currentGas < 0){currentGas = 0;start = i + 1;}}return start;}
}

621. 任务调度器 (medium)

js:

function leastInterval(tasks, n) {let arr = Array(26).fill(0);for (let c of tasks) {//统计各个字母出现的次数arr[c.charCodeAt() - "A".charCodeAt()]++;}let max = 0;for (let i = 0; i < 26; i++) {//找到最大次数max = Math.max(max, arr[i]);}let ret = (max - 1) * (n + 1); //计算前n-1行n的间隔的时间大小for (let i = 0; i < 26; i++) {//计算和最大次数相同的字母个数,然后累加进retif (arr[i] == max) {ret++;}}return Math.max(ret, tasks.length); //在tasks的长度和ret中取较大的一个
}

java:

class Solution {public int leastInterval(char[] tasks, int n) {int[] arr = new int[26];for (char c : tasks) {arr[c - 'A']++;}int max = 0;for (int i = 0; i < 26; i++) {max = Math.max(max, arr[i]);}int ret = (max - 1) * (n + 1);for (int i = 0; i < 26; i++) {if (arr[i] == max) {ret++;}}return Math.max(ret, tasks.length);}
}

860. 柠檬水找零 (easy)

js:

var lemonadeChange = function (bills) {let five = 0, ten = 0;for (const bill of bills) {if (bill === 5) {//面值为5 直接可以兑换柠檬水five += 1;} else if (bill === 10) {//面值为10 兑换柠檬水 还需要找5元if (five === 0) {return false;}five -= 1;ten += 1;} else {//面值为20 兑换柠檬水 需要找3个5元或一个10元一个5元if (five > 0 && ten > 0) {five -= 1;ten -= 1;} else if (five >= 3) {five -= 3;} else {return false;}}}return true;
};

java:

class Solution {public boolean lemonadeChange(int[] bills) {int five = 0, ten = 0;for (int bill : bills) {if (bill == 5) {five++;} else if (bill == 10) {if (five == 0) {return false;}five--;ten++;} else {if (five > 0 && ten > 0) {five--;ten--;} else if (five >= 3) {five -= 3;} else {return false;}}}return true;}
}

大厂算法面试之leetcode精讲4.贪心相关推荐

  1. 【算法面试】leetcode最常见的150道前端面试题 --- 中等题

    点击上方 前端瓶子君,关注公众号 回复算法,加入前端编程面试算法每日一题群 兄弟姐妹们,中等题来了,本篇17道,剩下63道,每周更新10道! 之前简单题的链接如下: [算法面试]leetcode最常见 ...

  2. Java面试笔试考点精讲视频教程

    Java面试笔试考点精讲视频教程 Java作为目前比较火的计算机语言之一,连续几年蝉联最受程序员欢迎的计算机语言榜首,因此每年新入职Java程序员也数不胜数.很多java程序员在学成之后,会面临着就业 ...

  3. 面试官系统精讲Java源码及大厂真题 - 01 开篇词:为什么学习本专栏

    01 开篇词:为什么学习本专栏 更新时间:2019-10-30 10:08:31 才能一旦让懒惰支配,它就一无可为. --克雷洛夫 不为了源码而读源码,只为了更好的实践 你好,我是文贺,Java 技术 ...

  4. java源码pdf_面试官系统精讲Java源码及大厂真题 PDF 下载

    主要内容: 第 1 章 基础 01 开篇词:为什么学习本专栏 不为了源码而读源码,只为了更好的实践 你好,我是文贺,Java 技术专家,DDD 和业务中台的资深实践者,一周面试 2-3 次的面试官. ...

  5. 看动画,拿 Offer:大厂算法面试真题全解析

    精讲视频包括 6.5 小时的随选视频 36 篇图文配套讲解 20 道编码练习题 完整的永久访问权 随时随地在移动设备上观看 为什么要学这门视频专栏? 在互联网大厂研发岗位的求职中,算法面试是必不可少的 ...

  6. 如何准备互联网大厂算法面试和笔试?

    干货长文预警!!!6 千多字的干货长文,建议先点赞收藏~ 先不说学算法有没有用,但我们得接受一个现实:好一点的公司都要面试算法 对于应届校招生,基本都要面临算法笔试和面试两关,每一轮技术面都会有算法题 ...

  7. 《大厂算法面试题目与答案汇总,剑指offer等常考算法题思路,python代码》V1.0版...

    为了进入大厂,我想很多人都会去牛客.知乎.CSDN等平台去查看面经,了解各个大厂在问技术问题的时候都会问些什么样的问题. 在看了几十上百篇面经之后,我将算法工程师的各种类型最常问到的问题都整理了出来, ...

  8. 面试官系统精讲Java源码及大厂真题 - 45 Socket 源码及面试题

    45 Socket 源码及面试题 引导语 Socket 中文翻译叫套接字,可能很多工作四五年的同学都没有用过这个 API,但只要用到这个 API 时,必然是在重要的工程的核心代码处. 大家平时基本都在 ...

  9. 面试官系统精讲Java源码及大厂真题 - 34 只求问倒:连环相扣系列锁面试题

    34 只求问倒:连环相扣系列锁面试题 自信和希望是青年的特权. 引导语 面试中,问锁主要是两方面:锁的日常使用场景 + 锁原理,锁的日常使用场景主要考察对锁 API 的使用熟练度,看看你是否真的使用过 ...

  10. 面试官系统精讲Java源码及大厂真题 - 30 AbstractQueuedSynchronizer 源码解析(上)

    30 AbstractQueuedSynchronizer 源码解析(上) 不想当将军的士兵,不是好士兵. 引导语 AbstractQueuedSynchronizer 中文翻译叫做同步器,简称 AQ ...

最新文章

  1. 计算机分级存储的特,一种分级存储的方法、系统、设备及介质技术方案
  2. centos 6.3安装mysql_centos6.3安装MySQL 5.6(转)
  3. 笔记本蓝牙显示输入码无效_小白笔记本连接蓝牙设备进阶篇
  4. pacemaker集群管理相关命令
  5. (十八)深入浅出TCPIP之epoll的一些思考
  6. C++语言基础 —— STL —— 算法
  7. Java多线程学习三十一:ThreadLocal 是用来解决共享资源的多线程访问的问题吗?
  8. thinkphp自定义汉字转拼音类
  9. 单片机音频信号分析仪
  10. win10计算机怎么计算根号,详细介绍win10系统自带的计算器的功能,经验告诉你该这样...
  11. android 拨打电话 发送短信 权限,Android中发送短信和拨打电话
  12. Jmeter如何分析压测结果
  13. 如何挖掘站外营销推广策略
  14. 淘宝/天猫API接口,获得淘宝商品详情高级版
  15. 2018中国初创企业融资近千亿 人工智能领跑新经济破局
  16. esxi与unraid比较
  17. Linux调度器笔记
  18. 苹果x处理器多少_精仿苹果iPhone XR手机配置介绍
  19. Kerberos认证介绍及黄金票据和白银票据
  20. Java基于springboot开发的财务咨询系统代理记账系统有论文

热门文章

  1. PHP:使用pecl安装 swoole
  2. ORACLE AutoVue 服务器/桌面版/WebService/SDK安装
  3. 程序员转行有哪些方向?人到中年,不能当一辈子普通程序员吧!
  4. surface php老是用不了,surface启动一直进入uefi怎么办
  5. 【每日一P】简单实用的调色原理
  6. 系统运维工程师30岁学python_一名Linux系统运维工程师的自述
  7. 安卓自定义悬浮按钮实现
  8. 好用的电子邮箱测评,这么多的邮箱究竟哪个最好用呢?
  9. 一个在线运行的Taro小程序完整实例
  10. 李欣桐 计算机竞赛,思维角逐 以赛促学 ——记义乌枫叶小学部第四届计算“小能手”比赛...