diff --git a/.vscode/settings.json b/.vscode/settings.json
index c8dd357..7e11d96 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,6 +1,8 @@
{
"cSpell.words": [
+ "Gitpod",
"LVIII",
+ "Prinme",
"abcdefg",
"cdefgab",
"chuan",
@@ -15,6 +17,7 @@
"powx",
"pwwkew",
"subarray",
+ "subarrays",
"umghlrlose",
"xuan",
"zhong",
diff --git a/README.md b/README.md
index 7ccb774..96cdfa1 100644
--- a/README.md
+++ b/README.md
@@ -86,6 +86,11 @@ LeetCode 与 LintCode 解题记录。此为个人练习仓库,代码中对重
- LeetCode 13. 罗马数字转整数
- LintCode 419. 罗马数字转整数
+- [验证回文字符串 Ⅱ](src/string/valid-palindrome-ii.js)
+
+ - LeetCode 680. 验证回文字符串 Ⅱ
+ - LintCode 891. 有效回文 II
+
## 数组/队列/集合/映射
- [电话号码的字母组合](src/array/letter-combinations-of-a-phone-number.js)
@@ -293,6 +298,11 @@ LeetCode 与 LintCode 解题记录。此为个人练习仓库,代码中对重
- LeetCode 387. 字符串中的第一个唯一字符
- LintCode 209. 第一个只出现一次的字符
+- [乘积最大子数组](src/array/maximum-product-subarray.js)
+
+ - LeetCode 152. 乘积最大子数组
+ - LintCode 191. 乘积最大子序列
+
## 栈
- [最大矩阵](src/stack/maximal-rectangle.js)
@@ -333,17 +343,17 @@ LeetCode 与 LintCode 解题记录。此为个人练习仓库,代码中对重
- [回文数](src/math/palindrome-number.js)
- LeetCode 9. 回文数
- - LintCode 491. 回文数
+ - LintCode 491. 回文数
- [Pow(x, n)](src/math/powx-n.js)
- LeetCode 50. Pow(x, n)
- - LintCode 428. x的n次幂
+ - LintCode 428. x的n次幂
- [和为K的子数组](src/math/subarray-sum-equals-k.js)
- LeetCode 560. 和为K的子数组
- - LintCode 838. 子数组和为K
+ - LintCode 838. 子数组和为K
## 堆
@@ -362,7 +372,7 @@ LeetCode 与 LintCode 解题记录。此为个人练习仓库,代码中对重
- [验证二叉搜索树](src/tree/validate-binary-search-tree.js)
- LeetCode 98. 验证二叉搜索树
- - LintCode 95. 验证二叉查找树
+ - LintCode 95. 验证二叉查找树
- [二叉树的最近公共祖先](src/tree/lowest-common-ancestor-of-a-binary-tree.js)
@@ -415,4 +425,12 @@ LeetCode 与 LintCode 解题记录。此为个人练习仓库,代码中对重
- [K个一组翻转链表](src/list/reverse-nodes-in-k-group.js)
- LeetCode 25. K 个一组翻转链表
- - LintCode 450. K组翻转链表
+ - LintCode 450. K组翻转链表
+
+## 图
+
+- [课程表](src/graphs/course-schedule-ii.js)
+
+ - LeetCode 210. 课程表 II
+ - LeetCode 207. 课程表
+ - LintCode 615. 课程表
diff --git a/src/array/maximum-product-subarray.js b/src/array/maximum-product-subarray.js
new file mode 100644
index 0000000..cbaee4f
--- /dev/null
+++ b/src/array/maximum-product-subarray.js
@@ -0,0 +1,18 @@
+/**
+ * @param {number[]} nums
+ * @return {number}
+ */
+export const maxProduct = function (nums) {
+ let res = nums[0]
+ let prevMin = nums[0]
+ let prevMax = nums[0]
+ let tmp1 = 0; let tmp2 = 0
+ for (let i = 1; i < nums.length; i++) {
+ tmp1 = prevMin * nums[i]
+ tmp2 = prevMax * nums[i]
+ prevMin = Math.min(tmp1, tmp2, nums[i])
+ prevMax = Math.max(tmp1, tmp2, nums[i])
+ res = Math.max(prevMax, res)
+ }
+ return res
+}
diff --git a/src/graphs/course-schedule-ii.js b/src/graphs/course-schedule-ii.js
new file mode 100644
index 0000000..a786dd8
--- /dev/null
+++ b/src/graphs/course-schedule-ii.js
@@ -0,0 +1,34 @@
+/**
+ * @param {number} numCourses
+ * @param {number[][]} prerequisites
+ * @return {number[]}
+ */
+export const findOrder = function (numCourses, prerequisites) {
+ const courses = Array(numCourses).fill(0) // 初始化 上课 需要先完成课程 的门数
+ const obj = {} // 记录受该课程 影响的其他课
+ prerequisites.forEach(item => {
+ const one = item[0]; const two = item[1] // one 要上的课, two 需先完成的课
+ courses[one]++ // 门数 + 1
+ obj[two] ? obj[two].push(one) : obj[two] = [one] // 存在就加, 不存在就新建
+ })
+ const res = []
+ const queue = [] // 队列
+ courses.forEach((t, i) => { // 往队列添加 无需先上 就可以 上 的课
+ if (t === 0) queue.push(i) // 因为是从0开始的, 所以索引也能代替 课的名称
+ })
+ while (queue.length) {
+ const cur = queue.shift() // 出队 表示该课已经上了
+ res.push(cur) // 把出队的放入 结果数组
+ const list = obj[cur] // 获取受该课影响的 课
+ list && list.forEach(item => {
+ courses[item]-- // 因为 出队表示该课已经上了, 所以 要先完成的门数 - 1
+ if (courses[item] === 0) { // 当这个课 要先修完的 已经修完了, 入队
+ queue.push(item)
+ }
+ })
+ }
+
+ return res.length === numCourses ? res : []
+}
+
+// 参考自 https://leetcode-cn.com/problems/course-schedule-ii/solution/chao-da-an-by-shetia/
diff --git a/src/string/valid-palindrome-ii.js b/src/string/valid-palindrome-ii.js
new file mode 100644
index 0000000..6713d69
--- /dev/null
+++ b/src/string/valid-palindrome-ii.js
@@ -0,0 +1,37 @@
+/**
+ * @param {string} s
+ * @return {boolean}
+ */
+export const validPalindrome = function (s, flag = true) {
+ let l = 0; let r = s.length - 1
+ while (l < r && s[l] === s[r]) {
+ l++; r--
+ }
+ if (l >= r) return true // 说明是回文
+ if (flag) return validPalindrome(s.slice(l, r), false) || validPalindrome(s.slice(l + 1, r + 1), false) // 如果不是回文,切左边或者右边,再判断一次
+ return false
+}
+
+// 暴力解法,会超时
+// /**
+// * @param {string} s
+// * @return {boolean}
+// */
+// export const validPalindrome = function (s) {
+// const len = s.length
+// const tmpLen = len - 1
+// let mid = len >> 1
+// if (s.substring(0, mid) === s.substring(len % 2 ? mid + 1 : mid, len).split('').reverse().join('')) {
+// return true
+// }
+
+// mid = tmpLen >> 1
+// for (let n = 0; n < len; n++) {
+// const tmp = s.substring(0, n) + s.substring(n + 1, len)
+// if (tmp.substring(0, mid) === tmp.substring(tmpLen % 2 ? mid + 1 : mid, tmpLen).split('').reverse().join('')) {
+// return true
+// }
+// }
+
+// return false
+// }
diff --git a/test/array/maximum-product-subarray.test.js b/test/array/maximum-product-subarray.test.js
new file mode 100644
index 0000000..fc96c07
--- /dev/null
+++ b/test/array/maximum-product-subarray.test.js
@@ -0,0 +1,6 @@
+import { maxProduct } from '../../src/array/maximum-product-subarray'
+
+test('', () => {
+ expect(maxProduct([2, 3, -2, 4])).toBe(6)
+ expect(maxProduct([-2, 0, -1])).toBe(0)
+})
diff --git a/test/graphs/course-schedule-ii.test.js b/test/graphs/course-schedule-ii.test.js
new file mode 100644
index 0000000..92a19b2
--- /dev/null
+++ b/test/graphs/course-schedule-ii.test.js
@@ -0,0 +1,6 @@
+import { findOrder } from '../../src/graphs/course-schedule-ii'
+
+test('课程表 II', () => {
+ expect(findOrder(2, [[1, 0]])).toEqual([0, 1])
+ expect(findOrder(4, [[1, 0], [2, 0], [3, 1], [3, 2]])).toEqual([0, 1, 2, 3])
+})
diff --git a/test/string/valid-palindrome-ii.test.js b/test/string/valid-palindrome-ii.test.js
new file mode 100644
index 0000000..ab25162
--- /dev/null
+++ b/test/string/valid-palindrome-ii.test.js
@@ -0,0 +1,8 @@
+import { validPalindrome } from '../../src/string/valid-palindrome-ii'
+
+test('验证回文字符串 Ⅱ', () => {
+ expect(validPalindrome('aba')).toBe(true)
+ expect(validPalindrome('abba')).toBe(true)
+ expect(validPalindrome('abca')).toBe(true)
+ expect(validPalindrome('abcda')).toBe(false)
+})