你需要掌握三种核心的能力:编程、写作、设计!
前言
算法这个活动很严,每天必须打卡,而且不限制语言,群内已有PHP、Python、Java、Javascript等语言,欢迎大家参加,并坚持。能坚持的公众号回复算法。本公众号以JS为主,解题思路均以js来举例。

罗马数字转整数
罗马数字包含以下七种字符: 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:
输入: "III"
输出: 3示例 2:
输入: "IV"
输出: 4示例 3:
输入: "IX"
输出: 9示例 4:
输入: "LVIII"
输出: 58
解释: L = 50, V= 5, III = 3.示例 5:
输入: "MCMXCIV"
输出: 1994
解释: M = 1000, CM = 900, XC = 90, IV = 4.题解:
把特殊的情况特殊处理,利用map来存取。
执行用时:236ms;内存消耗:42.6MB;
var romanToInt = function(s) {
let numMap=new Map();
let num=0;
let keys=["I","IV","V","IX","X","XL","L","XC","C","CD","D","CM","M"];
let values=[1,4,5,9,10,40,50,90,100,400,500,900,1000];
keys.forEach((a,i)=>numMap.set(keys[i],values[i]))
for(let i = 0;i < s.length;) {
if(i + 1 < s.length && numMap.has(s.substring(i, i+2))) {
num += numMap.get(s.substring(i, i+2));
i += 2;
} else {
num += numMap.get(s.substring(i, i+1));
i ++;
}
}
return num;
}思路2:正则法
思路和上面的一样,换成了正则来分割字符串,通过长度来相加。
执行用时:224ms;内存消耗:40.6MB;
var romanToInt = function(s) {
const mainMap={I:1,V:5,X:10, L:50,C:100,D:500,M:1000};
const subMap={IV:4,IX:9,XL:40,XC:90,CD:400,CM:900};
let res=0;
const splitArr=s.match(/(CM)|(CD)|(XC)|(XL)|(IX)|(IV)|(\w)/g);
for(let v of splitArr) {
if(v.length===1){
res+=mainMap[v]
}else if (v.length===2){
res+=subMap[v]
}
}
return res;
}最长公共前缀
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 ""。
示例 1:
输入: ["flower","flow","flight"]
输出: "fl"示例 2:
输入: ["dog","racecar","car"]
输出: ""
解释: 输入不存在公共前缀。说明:
所有输入只包含小写字母 a-z 。
题解:
思路1:逐位法
逐位比较,取第一个字符串的来与余下字符串比较,相同继续,不同则返回。
执行用时:76ms;内存消耗:35.1MB;
var longestCommonPrefix = function(strs) {
var re = '';
if (!strs.length) return re;
for (var j=0;j<strs[0].length;j++){
for (var i=1;i<strs.length;i++){
if (strs[i][j]!=strs[0][j]) return re
}
re += strs[0][j];
}
return re;
}
思路2:正则法
思路和上面的一样,换成了正则来匹配。
执行用时:76ms;内存消耗:34.1MB;
var longestCommonPrefix = function(strs) {
var re = strs[0] ? strs[0]:'';
for (var i=1;i<strs.length;i++){
var regex = new RegExp('^'+re);
while (!regex.test(strs[i])&&re.length){
re = re.slice(0,re.length-1);
regex = new RegExp('^'+re);
}
}
return re;
}
有效的括号
给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。
有效字符串需满足:
1、左括号必须用相同类型的右括号闭合。
2、左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。
示例 1:
输入: "()"
输出: true示例 2:
输入: "()[]{}"
输出: true示例 3:
输入: "(]"
输出: false示例 4:
输入: "([)]"
输出: false示例 5:
输入: "{[]}"
输出: true题解:
思路1:消除法
通过replace来消去可以闭合的括号,消不掉就返回false。
执行用时:104ms;内存消耗:36MB;
var isValid = function(s) {
while (s.length) {
var temp = s;
s = s.replace('()', '');
s = s.replace('[]', '');
s = s.replace('{}', '');
if (s == temp) return false
}
return true;
}
思路2:立即匹配法
建立一个键值对结构,左括号为key,右括号为value,循环字符串,当遇到左括号时放入栈中,当遇到右括号的时候与上一次存储的左括号匹配,如果相同,删除栈中最后一位,如果不同,返回false。特殊情况全是左括号,则返回false。
执行用时:104ms;内存消耗:36MB;
var isValid = function(s) {
var map = {
"(": ")",
"[": "]",
"{": "}"
}
var leftArr = []
for (var ch of s){
if (ch in map) leftArr.push(ch); //为左括号时,顺序保存
else { //为右括号时,与数组末位匹配
if(ch != map[leftArr.pop()]) return false;
}
}
return !leftArr.length //防止全部为左括号
}
合并两个有序链表
将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4题解:
思路1:数组转换法
把原对象转化为数组,在合并数组转化为递归对象。
执行用时:96ms;内存消耗:36.4MB;
var isValid = function(s) {
var mergeTwoLists = function(l1, l2) {
let node1=l1||{};
let node2=l2||{};
let res=null;
let list=[];
while(node1.val||node2.val||node1.val==0||node2.val==0){
if(node1.val||node1.val==0){
list.push(node1.val)
}
if(node2.val||node2.val==0){
list.push(node2.val)
}
if(node1.next){
node1=node1.next
}else{
node1={}
}
if(node2.next){
node2=node2.next
}else{
node2={}
}
}
list=list.sort((a,b)=>a-b);
list.reduce((pre,cur)=>{
if(!pre){
res={
val:cur,
next:null
}
return res
}
else{
pre.next={
val:cur,
next:null
}
return pre.next
}
return pre
},null)
return res;
}
思路2:递归法
把原对象转化为数组,在合并数组转化为递归对象。
执行用时:84ms;内存消耗:35.5MB;
var mergeTwoLists = function(l1, l2) {
if(l1==null){
return l2
}
if(l2==null){
return l1
}
if(l1.val<l2.val){
l1.next=mergeTwoLists(l1.next,l2)
return l1
}else{
l2.next=mergeTwoLists(l1,l2.next)
return l2
}
}
删除排序数组中的重复项
给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 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。
你不需要考虑数组中超出新长度后面的元素。说明:
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以“引用”方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝
int len = removeDuplicates(nums);
// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。
for (int i = 0; i < len; i++) {
print(nums[i]);
}题解:
思路1:指针删除法
原题意是不占用额外空间的情况下,修改原数组,判断当前元素和下一个元素是否相等,如果相同删掉,让指针--,不漏掉其他值。
执行用时:148ms;内存消耗:37.1MB;
var removeDuplicates = function(nums) {
for(let i=0;i<nums.length;i++){
if(nums[i]==nums[i+1]){
nums.splice(i,1);
i--
}
}
return nums.length
}
思路2:占位法
思路一的方法耗时较长,换个角度考虑,动态改变数组前几位就可以了。
执行用时:96ms;内存消耗:36.5MB;
var removeDuplicates = function(nums) {
var len = 1;
for (var i = 1; i < nums.length; i++)
if (nums[i] != nums[i-1]) nums[len++] = nums[i];
return len
}
五期结束,希望有更多的小伙伴加入。

关注公众号回复「算法」。拉你进群。