每天一道算法题(第五期)

288 阅读7分钟
你需要掌握三种核心的能力:编程、写作、设计!

前言

算法这个活动很严,每天必须打卡,而且不限制语言,群内已有PHP、Python、Java、Javascript等语言,欢迎大家参加,并坚持。能坚持的公众号回复算法。本公众号以JS为主,解题思路均以js来举例。


罗马数字转整数

罗马数字包含以下七种字符: IVXLCDM

字符          数值
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
}

五期结束,希望有更多的小伙伴加入。


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