引言(文末有福利)????
算法始终是大厂前端面试常问的一块,而大家往往筹备这方面的面试都是通过leetcode
刷题。
我顺便整顿了几道leetcode
中「很有意思
」而且十分「高频
」的算法题目,别离给出了思路剖析(带图解)和代码实现。
认真仔细的浏览完本文,置信对于你在算法方面的面试肯定会有不小的帮忙!????
两数之和 ????
题目难度easy
,波及到的算法常识有数组、哈希表
题目形容
给定一个整数数组 nums
和一个目标值 target
,请你在该数组中找出和为目标值的那两个
整数,并返回他们的数组下标。
你能够假如每种输出只会对应一个答案。然而,数组中同一个元素不能应用两遍。
示例:
给定 nums = [2, 7, 11, 15], target = 9因为 nums[0] + nums[1] = 2 + 7 = 9所以返回 [0, 1]
思路剖析
大多数同学看到这道题目,心中必定会想:这道题目太简略了,不就两层遍历嘛:两层循环来遍历同一个数组;第一层循环遍历的值记为a
,第二层循环时遍历的值记为b
;若a+b = 目标值
,那么a
和b
对应的数组下标就是咱们想要的答案。
这种解法没故障,但有没有优化的计划呢?????
要晓得两层循环很多状况下都意味着O(n^2)
的复杂度,这个复杂度非常容易导致你的算法超时。即使没有超时,在明明有一层遍历解法的状况下,你写了两层遍历,面试官也会对你的印象分大打折扣。????
其实咱们能够在遍历数组的过程中,减少一个Map
构造来存储曾经遍历过的数字及其对应的索引值。而后每遍历到一个新数字的时候,都回到Map
里去查问targetNum
与该数的差值是否曾经在后面的数字中呈现过了。若呈现过,那么答案未然浮现,咱们就不用再往下走了。
咱们就以本题中的例子联合图片来阐明一下下面提到的这种思路:
- 这里用对象
diffs
来模仿map
构造:首先遍历数组第一个元素,此时
key
为 2,value
为索引 0 - 往下遍历,遇到了 7:
计算
targetNum
和 7 的差值为 2,去diffs
中检索 2 这个key
,发现是之前呈现过的值。那么本题的答案就进去了!
代码实现
/** * @param {number[]} nums * @param {number} target * @return {number[]} */const twoSum = function (nums, target) { const diffs = {}; // 缓存数组长度 const len = nums.length; // 遍历数组 for (let i = 0; i < len; i++) { // 判断以后值对应的 target 差值是否存在 if (diffs[target - nums[i]] !== undefined) { // 若有对应差值,那么失去答案 return [diffs[target - nums[i]], i]; } // 若没有对应差值,则记录以后值 diffs[nums[i]] = i; }};
三数之和 ????
题目难度medium
,波及到的算法常识有数组、双指针
题目形容
给你一个蕴含n
个整数的数组nums
,判断nums
中是否存在三个元素a
,b
,c
,使得a + b + c = 0
。请你找出所有满足条件且不反复的三元组。
留神:答案中不能够蕴含反复的三元组。
示例:
给定数组 nums = [-1, 0, 1, 2, -1, -4],满足要求的三元组汇合为:[ [-1, 0, 1], [-1, -1, 2]]
思路剖析
和下面的两数之和
一样,如果不认真思考,最快的形式可能就是多层遍历了。但有了前事不忘;后事之师,咱们同样能够把求和问题变为求差问题:固定其中一个数,在剩下的数中寻找是否有两个数的和这个固定数相加是等于 0 的。
这里咱们采纳双指针法
来解决问题,相比三层循环,效率会大大晋升。
双指针法的适用范围比拟广,个别像求和、比大小的都能够用它来解决。然而有一个前提:数组必须有序
因而咱们的第一步就是先将数组进行排序:
// 给 nums 排序nums = nums.sort((a, b) => { return a - b;});
而后对数组进行遍历,每遍历到哪个数字,就固定以后的数字。同时左指针指向该数字前面的紧邻的那个数字,右指针指向数组开端。而后左右指针别离向两头聚拢:
每次指针挪动一次地位,就计算一下两个指针指向数字之和加上固定的那个数之后,是否等于 0。如果是,那么咱们就失去了一个指标组合;否则,分两种状况来看:
- 相加之和大于 0,阐明右侧的数偏大了,右指针左移
- 相加之和小于 0,阐明左侧的数偏小了,左指针右移
代码实现
/** * @param {number[]} nums * @return {number[][]} */const threeSum = function (nums) { // 用于寄存后果数组 let res = []; // 目标值为0 let sum = 0; // 给 nums 排序 nums = nums.sort((a, b) => { return a - b; }); // 缓存数组长度 const len = nums.length; for (let i = 0; i < len - 2; i++) { // 左指针 j let j = i + 1; // 右指针k let k = len - 1; // 如果遇到反复的数字,则跳过 if (i > 0 && nums[i] === nums[i - 1]) { continue; } while (j < k) { // 三数之和小于0,左指针后退 if (nums[i] + nums[j] + nums[k] < 0) { j++; // 解决左指针元素反复的状况 while (j < k && nums[j] === nums[j - 1]) { j++; } } else if (nums[i] + nums[j] + nums[k] > 0) { // 三数之和大于0,右指针后退 k--; // 解决右指针元素反复的状况 while (j < k && nums[k] === nums[k + 1]) { k--; } } else { // 失去指标数字组合,推入后果数组 res.push([nums[i], nums[j], nums[k]]); // 左右指针一起后退 j++; k--; // 若左指针元素反复,跳过 while (j < k && nums[j] === nums[j - 1]) { j++; } // 若右指针元素反复,跳过 while (j < k && nums[k] === nums[k + 1]) { k--; } } } } // 返回后果数组 return res;};
盛最多水的容器 ????
题目难度medium
,波及到的算法常识有数组、双指针
题目形容
给你 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点别离为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴独特形成的容器能够包容最多的水。
阐明:你不能歪斜容器,且 n 的值至多为 2。
图中垂直线代表输出数组[1,8,6,2,5,4,8,3,7]。在此状况下,容器可能包容水(示意为蓝色局部)的最大值为 49。
示例:
输出:[1,8,6,2,5,4,8,3,7]输入:49
思路剖析
首先,咱们能疾速想到的一种办法:两两进行求解,计算能够承载的水量。 而后不断更新最大值,最初返回最大值即可。
这种解法,须要两层循环,工夫复杂度是O(n^2)
。这种相对来说比拟暴力,对应就是暴力法
。
暴力法
/** * @param {number[]} height * @return {number} */var maxArea = function (height) { let max = 0; for (let i = 0; i < height.length - 1; i++) { for (let j = i + 1; j < height.length; j++) { let area = (j - i) * Math.min(height[i], height[j]); max = Math.max(max, area); } } return max;};
那么有没有更好的方法呢?答案是必定有。
其实有点相似双指针
的概念,左指针指向下标 0,右指针指向length-1
。而后别离从左右两侧向两头挪动,每次取小的那个值(因为水的高度肯定是以小的那个为准)。
如果左侧小于右侧,则i++
,否则j--
(这一步其实就是取所有高度中比拟高的,咱们晓得面积等于长*宽
)。对应就是双指针 动静滑窗
双指针 动静滑窗
/** * @param {number[]} height * @return {number} */var maxArea = function (height) { let max = 0; let i = 0; let j = height.length - 1; while (i < j) { let minHeight = Math.min(height[i], height[j]); let area = (j - i) * minHeight; max = Math.max(max, area); if (height[i] < height[j]) { i++; } else { j--; } } return max;};
爬楼梯 ????
题目难度easy
,波及到的算法常识有斐波那契数列、动静布局。
题目形容
假如你正在爬楼梯。须要 n 阶你能力达到楼顶。
每次你能够爬 1 或 2 个台阶。你有多少种不同的办法能够爬到楼顶呢?
留神:给定 n 是一个正整数。
示例 1:
输出: 2输入: 2解释: 有两种办法能够爬到楼顶。1. 1 阶 + 1 阶2. 2 阶
示例 2:
输出: 3输入: 3解释: 有三种办法能够爬到楼顶。1. 1 阶 + 1 阶 + 1 阶2. 1 阶 + 2 阶3. 2 阶 + 1 阶
思路剖析
这道题目是一道十分高频的面试题目,也是一道十分经典的斐波那契数列
类型的题目。
解决本道题目咱们会用到动静布局的算法思维-能够分成多个子问题,爬第 n 阶楼梯的办法数量,等于 2 局部之和:
- 爬上
n−1
阶楼梯的办法数量。因为再爬 1 阶就能到第 n 阶 - 爬上
n−2
阶楼梯的办法数量,因为再爬 2 阶就能到第 n 阶
能够失去公式:
climbs[n] = climbs[n - 1] + climbs[n - 2];
同时须要做如下初始化:
climbs[0] = 1;climbs[1] = 1;
代码实现
/** * @param {number} n * @return {number} */var climbStairs = function (n) { let climbs = []; climbs[0] = 1; climbs[1] = 1; for (let i = 2; i <= n; i++) { climbs[i] = climbs[i - 1] + climbs[i - 2]; } return climbs[n];};
环形链表 ????
题目难度easy
,波及到的算法常识有链表、快慢指针。
题目形容
给定一个链表,判断链表中是否有环。
为了示意给定链表中的环,咱们应用整数 pos 来示意链表尾连贯到链表中的地位(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
示例 1:
输出:head = [3,2,0,-4], pos = 1输入:true解释:链表中有一个环,其尾部连贯到第二个节点。
示例 2:
输出:head = [1,2], pos = 0输入:true解释:链表中有一个环,其尾部连贯到第一个节点。
示例 3:
输出:head = [1], pos = -1输入:false解释:链表中没有环。
思路剖析
链表成环
问题也是十分经典的算法问题,在面试中也常常会遇到。
解决这种问题个别有常见的两种办法:标记法
和快慢指针法
。
标记法
给每个已遍历过的节点加标记位,遍历链表,当呈现下一个节点已被标记时,则证实单链表有环。
/** * Definition for singly-linked list. * function ListNode(val) { * this.val = val; * this.next = null; * } *//** * @param {ListNode} head * @return {boolean} */var hasCycle = function (head) { while (head) { if (head.flag) return true; head.flag = true; head = head.next; } return false;};
快慢指针(双指针法)
设置快慢两个指针,遍历单链表,快指针一次走两步,慢指针一次走一步,如果单链表中存在环,则快慢指针终会指向同一个节点,否则直到快指针指向null
时,快慢指针都不可能相遇。
/** * Definition for singly-linked list. * function ListNode(val) { * this.val = val; * this.next = null; * } *//** * @param {ListNode} head * @return {boolean} */var hasCycle = function (head) { if (!head || !head.next) { return false; } let slow = head, fast = head.next; while (slow !== fast) { if (!fast || !fast.next) return false; fast = fast.next.next; slow = slow.next; } return true;};
无效的括号 ????
题目难度easy
,波及到的算法常识有栈、哈希表。
题目形容
给定一个只包含'('
,')'
,'{'
,'}'
,'['
,']'
的字符串,判断字符串是否无效。
无效字符串需满足:
1、左括号必须用雷同类型的右括号闭合。
2、左括号必须以正确的程序闭合。
留神空字符串可被认为是无效字符串。
示例 1:
输出: "()";输入: true;
示例 2:
输出: "()[]{}";输入: true;
示例 3:
输出: "(]";输入: false;
示例 4:
输出: "([)]";输入: false;
示例 5:
输出: "{[]}";输入: true;
思路剖析
这道题能够利用栈
构造。
思路大略是:遇到左括号,一律推入栈中,遇到右括号,将栈顶部元素拿出,如果不匹配则返回 false
,如果匹配则持续循环。
第一种解法是利用switch case
。
switch case
/** * @param {string} s * @return {boolean} */var isValid = function (s) { let arr = []; let len = s.length; if (len % 2 !== 0) return false; for (let i = 0; i < len; i++) { let letter = s[i]; switch (letter) { case "(": { arr.push(letter); break; } case "{": { arr.push(letter); break; } case "[": { arr.push(letter); break; } case ")": { if (arr.pop() !== "(") return false; break; } case "}": { if (arr.pop() !== "{") return false; break; } case "]": { if (arr.pop() !== "[") return false; break; } } } return !arr.length;};
第二种是保护一个map
对象:
哈希表map
/** * @param {string} s * @return {boolean} */var isValid = function (s) { let map = { "(": ")", "{": "}", "[": "]", }; let stack = []; let len = s.length; if (len % 2 !== 0) return false; for (let i of s) { if (i in map) { stack.push(i); } else { if (i !== map[stack.pop()]) return false; } } return !stack.length;};
滑动窗口最大值 ⛵
题目难度hard
,波及到的算法常识有双端队列。
题目形容
给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧挪动到数组的最右侧。你只能够看到在滑动窗口内的 k 个数字。滑动窗口每次只向右挪动一位。
返回滑动窗口中的最大值。
进阶:你能在线性工夫复杂度内解决此题吗?
示例:
输出: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3输入: [3,3,5,5,6,7]解释: 滑动窗口的地位 最大值--------------- -----[1 3 -1] -3 5 3 6 7 3 1 [3 -1 -3] 5 3 6 7 3 1 3 [-1 -3 5] 3 6 7 5 1 3 -1 [-3 5 3] 6 7 5 1 3 -1 -3 [5 3 6] 7 6 1 3 -1 -3 5 [3 6 7] 7
提醒:
- 1 <= nums.length <= 10^5
- -10^4 <= nums[i] <= 10^4
- 1 <= k <= nums.length
思路剖析
暴力求解
第一种办法,比较简单。也是大多数同学很快就能想到的办法。
- 遍历数组
- 顺次遍历每个区间内的最大值,放入数组中
/** * @param {number[]} nums * @param {number} k * @return {number[]} */var maxSlidingWindow = function (nums, k) { let len = nums.length; if (len === 0) return []; if (k === 1) return nums; let resArr = []; for (let i = 0; i <= len - k; i++) { let max = Number.MIN_SAFE_INTEGER; for (let j = i; j < i + k; j++) { max = Math.max(max, nums[j]); } resArr.push(max); } return resArr;};
双端队列
这道题还能够用双端队列去解决,外围在于在窗口产生挪动时,只依据发生变化的元素对最大值进行更新。
联合下面动图(图片起源)咱们梳理下思路:
- 查看队尾元素,看是不是都满足大于等于以后元素的条件。如果是的话,间接将以后元素入队。否则,将队尾元素一一出队、直到队尾元素大于等于以后元素为止。(这一步是为了维持队列的递减性:确保队头元素是以后滑动窗口的最大值。这样咱们每次取最大值时,间接取队头元素即可。)
- 将以后元素入队
- 查看队头元素,看队头元素是否曾经被排除在滑动窗口的范畴之外了。如果是,则将队头元素出队。(这一步是维持队列的有效性:确保队列里所有的元素都在滑动窗口圈定的范畴以内。)
- 排除掉滑动窗口还没有初始化实现、第一个最大值还没有呈现的非凡状况。
/** * @param {number[]} nums * @param {number} k * @return {number[]} */var maxSlidingWindow = function (nums, k) { // 缓存数组的长度 const len = nums.length; const res = []; const deque = []; for (let i = 0; i < len; i++) { // 队尾元素小于以后元素 while (deque.length && nums[deque[deque.length - 1]] < nums[i]) { deque.pop(); } deque.push(i); // 当队头元素的索引曾经被排除在滑动窗口之外时 while (deque.length && deque[0] <= i - k) { // 队头元素出对 deque.shift(); } if (i >= k - 1) { res.push(nums[deque[0]]); } } return res;};
每日温度 ????
题目难度medium
,波及到的算法常识有栈。
题目形容
依据每日气温列表,请从新生成一个列表,对应地位的输入是须要再期待多久温度才会升高超过该日的天数。如果之后都不会升高,请在该地位用 0 来代替。
例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输入应该是 [1, 1, 4, 2, 1, 1, 0, 0]。
提醒:气温列表长度的范畴是 [1, 30000]。每个气温的值的均为华氏度,都是在 [30, 100] 范畴内的整数。
思路剖析
看到这道题,大家很容易就会想到暴力遍历法:间接两层遍历,第一层定位一个温度,第二层定位离这个温度最近的一次升温是哪天,而后求出两个温度对应索引的差值即可。
然而这种解法须要两层遍历,工夫复杂度是O(n^2)
,显然不是最优解法。
本道题目能够采纳栈去做一个优化。
大略思路就是:保护一个递加栈。当遍历过的温度,维持的是一个枯燥递加的态势时,咱们就对这些温度的索引下标执行入栈操作;只有呈现了一个数字,它突破了这种枯燥递加的趋势,也就是说它比前一个温度值高,这时咱们就对前后两个温度的索引下标求差,得出前一个温度间隔第一次升温的指标差值。
代码实现
/** * @param {number[]} T * @return {number[]} */var dailyTemperatures = function (T) { const len = T.length; const stack = []; const res = new Array(len).fill(0); for (let i = 0; i < len; i++) { while (stack.length && T[i] > T[stack[stack.length - 1]]) { const top = stack.pop(); res[top] = i - top; } stack.push(i); } return res;};
括号生成 ????
题目难度medium
,波及到的算法常识有递归、回溯。
题目形容
数字 n 代表生成括号的对数,请你设计一个函数,用于可能生成所有可能的并且 无效的 括号组合。
示例:
输出:n = 3输入:[ "((()))", "(()())", "(())()", "()(())", "()()()" ]
思路剖析
这道题目通过递归去实现。
因为左右括号须要匹配、闭合。所以对应“(”和“)”的数量都是n
,当满足这个条件时,一次递归就完结,将对应值放入后果数组中。
这里有一个潜在的限度条件:无效的
括号组合。对应逻辑就是在往每个地位去放入“(”或“)”前:
- 须要判断“(”的数量是否小于 n
- “)”的数量是否小于“(”
代码实现
/** * @param {number} n * @return {string[]} */var generateParenthesis = function (n) { let res = []; const generate = (cur, left, right) => { if (left === n && right === n) { res.push(cur); return; } if (left < n) { generate(cur + "(", left + 1, right); } if (right < left) { generate(cur + ")", left, right + 1); } }; generate("", 0, 0); return res;};
电话号码的字母组合 ????
题目难度medium
,波及到的算法常识有递归、回溯。
题目形容
给定一个仅蕴含数字 2-9 的字符串,返回所有它能示意的字母组合。
给出数字到字母的映射如下(与电话按键雷同)。留神 1 不对应任何字母。
示例:
输出:"23"输入:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
思路剖析
首先用一个对象map
存储数字与字母的映射关系,接下来遍历对应的字符串,第一次将字符串存在后果数组result
中,第二次及当前的就双层遍历生成新的字符串数组。
代码实现
哈希映射 逐层遍历
/** * @param {string} digits * @return {string[]} */var letterCombinations = function (digits) { let res = []; if (digits.length === 0) return []; let map = { 2: "abc", 3: "def", 4: "ghi", 5: "jkl", 6: "mno", 7: "pqrs", 8: "tuv", 9: "wxyz", }; for (let num of digits) { let chars = map[num]; if (res.length > 0) { let temp = []; for (let char of chars) { for (let oldStr of res) { temp.push(oldStr + char); } } res = temp; } else { res.push(...chars); } } return res;};
递归
/** * @param {string} digits * @return {string[]} */var letterCombinations = function (digits) { let res = []; if (!digits) return []; let map = { 2: "abc", 3: "def", 4: "ghi", 5: "jkl", 6: "mno", 7: "pqrs", 8: "tuv", 9: "wxyz", }; function generate(i, str) { let len = digits.length; if (i === len) { res.push(str); return; } let chars = map[digits[i]]; for (let j = 0; j < chars.length; j++) { generate(i + 1, str + chars[j]); } } generate(0, ""); return res;};
岛屿数量 ????
题目难度medium
,波及到的算法常识有 DFS(深度优先搜寻)。
题目形容
给你一个由 '1'(海洋)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水突围,并且每座岛屿只能由程度方向或竖直方向上相邻的海洋连贯造成。
此外,你能够假如该网格的四条边均被水突围。
示例 1:
输出: 11110;11010;11000;00000;输入: 1;
示例 2:
输出:11000110000010000011输入: 3解释: 每座岛屿只能由程度和/或竖直方向上相邻的海洋连贯而成。
思路剖析
如上图,咱们须要计算的就是图中相连(只能是程度和/或竖直方向上相邻)的绿色岛屿的数量。
这道题目一个经典的做法是沉岛
,大抵思路是:采纳DFS
(深度优先搜寻),遇到 1 的就将以后的 1 变为 0,并将以后坐标的上下左右都执行 dfs,并计数。
终止条件是:超出二维数组的边界或者是遇到 0 ,间接返回。
代码实现
/** * @param {character[][]} grid * @return {number} */var numIslands = function (grid) { const rows = grid.length; if (rows === 0) return 0; const cols = grid[0].length; let res = 0; for (let i = 0; i < rows; i++) { for (let j = 0; j < cols; j++) { if (grid[i][j] === "1") { helper(grid, i, j, rows, cols); res++; } } } return res;};function helper(grid, i, j, rows, cols) { if (i < 0 || j < 0 || i > rows - 1 || j > cols - 1 || grid[i][j] === "0") return; grid[i][j] = "0"; helper(grid, i + 1, j, rows, cols); helper(grid, i, j + 1, rows, cols); helper(grid, i - 1, j, rows, cols); helper(grid, i, j - 1, rows, cols);}
散发饼干 ????
题目难度easy
,波及到的算法常识有贪婪算法。
题目形容
假如你是一位很棒的家长,想要给你的孩子们一些小饼干。然而,每个孩子最多只能给一块饼干。对每个孩子 i ,都有一个胃口值 gi ,这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j ,都有一个尺寸 sj 。如果 sj >= gi ,咱们能够将这个饼干 j 调配给孩子 i ,这个孩子会失去满足。你的指标是尽可能满足越多数量的孩子,并输入这个最大数值。
留神:
你能够假如胃口值为正。
一个小朋友最多只能领有一块饼干。
示例 1:
输出: [1,2,3], [1,1]输入: 1解释:你有三个孩子和两块小饼干,3个孩子的胃口值别离是:1,2,3。尽管你有两块小饼干,因为他们的尺寸都是1,你只能让胃口值是1的孩子满足。所以你应该输入1。
示例 2:
输出: [1,2], [1,2,3]输入: 2解释:你有两个孩子和三块小饼干,2个孩子的胃口值别离是1,2。你领有的饼干数量和尺寸都足以让所有孩子满足。所以你应该输入2.
思路剖析
这道题目是一道典型的贪婪算法
类。解题思路大略如下:
- 优先满足胃口小的小朋友的需要
- 设最大可满足的孩子数量为
maxNum = 0
- 胃口小的拿小的,胃口大的拿大的
两边升序,而后一一比照
- 当
饼干j
>=胃口i
时,i++
、j++
、maxNum++
- 当
饼干j
<胃口i
时,阐明饼干不够吃,换更大的,j++
- 当
- 到边界后进行
代码实现
/** * @param {number[]} g * @param {number[]} s * @return {number} */var findContentChildren = function (g, s) { g = g.sort((a, b) => a - b); s = s.sort((a, b) => a - b); let gLen = g.length, sLen = s.length, i = 0, j = 0, maxNum = 0; while (i < gLen && j < sLen) { if (s[j] >= g[i]) { i++; maxNum++; } j++; } return maxNum;};
交易股票的最佳时机 II ????
题目难度easy
,波及到的算法常识有动静布局、贪婪算法。
题目形容
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你能够尽可能地实现更多的交易(屡次交易一支股票)。
留神:你不能同时参加多笔交易(你必须在再次购买前发售掉之前的股票)。
示例 1:
输出: [7,1,5,3,6,4]输入: 7解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能取得利润 = 5-1 = 4 。 随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能取得利润 = 6-3 = 3 。
示例 2:
输出: [1,2,3,4,5]输入: 4解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能取得利润 = 5-1 = 4 。 留神你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。 因为这样属于同时参加了多笔交易,你必须在再次购买前发售掉之前的股票。
示例 3:
输出: [7,6,4,3,1]输入: 0解释: 在这种状况下, 没有交易实现, 所以最大利润为 0。
提醒:
- 1 <= prices.length <= 3 * 10 ^ 4
- 0 <= prices[i] <= 10 ^ 4
思路剖析
其实这道题目思路也比较简单:
- 保护一个变量
profit
用来存储利润 - 因为能够屡次交易,那么就要前面的价格比后面的大,那么就能够进行交易
- 因而,只有
prices[i+1] > prices[i]
,那么就去叠加profit
- 遍历实现失去的
profit
就是获取的最大利润
代码实现
/** * @param {number[]} prices * @return {number} */var maxProfit = function (prices) { let profit = 0; for (let i = 0; i < prices.length - 1; i++) { if (prices[i + 1] > prices[i]) profit += prices[i + 1] - prices[i]; } return profit;};
不同门路 ????
题目难度medium
,波及到的算法常识有动静布局。
题目形容
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
机器人每次只能向下或者向右挪动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
问总共有多少条不同的门路?
例如,上图是一个 7 x 3 的网格。有多少可能的门路?
示例 1:
输出: m = 3, n = 2输入: 3解释:从左上角开始,总共有 3 条门路能够达到右下角。1. 向右 -> 向右 -> 向下2. 向右 -> 向下 -> 向右3. 向下 -> 向右 -> 向右
示例 2:
输出: (m = 7), (n = 3);输入: 28;
思路剖析
由题可知:机器人只能向右或向下挪动一步,那么从左上角到右下角的走法 = 从左边开始走的门路总数+从下边开始走的门路总数。
所以可推出动静方程为:dp[i][j] = dp[i-1][j]+dp[i][j-1]
。
代码实现
这里采纳Array(m).fill(Array(n).fill(1))
进行了初始化,因为每一格至多有一种走法。
/** * @param {number} m * @param {number} n * @return {number} */var uniquePaths = function (m, n) { let dp = Array(m).fill(Array(n).fill(1)); for (let i = 1; i < m; i++) { for (let j = 1; j < n; j++) { dp[i][j] = dp[i - 1][j] + dp[i][j - 1]; } } return dp[m - 1][n - 1];};
零钱兑换 ????
题目难度medium
,波及到的算法常识有动静布局。
题目形容
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算能够凑成总金额所需的起码的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
示例 1:
输出: (coins = [1, 2, 5]), (amount = 11);输入: 3;解释: 11 = 5 + 5 + 1;
示例 2:
输出: (coins = [2]), (amount = 3);输入: -1;
阐明:
你能够认为每种硬币的数量是有限的。
思路剖析
这道题目咱们同样采纳动静布局
来解决。
假如给出的不同面额的硬币是[1, 2, 5],指标是 60,问起码须要的硬币个数?
咱们须要先合成子问题,分层级找最优子结构。
dp[i]
: 示意总金额为i
的时候最优解法的硬币数
咱们想一下:求总金额 60 有几种办法?一共有 3 种形式,因为咱们有 3 种不同面值的硬币。
- 拿一枚面值为 1 的硬币 + 总金额为 59 的最优解法的硬币数量。即:dp[59] + 1
- 拿一枚面值为 2 的硬币 + 总金额为 58 的最优解法的硬币数。即:dp[58] + 1
- 拿一枚面值为 5 的硬币 + 总金额为 55 的最优解法的硬币数。即:dp[55] + 1
所以,总金额为 60 的最优解法就是下面这三种解法中最优的一种,也就是硬币数起码的一种,咱们上面用代码来示意一下:
dp[60] = Math.min(dp[59] + 1, dp[58] + 1, dp[55] + 1);
推导出状态转移方程
:
dp[i] = Math.min(dp[i - coin] + 1, dp[i - coin] + 1, ...)
其中coin
有多少种可能,咱们就须要比拟多少次,遍历coins
数组,别离去比照即可
代码实现
/** * @param {number[]} coins * @param {number} amount * @return {number} */var coinChange = function (coins, amount) { let dp = new Array(amount + 1).fill(Infinity); dp[0] = 0; for (let i = 0; i <= amount; i++) { for (let coin of coins) { if (i - coin >= 0) { dp[i] = Math.min(dp[i], dp[i - coin] + 1); } } } return dp[amount] === Infinity ? -1 : dp[amount];};
福利
大多数前端同学对于算法的零碎学习,其实是比拟茫然的,这里我整顿了一张思维导图,算是比拟全面的概括了前端算法体系。
另外我还保护了一个github
仓库:https://github.com/Cosen95/js_algorithm
,外面蕴含了大量的leetcode
题解,并且还在不断更新中,感觉不错的给个star
哈!????
❤️ 爱心三连击
1.如果感觉这篇文章还不错,来个分享、点赞、在看三连吧,让更多的人也看到~
2.关注公众号前端森林,定期为你推送陈腐干货好文。
3.非凡阶段,带好口罩,做好集体防护。