暑假到了...作为算法小渣的我来说
很有必要开启算法的上山之路!
本文章用于记录在路途中的一步一脚印
~不拍照的那种
坚持周期性更新(一天3篇Easy, 一天2篇Mid, 一天1篇Hard)
为了两年后毕业后面试的自己打下基础
奋进吧!少儿郎...
提示: 每一道题均有实例、解题思路、解答代码、执行结果
要是对您有所帮助启发点个小赞噢~
希望你的算法之路,也有我的陪伴~
第一天 - 07.08
前记: 今天是高考的最后一天...望考生们不负自己的努力
Easy
1.两数之和
2.整数反转
3.罗马数字转整数
两数之和
给定一个整数数组 nums 和一个目标值 target,
请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。
实例
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
解答思路
1.关键在于target-当前数组的剩余值,等于数组剩余值中的某一个
2.利用对象key为值, value为下标
解答代码
var twoSum = function(nums, target) {
const prevNums = {}; // 存储的对象
for (let i = 0; i < nums.length; i++) {
const curNum = nums[i]; // 当前数组值
const targetNum = target - curNum; // 当前剩余值
const targetNumIndex = prevNums[targetNum]; // 在存储对象中key为当前剩余值时候 -> 获取value下标值
if (targetNumIndex !== undefined) { // 当存储对象有该下标时
return [targetNumIndex, i]; // 返回目标下标和当前下标
}
prevNums[curNum] = i; // 否则存储value -> 下标和key -> 数值
}
}
代码执行结果
输入:
[2,7,11,15]
9输出
[0,1]预期结果
[0,1]
整数反转
给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。
注意反转后整数可能溢出情况 -> 返回0
实例
示例 1:
输入: 123
输出: 321
示例 2:
输入: -123
输出: -321
示例 3:
输入: 120
输出: 21
解答思路
1.反转整数向左移一位同时加上原整数的最后一位 -> 原整数去掉最后一位 -> 至到原整数为0
2.注意需要将反转整数转为32位有符号整数
3.若溢出 -> 反转整数不等于自身 -> 溢出为Infinity
解答代码
var reverse = function(x) {
let reverseNumber = 0 // 反转整数为0
while(x) {
reverseNumber = reverseNumber * 10 + x % 10 // 反转整数向左移一位 再加上x最后一位数字
x = (x / 10) | 0 // 原整数去掉最后一位数字 x | 0 -> 强制转换为32位有符号整数
}
return (reverseNumber | 0) === reverseNumber ? reverseNumber : 0
// 反转整数强制转换为32位有符号整数 -> 若不等于自身 -> 判断溢出 -> 值为0
};
代码执行结果
输入
12345输出
54321预期结果
54321
罗马数字转整数
罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。
字符 数值
I 1
V 5
X 10
L 50
C 100
D 500
M 1000
例如, 罗马数字 2 写做 II ,即为两个并列的 1。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。
通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。
数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。
同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况:
I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。
X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。
C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。
给定一个罗马数字,将其转换成整数。输入确保在 1 到 3999 的范围内。
实例
示例 1:
输入: "IX"
输出: 9
示例 2:
输入: "LVIII"
输出: 58
解释: L = 50, V= 5, III = 3.
示例 3:
输入: "MCMXCIV"
输出: 1994
解释: M = 1000, CM = 900, XC = 90, IV = 4.
解答思路
1.创建一个罗马字符的对象 -> 对应字符对应数值
2.贪心思想 -> 由最大的字符往最小的字符一一转换
3.当前罗马字符 < 右边罗马字符时 -> 整数减去当前罗马字符代表的数值 反之则加
4.遍历整个罗马字符完成
解答代码
var romanToInt = function(s) {
const roman = { // 创建罗马字符对象
'I': 1,
'V': 5,
'X': 10,
'L': 50,
'C': 100,
'D': 500,
'M': 1000,
}
const length = s.length // 罗马字符串长度
let result = 0 // 转换整数
for(let i = 0; i < s.length; i++ ) {
const currentNum = roman[s[i]] // 当罗马字符对应数值
if(i < length - 1 && currentNum < roman[s[i+1]]) { // 判断为倒数第二个字符串 并且当前罗马对应数值小于右边对应数值
result -= currentNum // 减去当前值
} else {
result += currentNum // 反之加上当前值
}
}
return result // 返回转换后的整数
};
代码执行结果
输入
"MCMXCIV"输出
1994预期结果
1994
第二天 - 07.09
前记: 写了好久小程序,和小妹妹出去散散步
Mid
1.两数相加
2.无重复字符的最长子串
两数相加
给出两个 非空 的链表用来表示两个非负的整数。
其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
实例
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
解答思路
1.因为倒叙
2.链表取出l1和l2的首个数字相加 -> 为sum
3.sum取余10 -> 存入新链表中(防止为两位数)
4.若sum >= 10 -> 向下一个链表next + 1
解答代码
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} l1
* @param {ListNode} l2
* @return {ListNode}
*/
var addTwoNumbers = function(l1, l2) {
let node = new ListNode('head'); // 表头(上方的ListNode链表方法)
let temp = node; // 当前指向表头
let add = 0; // 是否进一
let sum = 0; // 当前总和值为
while(l1 || l2){ //遍历,直到最长链表的都为空
// 当前l1链表值和l2链表值相加(无值时为0)且判断l1+l2 > 10时 -> 需要+1
sum = (l1 ? l1.val : 0) + (l2 ? l2.val : 0) + add;
temp.next = new ListNode(sum % 10); // 当前总和取余存入新链表中
temp = temp.next; // 指向下一链表
add = sum >= 10 ? 1 : 0; // 判断总和是否 >= 10
l1 && (l1 = l1.next); // l1指向下一链表
l2 && (l2 = l2.next); // l2指向下一链表
}
// 当最后一次add存在时 -> 最后相加 >= 10了 -> 再存入新链表中
add && (temp.next = new ListNode(add));
return node.next; // 返回新链表的next结果
};
代码执行结果
输入
[2,4,3]
[5,6,4]输出
[7,0,8]预期结果
[7,0,8]
无重复字符的最长子串
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
实例
示例 1:
输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。。
解答思路
1.遍历字符串每个字符
2.用一个数组存储每个字符
3.若数组中存在当前字符 -> 清除数组当前字符和前的数据
4.判断max取整个过程的最长长度
解答代码
var lengthOfLongestSubstring = function(s) {
let arr = [], max = 0 // 存储到数组中,无重复字符的最长长度
for(let i = 0; i < s.length; i++) { // 遍历每一个字符
let index = arr.indexOf(s[i]) // 数组中是否有当前字符
if(index !== -1) {
arr.splice(0, index+1); // 若已存在 -> 清除已有字符前面的
}
arr.push(s.charAt(i)) // 存入当前字符 -> 至少长度为1
max = Math.max(arr.length, max) // 判断当前数组长度与max大小
}
return max // 返回最长长度
};
代码执行结果
输入
"dvdf"输出
3预期结果
3
第三天 - 07.10
前记: 打了会游戏,困难题肝了快3小时,还是不太清除官方做法...
Hard
1.正则表达式匹配
正则表达式匹配
给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.' 和 '*' 的正则表达式匹配。
'.' 匹配任意单个字符
'*' 匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。
说明:
s 可能为空,且只包含从 a-z 的小写字母。
p 可能为空,且只包含从 a-z 的小写字母,以及字符 . 和 *。
注意可不要使用JS的正则表达式呀~那这道题将毫无意义
实例
示例 1:
输入:
s = "aa"
p = "a"
输出: false
解释: "a" 无法匹配 "aa" 整个字符串。
示例 2:
输入:
s = "ab"
p = ".*"
输出: true
解释: ".*" 表示可匹配零个或多个('*')任意字符('.')。
示例 3:
输入:
s = "aab"
p = "c*a*b"
输出: true
解释: 因为 '*' 表示零个或多个,这里 'c' 为 0 个, 'a' 被重复一次。因此可以匹配字符串 "aab"。
示例 4:
输入:
s = "mississippi"
p = "mis*is*p*."
输出: false
解答思路
1.定义基础返回条件,p的长度为0时,s的长度为0则表示匹配,s的长度不为0则表示不匹配
2.定义匹配状态match,即为第一个字符的互相比较,如果相等或p[0]为'.'则为true
3.当p没有模式的时候,我们根据match的状态进行返回,如果match为false,直接返回false,如果为true,那么进行下一步的判断getIsMactch(s.slice(1), p.slice(1)
4.当p有模式的时候,有两种情况:一是s匹配0个字符,则进行getIsMactch(s, p.slice(2)),二是s匹配1个字符,递归下去,用来表示s匹配多个s,这样如果match为false,直接返回false,如果为true,那么进行getIsMactch(s.slice(1), p)。这两种只要一个能成立就够了
5.返回匹配结果
解答代码
var isMatch = function (s, p) {
let getIsMactch = (s, p) => {
// 判断,如果传入p的长度为0,那么,必须s的长度也为0才会返回true
if (p.length === 0) {
return !s.length
}
// 判断第一个字符是否相等
let match = false
if (s.length > 0 && (s[0] === p[0] || p[0] === '.')) {
match = true
}
//p有模式的
if (p.length > 1 && p[1] === "*") {
// 如果有"*"字符,回溯字符
// 第一种情况:s*匹配0个字符
// 第二种情况:s*匹配1个字符,递归下去,用来表示s*匹配多个s*
return getIsMactch(s, p.slice(2)) || (match && getIsMactch(s.slice(1), p))
} else {
return (match && getIsMactch(s.slice(1), p.slice(1)))
}
}
return getIsMactch(s, p) // 返回最终调用方法匹配结果
}
/**
* 官方解答代码
*/
var isMatch = function(s, p) {
function matches(s, p, i, j) {
if (i == 0) {
return false;
}
if (p.charAt(j - 1) === '.') {
return true;
}
return s[i - 1] === p[j - 1];
}
let m = s.length
let n = p.length
let f = []
for (let i = 0; i <= m; i++) {
f.push(new Array(n + 1).fill(false))
}
f[0][0] = true
for (let i = 0; i <= m; ++i) {
for (let j = 1; j <= n; ++j) {
if (p[j - 1] == '*') {
f[i][j] = f[i][j - 2];
if (matches(s, p, i, j - 1)) {
f[i][j] = f[i][j] || f[i - 1][j];
}
}
else {
if (matches(s, p, i, j)) {
f[i][j] = f[i - 1][j - 1];
}
}
}
}
return f[m][n]
}
代码执行结果
输入
"mississippi"
"misisp*."输出
false预期结果
false
第四天 - 07.11
前记: 噢吼吼吼吼吼,写了一天小程序。等会看小白船
Easy
1.回文数
2.最长公共前缀
3.有效的括号
两数之和
判断一个整数是否是回文数。
回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。
实例
示例 1:
输入: 121
输出: true
示例 2:
输入: -121
输出: false
解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。
示例 3:
输入: 10
输出: false
解释: 从右向左读, 为 01 。因此它不是一个回文数。
解答思路
1.将整数转为字符串
2.字符串倒置
3.判断字符串倒置和原整数字符串是否一致
解答代码
var isPalindrome = function(x) {
const reserveStr = x.toString().split("").reverse().join("")
return reserveStr === x.toString()
}
代码执行结果
输入:
121
9输出
true预期结果
true
最长公共前缀
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 ""。
实例
示例 1:
输入: ["flower","flow","flight"]
输出: "fl"
示例 2:
输入: ["dog","racecar","car"]
输出: ""
解释: 输入不存在公共前缀。
说明:
所有输入只包含小写字母 a-z 。
解答思路
1.初始化任意一个数组字符串(原理: 公共前缀小于任意一个字符串)
2.开始遍历和对比字符串数组 -> 若公共前缀与当前不相等 -> 截取之前的公共前缀
3.遍历完成返回公共前缀 -> 若公共前缀为空字符 -> 提前返回
解答代码
var longestCommonPrefix = function(strs) {
if(strs.length == 0) // 如果字符数组为空数组 -> 返回为空
return "";
let ans = strs[0]; // 初始化公共前缀第一个数组字符串
for(let i = 1; i < strs.length; i++) { // 遍历字符数组
let j = 0;
// 公共前缀小于初始化字符串长度 并且 小于当前字符串长度
for(;j < ans.length && j < strs[i].length; j++) {
// 公共前缀字符 和 当前数组字符串字符不相等时 -> break
if(ans[j] != strs[i][j])
break;
}
// 截取目前相等的公共前缀
ans = ans.substr(0, j);
if(ans === "") // 若公共前缀为空 -> 直接返回无需遍历
return ans;
}
return ans; // 遍历完成,返回公共前缀
};
代码执行结果
输入
["flower","flow","flight"]输出
"fl"预期结果
"fl"
有效的括号
给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。
实例
示例 1:
输入: "()[]{}"
输出: true
示例 2:
输入: "(]"
输出: false
示例 3:
输入: "([)]"
输出: false
示例 4:
输入: "{[]}"
输出: true
解答思路
1.因为空字符串可被认为是有效字符串 -> 利用对象的key和value只提取出括号字符
2.利用栈思想 -> 先近后出原理 -> 这里将括号字符定义为开、关两种
3.若 开 字符 -> 存入栈中对应 闭 字符
4.若 闭 字符 -> 取出栈顶字符 -> 判断是否相等
5.若不相等 -> 返回false / 若遍历结束后栈为空 -> 返回true
解答代码
var isValid = function(s) {
// 括号字符分为 开、关 两种
const brackets = { // 定义括号对象 -> key为开、value为闭
'(': ')',
'{': '}',
'[': ']'
}
let stack = [] //定义空栈
let top = undefined // 栈顶
for(let char of s) { // 遍历字符
let value
if((value = brackets[char])) { // 若是对象key的 开 字符 -> 赋值给value
stack.push(value) // 存入栈中对应 闭 字符
} else { // 若是 闭 字符
top = stack.pop() // 取出栈顶
if(top !== char) { // 判断是否栈顶值等于对应当前 闭 字符
return false // 若不相等 -> 返回false
}
}
}
return stack.length === 0 // 遍历结束,栈为空 -> 返回true
};
代码执行结果
输入
"([)]"输出
false预期结果
false
第五天 - 07.13
前记: emmm这个暑假有点懒散
Mid
1.最长回文子串
2.Z 字形变换
两数相加
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
实例
示例 1:
输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。
示例 2:
输入: "cbbd"
输出: "bb"
解答思路
1.找最长回文字符串 -> 使用动态规划思想
2.从一个字符往两边延伸开 -> 且两边字符串也是回文串 -> 仍标为true
解答代码
var longestPalindrome = function(s) {
// babad
// tag : dp
if (!s || s.length === 0) return "";
let res = s[0]; // 若不为空,初始化回文子串为第一个
const dp = [];
// 倒着遍历简化操作, 这么做的原因是dp[i][..]依赖于dp[i + 1][..]
for (let i = s.length - 1; i >= 0; i--) {
dp[i] = [];
for (let j = i; j < s.length; j++) {
// 如果为第一个字符 -> 标记为true
if (j - i === 0) dp[i][j] = true;
// 如果第二个字符且相等字符 -> 标记为true
else if (j - i === 1 && s[i] === s[j]) dp[i][j] = true;
// 如果一个字符串是回文串且两边字符串也是回文串,那么它一定还是一个回文串
else if (s[i] === s[j] && dp[i + 1][j - 1]) {
dp[i][j] = true;
}
// 标记位为true 且 长度大于现回文子串长度
if (dp[i][j] && j - i + 1 > res.length) {
// 更新截取回文子串
res = s.slice(i, j + 1);
}
}
}
return res;
};
代码执行结果
输入
"babad"输出
"aba"预期结果
"bab"
Z 字形变换
将一个给定字符串根据给定的行数,以从上往下、从左到右进行 Z 字形排列。
比如输入字符串为 "LEETCODEISHIRING" 行数为 3 时,排列如下:
L C I R
E T O E S I I G
E D H N
之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:"LCIRETOESIIGEDHN"。
实例
示例 1:
输入: s = "LEETCODEISHIRING", numRows = 3
输出: "LCIRETOESIIGEDHN"
示例 2:
输入: s = "LEETCODEISHIRING", numRows = 4
输出: "LDREOEIIECIHNTSG"
解释:
L D R
E O E I I
E C I H N
T S G
解答思路
1.题目最后是需要转换后的字符串newStr -> newStr是根据Z字形的行数重新拼接
2.可以根据输入行数来移动数组下标(上升或下降) -> 存入对应同一行的字符
3.最后再拼接数组中每个字符串 -> 新的Z字形变换
解答代码
var convert = function(s, numRows) {
// 行数为1直接返回字符串
if(numRows === 1) return s
// 判断字符长度是否大于行数取最小
const len = Math.min(s.length, numRows)
// 每行字符串数组
let rowStr=[]
// 初始化字符串数组为空字符串
for(let i = 0; i < len; i++) {
rowStr[i] = ""
}
// 定义索引和位置为上升还是下降
let index = 0, down = false
// 遍历字符串
for (const c of s) {
rowStr[index] += c
// 若在顶部或在最底部 -> 更换标示
if(index === 0 || index === numRows - 1) down = !down
// 索引改变
index += down ? 1 : -1
}
// 提取每行字符串 -> 拼接新字符串
let res = ""
for(const str of rowStr) {
res += str
}
return res
};
代码执行结果
输入
"PAYPALISHIRING"
3输出
"PAHNAPLSIIGYIR"预期结果
"PAHNAPLSIIGYIR"
第六天 - 07.14
前记: 今天看到了掘金上的广告,琢磨了一天,第一次应聘了字节跳动的夏令营,有点兴奋!希望自己可以幸运一点~
Hard
1.合并K个排序链表
合并K个排序链表
合并 k 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。
实例
示例 1:
输入:
[
1->4->5,
1->3->4,
2->6
]
输出: 1->1->2->3->4->4->5->6
解答思路
1.注意有k个已经排序过的链表 -> 可以采用暴力遍历一遍整个链表放入数组中 -> 再数组sort排序值 -> 再数组变为合并的排序链表
2.看了官方解答 -> 采用双指针逐一遍历k个链表
3.简单来说有k个链表[a, b, c, d]和新new链表 -> a+b为new -> new+c为new -> new+d为new -> 遍历完成
解答代码
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode[]} lists
* @return {ListNode}
*/
var mergeKLists = function(lists) {
let len = lists.length; // 链表长度
if(len == 0) return null; // 若空
if(len == 1) return lists[0]; // 若只有一条(无须合并)
let heap = new ListNode(); // 创建新链表
heap.next = lists[0]; // 指向第一个链表
for(let i = 1; i < len; i++){ // 遍历链表
let origh = heap; // 初始化新的合并链表
let cur1 = heap.next; // 新合并链表的next
let cur2 = lists[i]; // lists下一个链表
while(cur1 != null && cur2 != null){ // 均不为空时
// origh指向小的值同时移动cur1或cur2的next
if(cur1.val >= cur2.val) {
origh.next = cur2;
cur2 = cur2.next;
}else{
origh.next = cur1;
cur1 = cur1.next;
}
// 移动合并列表next
origh = origh.next;
}
// 最后若存在 -> 该链表中最大的值
if(cur1) origh.next = cur1;
if(cur2) origh.next = cur2;
}
return heap.next; // 遍历完成 -> 返回合并链表的next
};
/**
* 一个heap链表 -> 空间复杂度O(1)
* 时间复杂度 -> 若每个k链表中的元素有n个 -> O(kn)
*/
代码执行结果
输入
[[1,4,5],[1,3,4],[2,6]]输出
[1,1,2,3,4,4,5,6]预期结果
[1,1,2,3,4,4,5,6]
第七天 - 07.16
前记: 今天把小程序简历给完结了,打算出篇文章介绍一下
Easy
1.合并两个有序链表
2.删除排序数组中的重复项
3.移除元素
合并两个有序链表
将两个升序链表合并为一个新的 升序 链表并返回。
新链表是通过拼接给定的两个链表的所有节点组成的。
实例
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
解答思路
1.很像第六天Hard题的简化版可以参考一下噢
2.照常链表迭代即可
3.最后还会剩一个全场最大值,要记得next进去噢
解答代码
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} l1
* @param {ListNode} l2
* @return {ListNode}
*/
var mergeTwoLists = function(l1, l2) {
const list = new ListNode(-1) // 初始化合并链表第一个数 -1
let merge = list // 浅拷贝
while(l1 && l2) { // 当l1和l2链中均有值时
if(l1.val >= l2.val) {
merge.next = l2 // 合并链表next指向较小值
l2 = l2.next
} else {
merge.next = l1
l1 = l1.next
}
merge = merge.next // 移动合并链表指针
}
merge.next = l1 === null ? l2 : l1 // 最后还会有一个最大值
return list.next // 返回该链表的next
};
代码执行结果
输入:
[1,2,4]
[1,3,4]输出
[1,1,2,3,4,4]预期结果
[1,1,2,3,4,4]
删除排序数组中的重复项
给定一个排序数组,你需要在 原地 删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
实例
示例 1:
给定数组 nums = [1,1,2],
函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。
示例 2:
给定 nums = [0,0,1,1,1,2,2,3,3,4],
函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。
解答思路
1.使用indexOf判断元素第一次出现的下标,不等于当前则重复,删除它
2.删除数组长度减1,所以下标要往前移动一位
解答代码
/**
* @param {number[]} nums
* @return {number}
*/
var removeDuplicates = function(nums) {
for(let i = 0; i < nums.length; i++) {
if(nums.indexOf(nums[i]) !== i) {
nums.splice(i, 1)
i --
}
}
return nums.length
};
代码执行结果
输入
[0,0,1,1,1,2,2,3,3,4]输出
[0,1,2,3,4]预期结果
[0,1,2,3,4]
移除元素
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
实例
示例 1:
给定 nums = [3,2,2,3], val = 3,
函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。
示例 2:
给定 nums = [0,1,2,2,3,0,4,2], val = 2,
函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。
注意这五个元素可为任意顺序。
解答思路
1.简单的迭代判断一下是否有该val值
2.有 -> nums.splice一下
3.无 -> 返回新数组长度
解答代码
var removeElement = function(nums, val) {
let i // 定义返回索引
while (nums.indexOf(val) !== -1) { // 若数组中存在该值
i = nums.indexOf(val)
nums.splice(i, 1) // 移除该值
}
return nums.length // 返回长度
};
代码执行结果
输入
[3,2,2,3]
3输出
[2,2]预期结果
[2,2]
第八天 - 07.17
前记: 今天也是召唤师峡谷的一天
Mid
1.字符串转换整数 (atoi)
2.盛最多水的容器
字符串转换整数 (atoi)
请你来实现一个 atoi 函数,使其能将字符串转换成整数。
首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。
在任何情况下,若函数不能进行有效的转换时,请返回 0 。
实例
示例 1:
输入: "42"
输出: 42
示例 2:
输入: "4193 with words"
输出: 4193
解释: 转换截止于数字 '3' ,因为它的下一个字符不为数字。
示例 3:
输入: "words and 987"
输出: 0
解释: 第一个非空字符是 'w', 但它不是数字或正、负号。
因此无法执行有效的转换。
示例 4:
输入: "-91283472332"
输出: -2147483648
解释: 数字 "-91283472332" 超过 32 位有符号整数范围。
因此返回 INT_MIN (−231) 。
解答思路
1.利用Js的parseInt()函数 -> 无视开头空格 -> 返回有符号整数 -> 无视整数部分后的字符
2.我们需要判断的是 -> 范围在32位内(含) -> 其他情况返回0
解答代码
var myAtoi = function(str) {
const number = parseInt(str, 10);
if(isNaN(number)) { // 其他情况返回0
return 0;
}
// 判断在32位内
return result = number <= Math.pow(-2, 31) ? Math.pow(-2, 31) : number >= Math.pow(2, 31) ? Math.pow(2, 31) - 1 : number
}
代码执行结果
输入
"+-3241aac22"输出
0预期结果
0
盛最多水的容器
给你 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:
输入:[1,8,6,2,5,4,8,3,7]
输出:49
解答思路
1.双指针 -> 指开头和结尾
2.当开头指针 > 结尾指针时 -> 遍历结束
3.判断高度较短的指针向中间移动
4.当前容积和最高容积进行比较并赋值
解答代码
var maxArea = function (height) {
let res = 0, i = 0, j = height.length - 1, cur = 0;
while (i < j) { // i为左指针,j为右指针
// 高度为两边较短一边
let h = height[i] < height[j] ? height[i] : height[j];
cur = h * (j - i); // 当前容积
res = cur > res ? cur : res; // 判断最高容积
if (height[i] < height[j]) { // 移动较短的指针
i++;
} else {
j--;
}
}
return res; // 返回最高容积
};
代码执行结果
输入
[1,8,6,2,5,4,8,3,7]输出
49预期结果
49
第九天 - 07.18
前记: 今天参加codeJump的夏令营笔试,一直没有看邮箱!!等短信通知...结果最后40分钟才看到了考试的短信
唉总体都是算法的题目,自己还是弱了...而且150分钟,自己迟到作死,只有40分钟笔试。
给自己一个警钟,不单要看官网应聘流程和短信,还要看邮箱
Hard
1.寻找两个正序数组的中位数
寻找两个正序数组的中位数
给定两个大小为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。
请你找出这两个正序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。
你可以假设 nums1 和 nums2 不会同时为空。
实例
示例 1:
nums1 = [1, 3]
nums2 = [2]
则中位数是 2.0
示例 2:
nums1 = [1, 2]
nums2 = [3, 4]
则中位数是 (2 + 3)/2 = 2.5
解答思路
1.暴力算法 -> 合并 -> 排序 -> 判断奇偶 -> 返回(时间复杂度O(m+n) -> 不符合题意)
2.二分法(时间复杂度O(log(min(m, n))))-> 个人琢磨了个把小时还是没太理解,只能参照代码放上
3.将自己理解注释在代码中
解答代码
/**
* 暴力解法
* 时间复杂度O(m+n)
* 空间复杂度O(0)
*/
var findMedianSortedArrays = function(nums1, nums2) {
// 合并 -> 排序 -> 避免负数
let nums3 = nums1.concat(nums2).sort((a,b)=>a-b);
let length = nums3.length;
if(length%2 == 0){
return (nums3[length/2-1] + nums3[length/2])/2
}else{
return nums3[Math.floor(length/2)]
}
};
/**
* 二分法
* 时间复杂度O(log(min(m, n)))
* 空间复杂度O(0)
*/
var findMedianSortedArrays = function(nums1, nums2) {
// 保证m是最短的数组长度
if (nums1.length > nums2.length) {
[nums1, nums2] = [nums2, nums1]
}
const m = nums1.length
const n = nums2.length
let low = 0
let high = m
while(low <= high) {
const i = low + Math.floor((high - low) / 2) // nums1的指针
const j = Math.floor((m + n + 1) / 2) - i // nums2的指针
// 判断数组越界
const maxLeftA = i === 0 ? -Infinity : nums1[i-1]
const minRightA = i === m ? Infinity : nums1[i]
const maxLeftB = j === 0 ? -Infinity : nums2[j-1]
const minRightB = j === n ? Infinity : nums2[j]
if (maxLeftA <= minRightB && minRightA >= maxLeftB) {
return (m + n) % 2 === 1 // 判断奇偶性
? Math.max(maxLeftA, maxLeftB)
: (Math.max(maxLeftA, maxLeftB) + Math.min(minRightA, minRightB)) / 2
} else if (maxLeftA > minRightB) { // 当nums1左数字大于nums2右数字时
high = i - 1 // 往high指针左移一位
} else {
low = low + 1 // 往low指针右移一位
}
}
};
代码执行结果
输入
[3]
[-2,-1]输出
-1.0预期结果
-1.0
第十天 - 07.19
前记: 总体将ES6 2015~2019复习了一遍新特性
总结: 感觉这样记录算法很累赘 20题左右就要拉很长了
还是决定题解放别的地方 -> 每50道算法发一篇思路带题解链接的文章
Easy
1.实现 strStr()
2.搜索插入位置
3.外观数列
第十一天 - 07.20
前记: 想逃离生活这一切
Mid
1.三数之和
2.电话号码的字母组合
第十二天 - 07.21
前记: 滴滴滴
Hard
1.缺失的第一个正数
第十三天 - 07.22
前记: 睡了好久好久 - 最近喜欢看爽文小说了,我去
Easy
1.最大子序和
2.加一
3.x 的平方根
4.爬楼梯
第十四天 - 07.23
前记: 小帅哥加量不加价噢
Mid
1.删除链表的倒数第N个节点
2.括号生成
3.两数相除
第十五天 - 07.24
前记: 明天要和一个小伙伴去惠州潇洒几天咯
Hard
1.接雨水 2.通配符匹配
第十六天 - 07.30
前记: 我回来了!去惠州潇洒了几天,深感要赚钱
Easy
1.合并两个有序数组
2.对称二叉树
3.二叉树的最大深度
第十七天 - 07.31
前记: 继续加油
Mid
1.搜索旋转排序数组
2.在排序数组中查找元素的第一个和最后一个位置
3.有效的数独
第十八天 - 08.01
前记: 八月你好呀!麻烦对我好一点哈哈~ 要坚持努力去大厂
Hard
1.最小覆盖子串
尾言
提前前挂一个尾言嘻嘻~
会坚持持续更新,可能做题方向不太正确时
各位大佬们还望不吝赐教一下!✨
拍照嘛,小编摄影光线贼好
小屋随时欢迎你们到来~
也欢迎各位dalao们的建议噢~