diff --git a/Algorithm-notes/README.md b/Algorithm-notes/README.md index 368145a..3580ba5 100644 --- a/Algorithm-notes/README.md +++ b/Algorithm-notes/README.md @@ -185,7 +185,8 @@ function bubbleSort(nums) { for(let i=0, len=nums.length; i nums[j+1]) { [nums[j], nums[j+1]] = [nums[j+1], nums[j]]; mark = false; @@ -239,6 +240,7 @@ function bubbleSort_twoWays(nums) { function selectSort(nums) { for(let i=0, len=nums.length; i nums[j]) { [nums[i], nums[j]] = [nums[j], nums[i]]; } @@ -260,7 +262,7 @@ function insertSort(nums) { for(let i=1, len=nums.length; i= 0 && temp < nums[j-1]) { + while(j > 0 && temp < nums[j-1]) { nums[j] = nums[j-1]; j--; } @@ -289,7 +291,7 @@ function insertSort(nums) { ### 快速排序之填坑 -从右边向中间推进的时候,遇到小于基数的数就赋给左边(一开始是基数的位置),右边保留原先的值等之后被左边的值填上。 +默认取第一个数作为基数,所以先从右边向中间推。遇到小于基数的数就赋给左边的坑位(一开始是基数的位置),而这个小于基数的数则由基数填上,这样基数右边的数就都大于基数了。右边保留原先的值等之后被左边的值填上。 ```js function quickSort(nums) { @@ -306,6 +308,7 @@ function quickSort(nums) { // 取第一个数为基数 let temp = arr[left]; while(left < right) { + // 因为是取第一书记作为IE while(left < right && arr[right] >= temp) right--; arr[left] = arr[right]; while(left < right && arr[left] < temp) left++; @@ -356,7 +359,7 @@ function quickSort1(nums) { ## 归并排序 -递归将数组分为两个序列,有序合并这两个序列。 +递归将数组平分为两个序列,并有序合并这两个序列。 + 最好:`O(n * logn)` + 最坏:`O(n * logn)` @@ -650,7 +653,9 @@ function shellSort(nums) { # 洗牌算法 -洗牌算法其实就是随机打乱数组,实现思路是:遍历数组元素,将当前元素和前面未有序序列中任意一个数进行交换,保证每个元素和其他元素交换的概率是等大的。 +> [洗牌算法](https://labuladong.gitbook.io/algo/suan-fa-si-wei-xi-lie/xi-pai-suan-fa) + +洗牌算法其实就是随机打乱数组。随机打乱一个长度为 n 的数组,相当于是找它的全排列,也就是有 `n!` 种,即数组打乱结果总共有 `n!` 种。实现思路是:遍历数组元素,将当前元素和前面未有序序列中任意一个数进行交换,保证每个元素和其他元素交换的概率是等大的。 ```js let arr = [0, 1, 2, 3, 4]; @@ -662,8 +667,17 @@ for(let i=arr.length-1; i>0; i--) { 有一种更简单的方法也可以打乱数组:``arr.sort(() => Math.random() - 0.5)``。但据说这种方法得到的数组并不能达到真正的乱序,具体原因我现在还不清楚,得之后我深入研究了再做补充。 +- 如何验证打乱后的数组是否真的乱序了? + - 方法一:先列出该数组的全排列。将该数组打乱 100 万次,记录每次打乱后出现的结果频次(哈希)。如果是完全乱序的话,各个结果出现的频次应该是差不多大的。 + - 方法二:取一个数组,里面只有一个元素是 1,其他元素值都为 0。将该数组打乱 100 万次,1 出现在每个位置上的次数应该是差不多大的。 + # 二叉树 +> 从第 0 层开始算起,给每个节点标号,第一个节点标号为 1。每层的第一个节点是 `2^n`,每层有 `2^n` 个节点。 +> +> 对于第 n 层,从第 0 层到第 n-1 层,总共有 `2^n - 1` 个节点,到第 n 层累计有 `2^(n+1)- 1` 个节点。 + + ## 构造二叉树 ### 根据先序遍历和中序遍历构造二叉树 diff --git "a/LeetCode/11. \347\233\233\346\234\200\345\244\232\346\260\264\347\232\204\345\256\271\345\231\250.js" "b/LeetCode/11. \347\233\233\346\234\200\345\244\232\346\260\264\347\232\204\345\256\271\345\231\250.js" new file mode 100644 index 0000000..8ad0a2b --- /dev/null +++ "b/LeetCode/11. \347\233\233\346\234\200\345\244\232\346\260\264\347\232\204\345\256\271\345\231\250.js" @@ -0,0 +1,44 @@ +/* +给你 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 +*/ + +/** + * @param {number[]} height + * @return {number} + */ + +// 暴力破解,两层 for 算出两两组合的矩形面积 +var maxArea = function(height) { + let res = 0; + for(let i=0; i res) { + res = temp; + } + } + } + return res; +}; + +// 双指针,一个在数组头,一个在数位尾。 +// 初始时底长已经是最大了,想要更大的面积,就只能寻找更大的边高。所以比较此时的两条边高,更小的那条舍弃,移动指针寻找下一条边看看能不能得到更大的面积 +var maxArea = function(height) { + let res = 0; + let l = 0, r = height.length - 1; + while (l < r) { + res = Math.max(res, (r - l) * Math.min(height[l], height[r])); + if (height[l] > height[r]) { + r--; + } else { + l++; + } + } + return res; +} \ No newline at end of file diff --git "a/LeetCode/1254. \347\273\237\350\256\241\345\260\201\351\227\255\345\262\233\345\261\277\347\232\204\346\225\260\347\233\256.js" "b/LeetCode/1254. \347\273\237\350\256\241\345\260\201\351\227\255\345\262\233\345\261\277\347\232\204\346\225\260\347\233\256.js" new file mode 100644 index 0000000..8a45bd4 --- /dev/null +++ "b/LeetCode/1254. \347\273\237\350\256\241\345\260\201\351\227\255\345\262\233\345\261\277\347\232\204\346\225\260\347\233\256.js" @@ -0,0 +1,94 @@ +/* + 有一个二维矩阵 grid ,每个位置要么是陆地(记号为 0 )要么是水域(记号为 1 )。 + 我们从一块陆地出发,每次可以往上下左右 4 个方向相邻区域走,能走到的所有陆地区域,我们将其称为一座「岛屿」。 + 如果一座岛屿 完全 由水域包围,即陆地边缘上下左右所有相邻区域都是水域,那么我们将其称为 「封闭岛屿」。 + 请返回封闭岛屿的数目。 + + 示例 1: + 输入:grid = [[1,1,1,1,1,1,1,0],[1,0,0,0,0,1,1,0],[1,0,1,0,1,1,1,0],[1,0,0,0,0,1,0,1],[1,1,1,1,1,1,1,0]] + 输出:2 + 解释: + 灰色区域的岛屿是封闭岛屿,因为这座岛屿完全被水域包围(即被 1 区域包围)。 + + 1 <= grid.length, grid[0].length <= 100 + 0 <= grid[i][j] <=1 +*/ +/** + * @param {number[][]} grid + * @return {number} + */ + +// DFS。遍历二维矩阵,遇到陆地则对上下左右四个方向进行 DFS 查询。如果某个方向上的 DFS 遇到了边界则说明它不是封闭的,可以直接返回 false 结束 DFS +// 在 DFS 过程中遇到陆地的话,还需要把其标记为已遍历过(这里把它的值改成了 2),防止重复递归而爆栈(比如二维矩阵中有一个正方形的陆地,不标记的话它会在里面重复递归) +var closedIsland = function(grid) { + function dfs(arr, x, y) { + if (x < 0 || x >= arr.length || y < 0 || y >= arr[0].length) { + return false; + } + // 遇到不是陆地则直接返回 true 表示从这个方向延伸出去是封闭的 + if (arr[x][y] !== 0) { + return true; + } + // 标记为已访问过 + arr[x][y] = 2; + // 选择一个陆地后就必须把它所在的岛屿上的所有位置遍历完,即使它有一个方向已经遇到了边界确定不是封闭岛屿 + // 如果没遍历完就结束的话相当于把一块岛屿分成了几个几块岛屿,如果原先的这块岛屿是封闭的,这样遍历就可能会得到几块封闭岛屿,导致结果大于答案值 + const left = dfs(arr, x, y-1); + const right = dfs(arr, x, y+1); + const top = dfs(arr, x-1, y); + const bottom = dfs(arr, x+1, y); + return left && right && bottom && top; + } + + let ans = 0; + for(let i=0; i= arr.length || y < 0 || y >= arr[0].length) { + mark = 0; + continue; + } + if (arr[x][y] === 0) { + arr[x][y] = 2; + queue.push({x: x, y: y-1}); + queue.push({x: x, y: y+1}); + queue.push({x: x-1, y}); + queue.push({x: x+1, y}); + } + } + return mark; + } + let ans = 0; + for(let i=0; i nums[r]) { + l = mid + 1; + } else if (nums[mid] < nums[r]) { + r = mid; + } else { + r--; + } + } + return nums[l]; +} \ No newline at end of file diff --git "a/LeetCode/175. \347\273\204\345\220\210\344\270\244\344\270\252\350\241\250.sql" "b/LeetCode/175. \347\273\204\345\220\210\344\270\244\344\270\252\350\241\250.sql" new file mode 100644 index 0000000..c6e645c --- /dev/null +++ "b/LeetCode/175. \347\273\204\345\220\210\344\270\244\344\270\252\350\241\250.sql" @@ -0,0 +1,31 @@ +/* + 表1: Person + + +-------------+---------+ + | 列名 | 类型 | + +-------------+---------+ + | PersonId | int | + | FirstName | varchar | + | LastName | varchar | + +-------------+---------+ + PersonId 是上表主键 + 表2: Address + + +-------------+---------+ + | 列名 | 类型 | + +-------------+---------+ + | AddressId | int | + | PersonId | int | + | City | varchar | + | State | varchar | + +-------------+---------+ + AddressId 是上表主键 +   + 编写一个 SQL 查询,满足条件:无论 person 是否有地址信息,都需要基于上述两表提供 person 的以下信息:FirstName, LastName, City, State。 +*/ + + +-- 左连接 +select FirstName, LastName, City, State +from Person left join Address +on Person.PersonId = Address.PersonId; \ No newline at end of file diff --git "a/LeetCode/181. \350\266\205\350\277\207\347\273\217\347\220\206\346\224\266\345\205\245\347\232\204\345\221\230\345\267\245.sql" "b/LeetCode/181. \350\266\205\350\277\207\347\273\217\347\220\206\346\224\266\345\205\245\347\232\204\345\221\230\345\267\245.sql" new file mode 100644 index 0000000..8d59d25 --- /dev/null +++ "b/LeetCode/181. \350\266\205\350\277\207\347\273\217\347\220\206\346\224\266\345\205\245\347\232\204\345\221\230\345\267\245.sql" @@ -0,0 +1,48 @@ +/* + Employee 表包含所有员工,他们的经理也属于员工。每个员工都有一个 Id,此外还有一列对应员工的经理的 Id。 + + +----+-------+--------+-----------+ + | Id | Name | Salary | ManagerId | + +----+-------+--------+-----------+ + | 1 | Joe | 70000 | 3 | + | 2 | Henry | 80000 | 4 | + | 3 | Sam | 60000 | NULL | + | 4 | Max | 90000 | NULL | + +----+-------+--------+-----------+ + 给定 Employee 表,编写一个 SQL 查询,该查询可以获取收入超过他们经理的员工的姓名。在上面的表格中,Joe 是唯一一个收入超过他的经理的员工。 + + +----------+ + | Employee | + +----------+ + | Joe | + +----------+ +*/ + +-- 这里需要获取两次数据表信息,一次用于遍历员工,另一次用于查该员工的经理的收入 +-- 对于需要查两次表的,就得 from 两次表,这样会产生 n^2 个记录,n 是该表的行数。 +select a.Name as Employee +from Employee a, Employee b +where + a.ManagerId = b.Id + AND + a.Salary > b.Salary; + + +-- 不获取两次数据表,而是建立个临时表 +select a.Name as Employee +from Employee a, ( + select Salary, Id from Employee +) b +where + a.ManagerId = b.Id + AND + a.Salary > b.Salary + + +-- 连接两个表 +select a.Name as Employee +from Employee a inner join Employee b +on + a.ManagerId = b.Id + AND + a.Salary > b.Salary \ No newline at end of file diff --git "a/LeetCode/182. \346\237\245\346\211\276\351\207\215\345\244\215\347\232\204\347\224\265\345\255\220\351\202\256\347\256\261.sql" "b/LeetCode/182. \346\237\245\346\211\276\351\207\215\345\244\215\347\232\204\347\224\265\345\255\220\351\202\256\347\256\261.sql" new file mode 100644 index 0000000..7668975 --- /dev/null +++ "b/LeetCode/182. \346\237\245\346\211\276\351\207\215\345\244\215\347\232\204\347\224\265\345\255\220\351\202\256\347\256\261.sql" @@ -0,0 +1,38 @@ +/* + 编写一个 SQL 查询,查找 Person 表中所有重复的电子邮箱。 + + 示例: + + +----+---------+ + | Id | Email | + +----+---------+ + | 1 | a@b.com | + | 2 | c@d.com | + | 3 | a@b.com | + +----+---------+ + 根据以上输入,你的查询应返回以下结果: + + +---------+ + | Email | + +---------+ + | a@b.com | + +---------+ + 说明:所有电子邮箱都是小写字母。 + */ + +-- 等值连接后,使用 distinct 去重 +select distinct a.Email +from Person a inner join Person b +on + a.Email = b.Email + AND + a.Id != b.Id; + + +-- 先计算表中相同数据的个数,生成临时表再做筛选 +select Email from ( + select Email, count(Email) as num + from Person + group by Email +) temp_table +where num > 1 \ No newline at end of file diff --git "a/LeetCode/183. \344\273\216\344\270\215\350\256\242\350\264\255\347\232\204\345\256\242\346\210\267.sql" "b/LeetCode/183. \344\273\216\344\270\215\350\256\242\350\264\255\347\232\204\345\256\242\346\210\267.sql" new file mode 100644 index 0000000..cd997e2 --- /dev/null +++ "b/LeetCode/183. \344\273\216\344\270\215\350\256\242\350\264\255\347\232\204\345\256\242\346\210\267.sql" @@ -0,0 +1,36 @@ +/* + 某网站包含两个表,Customers 表和 Orders 表。编写一个 SQL 查询,找出所有从不订购任何东西的客户。 + + Customers 表: + + +----+-------+ + | Id | Name | + +----+-------+ + | 1 | Joe | + | 2 | Henry | + | 3 | Sam | + | 4 | Max | + +----+-------+ + Orders 表: + + +----+------------+ + | Id | CustomerId | + +----+------------+ + | 1 | 3 | + | 2 | 1 | + +----+------------+ + 例如给定上述表格,你的查询应返回: + + +-----------+ + | Customers | + +-----------+ + | Henry | + | Max | + +-----------+ +*/ + +-- not in +select Name as Customers from Customers +where Id not in ( + select CustomerId from Orders +) ; diff --git "a/LeetCode/191. \344\275\2151\347\232\204\344\270\252\346\225\260.js" "b/LeetCode/191. \344\275\2151\347\232\204\344\270\252\346\225\260.js" new file mode 100644 index 0000000..986a7c9 --- /dev/null +++ "b/LeetCode/191. \344\275\2151\347\232\204\344\270\252\346\225\260.js" @@ -0,0 +1,24 @@ +/* +191. 位1的个数 +编写一个函数,输入是一个无符号整数,返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为汉明重量)。 + +示例 1: +输入:00000000000000000000000000001011 +输出:3 +解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'。 +*/ + +/** + * @param {number} n - a positive integer + * @return {number} + */ + +// 对于任意数字 n,将 n 和 n-1 做与运算,都会把最后一个 1 的位变成 0。所以一直对两者做与运算,直至 n 变成 0 即可 +var hammingWeight = function(n) { + let res = 0; + while(n) { + n = n & (n - 1); + res++; + } + return res; +}; diff --git "a/LeetCode/196. \345\210\240\351\231\244\351\207\215\345\244\215\347\232\204\347\224\265\345\255\220\351\202\256\347\256\261.sql" "b/LeetCode/196. \345\210\240\351\231\244\351\207\215\345\244\215\347\232\204\347\224\265\345\255\220\351\202\256\347\256\261.sql" new file mode 100644 index 0000000..7853767 --- /dev/null +++ "b/LeetCode/196. \345\210\240\351\231\244\351\207\215\345\244\215\347\232\204\347\224\265\345\255\220\351\202\256\347\256\261.sql" @@ -0,0 +1,34 @@ +/* + 编写一个 SQL 查询,来删除 Person 表中所有重复的电子邮箱,重复的邮箱里只保留 Id 最小 的那个。 + + +----+------------------+ + | Id | Email | + +----+------------------+ + | 1 | john@example.com | + | 2 | bob@example.com | + | 3 | john@example.com | + +----+------------------+ + + Id 是这个表的主键。 + 例如,在运行你的查询语句之后,上面的 Person 表应返回以下几行: + + +----+------------------+ + | Id | Email | + +----+------------------+ + | 1 | john@example.com | + | 2 | bob@example.com | + +----+------------------+ +   + + 提示: + 执行 SQL 之后,输出是整个 Person 表。 + 使用 delete 语句。 +*/ + + +-- 还是笛卡尔积,可以先筛选出重复的并且 ID 更大的数据,再把它们删掉即可 +delete a from Person a, Person b +where + a.Email = b.Email + AND + a.Id > b.Id \ No newline at end of file diff --git "a/LeetCode/468. \351\252\214\350\257\201IP\345\234\260\345\235\200.js" "b/LeetCode/468. \351\252\214\350\257\201IP\345\234\260\345\235\200.js" new file mode 100644 index 0000000..e683f3f --- /dev/null +++ "b/LeetCode/468. \351\252\214\350\257\201IP\345\234\260\345\235\200.js" @@ -0,0 +1,117 @@ +/* +编写一个函数来验证输入的字符串是否是有效的 IPv4 或 IPv6 地址。 +IPv4 地址由十进制数和点来表示,每个地址包含4个十进制数,其范围为 0 - 255, 用(".")分割。比如,172.16.254.1; +同时,IPv4 地址内的数不会以 0 开头。比如,地址 172.16.254.01 是不合法的。 +IPv6 地址由8组16进制的数字来表示,每组表示 16 比特。这些组数字通过 (":")分割。比如,  2001:0db8:85a3:0000:0000:8a2e:0370:7334 是一个有效的地址。而且,我们可以加入一些以 0 开头的数字,字母可以使用大写,也可以是小写。所以, 2001:db8:85a3:0:0:8A2E:0370:7334 也是一个有效的 IPv6 address地址 (即,忽略 0 开头,忽略大小写)。 +然而,我们不能因为某个组的值为 0,而使用一个空的组,以至于出现 (::) 的情况。 比如, 2001:0db8:85a3::8A2E:0370:7334 是无效的 IPv6 地址。 +同时,在 IPv6 地址中,多余的 0 也是不被允许的。比如, 02001:0db8:85a3:0000:0000:8a2e:0370:7334 是无效的。 +说明: 你可以认为给定的字符串里没有空格或者其他特殊字符。 + +示例 1: +输入: "172.16.254.1" +输出: "IPv4" +解释: 这是一个有效的 IPv4 地址, 所以返回 "IPv4"。 + +示例 2: +输入: "2001:0db8:85a3:0:0:8A2E:0370:7334" +输出: "IPv6" +解释: 这是一个有效的 IPv6 地址, 所以返回 "IPv6"。 + +示例 3: +输入: "256.256.256.256" +输出: "Neither" +解释: 这个地址既不是 IPv4 也不是 IPv6 地址。 +*/ + +/** + * @param {string} IP + * @return {string} + */ + +// 使用正则匹配。先将字符串转化为数组,如果数组长度为 4 的话进入 IPv4 的判断逻辑;数组长度是 8 的话进入 IPv6 的判断逻辑 +// IPv6 的话只要每组数都是4位以内的16进制数就可以了 +// IPv4 的话每组数要是3位以内(考虑到前导0的情况即可能为 000,所以对 0 做特殊处理),并且要小于或等于 255 +var validIPAddress = function(IP) { + const arr4 = IP.split('.'); + const arr6 = IP.split(':'); + if (arr4.length === 4) { + if (arr4.every(item => { + return new RegExp(/^0$|^([1-9](\d){0,2})$/).test(item) && item <= 255; + })) { + return 'IPv4'; + } + } + if (arr6.length === 8) { + if (arr6.every(item => { + return new RegExp(/^[0-9a-fA-F]{1,4}$/).test(item); + })) { + return 'IPv6' + } + } + return 'Neither'; +}; + +/* +按 ip 地址的格式要求一条条规则进行校验 +IPV4: + 1. 由三个 . 间隔开分成四组 + 2. 每组只能有数字字符且不能为空,且大于 0 小于等于 255 + 3. 每组不能有前导 0 +IPV6: + 1. 由七个 : 间隔开分组八组 + 2. 每组只能是十六进制中允许的字符 0-9 或 a-f 或 A-F + 3. 每组不能为空,且长度不能大于 4 +*/ +var validIPAddress = function(IP) { + const isIpv4 = (function judgeIpv4(str) { + const arr4 = str.split('.'); + if (arr4.length !== 4) { + return false; + } + for(let i=0; i 3 + ) { + return false;; + } + const num = parseInt(item); + if (num < 0 || num > 255) { + return false; + } + for(let j=0; j '9') { + return false; + } + } + } + return true; + })(IP); + + const isIpv6 = (function judgeIpv6(str) { + const arr6 = str.split(':'); + if (arr6.length !== 8) { + return false; + } + for(let i=0; i 4) { + return false; + } + for(let j=0; j '9') && + (item[j] < 'a' || item[j] > 'f') && + (item[j] < 'A' || item[j] > 'F') + ) { + return false; + } + } + } + return true; + })(IP); + return isIpv4 ? 'IPv4' : (isIpv6 ? 'IPv6' : 'Neither'); +} diff --git "a/LeetCode/70. \347\210\254\346\245\274\346\242\257.js" "b/LeetCode/70. \347\210\254\346\245\274\346\242\257.js" new file mode 100644 index 0000000..4d92122 --- /dev/null +++ "b/LeetCode/70. \347\210\254\346\245\274\346\242\257.js" @@ -0,0 +1,91 @@ +/* +假设你正在爬楼梯。需要 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 阶 +*/ + +/** + * @param {number} n + * @return {number} + */ + +// 暴力破解,递归。每次都递归跳一个台阶和跳两个台阶的情况,直到已经跳过的台阶大于等于目标阶数时才终止递归(有很多重复的计算)。 +// 时间复杂度:O(2^n),会超时 +var climbStairs = function(n) { + function fn(sum) { + if (sum > n) { + return 0; + } + if (sum === n) { + return 1; + } + return fn(sum+1) + fn(sum+2); + } + return fn(0); +}; + +// 还是递归,不过使用一个记忆数组记录已经计算后的值,防止重复的递归计算 +// 时间复杂度:O(n),空间复杂度:O(n) +var climbStairs = function(n) { + const mark = []; + function fn(sum) { + if (sum > n) { + return 0; + } + if (sum === n) { + return 1; + } + if (mark[sum]) { + return mark[sum]; + } + mark[sum] = fn(sum+1) + fn(sum+2); + return mark[sum]; + } + return fn(0); +}; + + +// dp。当要跳到第 n 阶时,最好的情况就是在第 n-1 阶时跳 1 阶,或者在第 n-2 阶时跳 2 阶,这样往前一直寻找最优解即可 +// 时间复杂度:O(n),空间复杂度:O(n) +var climbStairs = function(n) { + const dp = []; + dp[1] = 1; + dp[2] = 2; + for(let i=3; i<=n; i++) { + dp[i] = dp[i-1] + dp[i-2]; + } + return dp[n]; +}; + + +// 由 dp 的公式得出,其实结果值就是一个斐波那契数列,所以直接迭代计算斐波那契数列,不用一个数组来记录前面的 dp 值 +// 时间复杂度:O(n),空间复杂度:O(1) +var climbStairs = function(n) { + let first = 1, second = 2; + if (n === 1) { + return first; + } else if (n === 2) { + return second; + } + let res; + for(let i=3; i<=n; i++) { + res = first + second; + first = second; + second = res; + } + return res; +}; diff --git "a/LeetCode/746. \344\275\277\347\224\250\346\234\200\345\260\217\350\212\261\350\264\271\347\210\254\346\245\274\346\242\257.js" "b/LeetCode/746. \344\275\277\347\224\250\346\234\200\345\260\217\350\212\261\350\264\271\347\210\254\346\245\274\346\242\257.js" new file mode 100644 index 0000000..bf59f11 --- /dev/null +++ "b/LeetCode/746. \344\275\277\347\224\250\346\234\200\345\260\217\350\212\261\350\264\271\347\210\254\346\245\274\346\242\257.js" @@ -0,0 +1,46 @@ +/* +数组的每个索引做为一个阶梯,第 i个阶梯对应着一个非负数的体力花费值 cost[i](索引从0开始)。 +每当你爬上一个阶梯你都要花费对应的体力花费值,然后你可以选择继续爬一个阶梯或者爬两个阶梯。 +您需要找到达到楼层顶部的最低花费。在开始时,你可以选择从索引为 0 或 1 的元素作为初始阶梯。 + +示例 1: +输入: cost = [10, 15, 20] +输出: 15 +解释: 最低花费是从cost[1]开始,然后走两步即可到阶梯顶,一共花费15。 + +示例 2: +输入: cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1] +输出: 6 +解释: 最低花费方式是从cost[0]开始,逐个经过那些1,跳过cost[3],一共花费6。 + +注意: +cost 的长度将会在 [2, 1000]。 +每一个 cost[i] 将会是一个Integer类型,范围为 [0, 999]。 +*/ + +/** + * @param {number[]} cost + * @return {number} + */ +var minCostClimbingStairs = function(cost) { + let last = 0; + let lastLast = 0; + for(let i=0; i= str.length) { + res.push(str); + } + for(let i=start; i 1. +斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。 +答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。 + +示例 1: +输入:n = 2 +输出:1 + +示例 2: +输入:n = 5 +输出:5 +  +提示: +0 <= n <= 100 +``` + +[题解:](https://github.com/DangoSky/algorithm/blob/master/%E5%89%91%E6%8C%87Offer/%E9%9D%A2%E8%AF%95%E9%A2%9810-%20I.%20%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0%E5%88%97.js) + +1. 单纯递归,会超时。时间复杂度 O(n^2),空间复杂度 O(1)。 + +2. 递归 + 记忆数组,防止对已经计算出来的值重复递归计算。时间复杂度 O(n),空间复杂度 O(n)。 + +3. DP,使用两个变量来保存上一个和上上一个值。时间复杂度 O(n),空间复杂度 O(1)。 + +4. 斐波那契数列还可以用矩阵来求解,印象中用矩阵是最快的(时间复杂度是 O(logN)),但已经忘了怎么写了 Orz。 + + +### 面试题11. 旋转数组的最小数字 + +``` +把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。   + +示例 1: +输入:[3,4,5,1,2] +输出:1 + +示例 2: +输入:[2,2,2,0,1] +输出:0 +``` + +[题解:](https://github.com/DangoSky/algorithm/blob/master/%E5%89%91%E6%8C%87Offer/%E9%9D%A2%E8%AF%95%E9%A2%9811.%20%E6%97%8B%E8%BD%AC%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%B0%8F%E6%95%B0%E5%AD%97.js) + +1. 直接循环判断 `numbers[i]` 是否会小于 `numbers[i-1]`,是的话就是旋转点。时间复杂度为 O(n)。 + +2. 二分,时间复杂度为 O(logN)。 + +通过数组旋转,可以将原数组切分为两个升序排序的子数组,左子数组较大,右子数组较小,如 `[3,4,5,1,2]`。通过二分取中间的数组元素 `numbers[mid]`,将其和数组右边界元素 `numbers[r]` 比较。 + +大于的话说明 `numbers[mid]` 在右子数组中,那么旋转点在 `numbers[mid]` 右边,所以将寻找范围缩小到 `[mid+1, r]`。 + +小于的话说明 `numbers[mid]` 在左子数组中,那么旋转点可能是 `numbers[mid]` 或者在其左边,所以将寻找范围缩小到 `[l, mid]`。 + +等于的话只需 `r--` 舍弃掉 `numbers[r]`,因为 `r--` 后旋转点还是在 `[l, r]` 上。 + +需要注意的是(采坑了 Orz),不能将 `numbers[mid]` 和 `numbers[l]` 比较,因为两者比较无法得出 `numbers[mid]` 是在左子数组还是在右子数组。例如 `[3, 4, 5, 1, 2]` 与 `[1, 2, 3, 4, 5]`,`numbers[mid]` 都大于 `numbers[l]`,但最小值一个在后面,一个在前面。 + + +### + +``` + +``` + +[题解:]() + +1. \ No newline at end of file diff --git "a/\345\211\221\346\214\207Offer/\351\235\242\350\257\225\351\242\23003. \346\225\260\347\273\204\344\270\255\351\207\215\345\244\215\347\232\204\346\225\260\345\255\227.js" "b/\345\211\221\346\214\207Offer/\351\235\242\350\257\225\351\242\23003. \346\225\260\347\273\204\344\270\255\351\207\215\345\244\215\347\232\204\346\225\260\345\255\227.js" new file mode 100644 index 0000000..279e078 --- /dev/null +++ "b/\345\211\221\346\214\207Offer/\351\235\242\350\257\225\351\242\23003. \346\225\260\347\273\204\344\270\255\351\207\215\345\244\215\347\232\204\346\225\260\345\255\227.js" @@ -0,0 +1,65 @@ +/* + 找出数组中重复的数字。 + 在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。 + + 示例 1: + 输入: + [2, 3, 1, 0, 2, 5, 3] + 输出:2 或 3 +*/ + +/** + * @param {number[]} nums + * @return {number} + */ + +// indexOf 和 lastIndexOf +// 时间复杂度应该是 O(n^2) +var findRepeatNumber = function(nums) { + for(let i=0; i { + return a - b; + }) + for(let i=0; i= 0) { + if(matrix[row][col] === target) { + return true; + } else if (matrix[row][col] > target) { + col--; + } else if (matrix[row][col] < target) { + row++ + } + } + return false; +} diff --git "a/\345\211\221\346\214\207Offer/\351\235\242\350\257\225\351\242\23005. \346\233\277\346\215\242\347\251\272\346\240\274.js" "b/\345\211\221\346\214\207Offer/\351\235\242\350\257\225\351\242\23005. \346\233\277\346\215\242\347\251\272\346\240\274.js" new file mode 100644 index 0000000..98dfc78 --- /dev/null +++ "b/\345\211\221\346\214\207Offer/\351\235\242\350\257\225\351\242\23005. \346\233\277\346\215\242\347\251\272\346\240\274.js" @@ -0,0 +1,32 @@ +/* +请实现一个函数,把字符串 s 中的每个空格替换成"%20"。 + +示例 1: +输入:s = "We are happy." +输出:"We%20are%20happy." + +限制:0 <= s 的长度 <= 10000 +*/ + +/** + * @param {string} s + * @return {string} + */ + +// replace + 正则表达式 +var replaceSpace = function(s) { + return s.replace(/ /g, '%20'); +}; + +// 使用一个新的字符串,遇到空格就拼接 %20,否则就拼接原来的字符 +var replaceSpace = function(s) { + let res = ''; + for(let i=0; i r1 || l2 > r2) { + return null; + } + let mid = 0; + for(let i=l2; i<=r2; i++) { + if (inorder[i] === preorder[l1]) { + mid = i; + break; + } + } + const leftSum = mid - l2; + const node = new TreeNode(preorder[l1]); + node.left = fn(l1 + 1, l1 + leftSum, l2, mid - 1); + node.right = fn(l1 + leftSum + 1, r1, mid + 1, r2); + return node; + } + return fn(0, preorder.length - 1, 0, inorder.length - 1); +} diff --git "a/\345\211\221\346\214\207Offer/\351\235\242\350\257\225\351\242\23009. \347\224\250\344\270\244\344\270\252\346\240\210\345\256\236\347\216\260\351\230\237\345\210\227.js" "b/\345\211\221\346\214\207Offer/\351\235\242\350\257\225\351\242\23009. \347\224\250\344\270\244\344\270\252\346\240\210\345\256\236\347\216\260\351\230\237\345\210\227.js" new file mode 100644 index 0000000..f56f7f9 --- /dev/null +++ "b/\345\211\221\346\214\207Offer/\351\235\242\350\257\225\351\242\23009. \347\224\250\344\270\244\344\270\252\346\240\210\345\256\236\347\216\260\351\230\237\345\210\227.js" @@ -0,0 +1,48 @@ +/* + 用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 ) + + 示例 1: + 输入: + ["CQueue","appendTail","deleteHead","deleteHead"] + [[],[3],[],[]] + 输出:[null,null,3,-1] + + 示例 2: + 输入: + ["CQueue","deleteHead","appendTail","appendTail","deleteHead","deleteHead"] + [[],[],[5],[2],[],[]] + 输出:[null,-1,null,null,5,2] + + 提示: + 1 <= values <= 10000 + 最多会对 appendTail、deleteHead 进行 10000 次调用 +*/ + +// 把 arr1 和 arr2 当做是两个栈,后进先出,所以只能使用 push 和 pop 方法 +// 入队的时候,都将数据放到 arr1 里面。 +// 出队的时候,如果 arr2 为空,就将 arr1 的所有元素 pop 到 arr2 中,这样 arr2 中的数据顺序就符合先进先出了。 +// 如果 arr2 不为空,就直接从 arr2 里取数据,这样就不用每次出队都将 arr1 里的数据放到 arr2 里 +var CQueue = function() { + this.arr1 = []; + this.arr2 = []; +}; + +/** + * @param {number} value + * @return {void} + */ +CQueue.prototype.appendTail = function(value) { + this.arr1.push(value); +}; + +/** + * @return {number} + */ +CQueue.prototype.deleteHead = function() { + if(this.arr2.length === 0) { + while(this.arr1.length) { + this.arr2.push(this.arr1.pop()); + } + } + return this.arr2.length === 0 ? -1 : this.arr2.pop(); +}; diff --git "a/\345\211\221\346\214\207Offer/\351\235\242\350\257\225\351\242\23010- I. \346\226\220\346\263\242\351\202\243\345\245\221\346\225\260\345\210\227.js" "b/\345\211\221\346\214\207Offer/\351\235\242\350\257\225\351\242\23010- I. \346\226\220\346\263\242\351\202\243\345\245\221\346\225\260\345\210\227.js" new file mode 100644 index 0000000..5edc195 --- /dev/null +++ "b/\345\211\221\346\214\207Offer/\351\235\242\350\257\225\351\242\23010- I. \346\226\220\346\263\242\351\202\243\345\245\221\346\225\260\345\210\227.js" @@ -0,0 +1,65 @@ +/* +写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项。斐波那契数列的定义如下: +F(0) = 0,   F(1) = 1 +F(N) = F(N - 1) + F(N - 2), 其中 N > 1. +斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。 +答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。 + +示例 1: +输入:n = 2 +输出:1 + +示例 2: +输入:n = 5 +输出:5 +  +提示: +0 <= n <= 100 +*/ + +/** + * @param {number} n + * @return {number} + */ + +// 递归,会超时 +var fib = function(n) { + if (n === 0) { + return 0; + } + if (n === 1) { + return 1; + } + return (fib(n-1) + fib(n-2)) % 1000000007; +}; + +// 递归 + 记忆数组,防止对已经计算出来的值重复递归计算 +// 时间复杂度 O(n),空间复杂度 O(n) +var fib = function(n) { + const arr = [0, 1]; + function fn(n) { + if (arr[n] !== undefined) { + return arr[n]; + } + arr[n] = (fn(n-1) + fn(n-2)) % 1000000007; + return arr[n]; + } + return fn(n); +}; + +// DP,使用两个变量来保存上一个和上上一个值,可以使得空间复杂度为 O(1) +var fib = function(n) { + let first = 0, second = 1; + if (n === 0) { + return first; + } + if (n === 1) { + return second; + } + for(let i=2; i<=n; i++) { + [first, second] = [second, (first + second) % 1000000007]; + } + return second; +} + +// 斐波那契数列还可以用矩阵来求解,印象中用矩阵是最快的(时间复杂度是 O(logN)),但已经忘了怎么写了。 \ No newline at end of file diff --git "a/\345\211\221\346\214\207Offer/\351\235\242\350\257\225\351\242\23010- II. \351\235\222\350\233\231\350\267\263\345\217\260\351\230\266\351\227\256\351\242\230.js" "b/\345\211\221\346\214\207Offer/\351\235\242\350\257\225\351\242\23010- II. \351\235\222\350\233\231\350\267\263\345\217\260\351\230\266\351\227\256\351\242\230.js" new file mode 100644 index 0000000..47c5193 --- /dev/null +++ "b/\345\211\221\346\214\207Offer/\351\235\242\350\257\225\351\242\23010- II. \351\235\222\350\233\231\350\267\263\345\217\260\351\230\266\351\227\256\351\242\230.js" @@ -0,0 +1,52 @@ +/* +一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。 + +答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。 + +示例 1: +输入:n = 2 +输出:2 + +示例 2: +输入:n = 7 +输出:21 + +提示: +0 <= n <= 100 +*/ + +/** + * @param {number} n + * @return {number} + */ +var numWays = function(n) { + var fib = function(n) { + const arr = [1, 1]; + function fn(n) { + if (arr[n] !== undefined) { + return arr[n]; + } + arr[n] = (fn(n-1) + fn(n-2)) % 1000000007; + return arr[n]; + } + return fn(n); + }; + return fib(n); +}; + +var numWays = function(n) { + var fib = function(n) { + let first = 1, second = 1; + if (n === 0) { + return first; + } + if (n === 1) { + return second; + } + for(let i=2; i<=n; i++) { + [first, second] = [second, (first + second) % 1000000007]; + } + return second; + } + return fib(n); +} diff --git "a/\345\211\221\346\214\207Offer/\351\235\242\350\257\225\351\242\23011. \346\227\213\350\275\254\346\225\260\347\273\204\347\232\204\346\234\200\345\260\217\346\225\260\345\255\227.js" "b/\345\211\221\346\214\207Offer/\351\235\242\350\257\225\351\242\23011. \346\227\213\350\275\254\346\225\260\347\273\204\347\232\204\346\234\200\345\260\217\346\225\260\345\255\227.js" new file mode 100644 index 0000000..16b2a62 --- /dev/null +++ "b/\345\211\221\346\214\207Offer/\351\235\242\350\257\225\351\242\23011. \346\227\213\350\275\254\346\225\260\347\273\204\347\232\204\346\234\200\345\260\217\346\225\260\345\255\227.js" @@ -0,0 +1,51 @@ +/* + 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。   + + 示例 1: + 输入:[3,4,5,1,2] + 输出:1 + + 示例 2: + 输入:[2,2,2,0,1] + 输出:0 +*/ +/** + * @param {number[]} numbers + * @return {number} + */ + +// 直接循环判断 numbers[i] 是否会小于 numbers[i-1],是的话就是旋转点。时间复杂度为 O(n) +var minArray = function(numbers) { + for(let i=1; i numbers[r]) { + l = mid + 1; + } else if (numbers[mid] < numbers[r]) { + r = mid; + } else { + r--; + } + } + return numbers[l]; +};