[LeetCode] 13. 罗马数字转整数

引言

对于程序员来说, 算法二字应该都不陌生!!

首先什么是 算法? 它是解决问题的一个思路, 至于使用什么语言去完成不是它的重点, 你可以使用 Java 也可以使用 JS 甚至可以是任何你所熟悉的语言!!

那么我们为什么要学习 算法 呢? 即便我们没研究过它, 我们不是照样一样能完成手头的工作嘛? 其实在工作中, 业务开发过程中, 我们肯定会遇到一个个问题, 并通过编码解决它, 这中间其实或多或少都会用到 算法 只是我们可能对 算法 没怎么研究, 所以并没有感知到!!!

那么学习 算法 到底能给我带来什么呢?

  1. 对于求职者而, 算法 能力能成为你拿到优质 offer 的敲门砖, 不管是大厂还是国外面试都要求有一定 算法 能力
  2. 算法 是解决某类问题的的思路, 通过它可以很好培养我们的逻辑思维能力
  3. 也许在平常工作中你用不到你所学的 算法, 但是当遇到一些复杂问题, 它可以帮助你在复杂的情况中更好地分析问题、解决问题、从而寻找出最优的解决问题的思路
  4. 让我们自己研究 算法 很难, 但是目前很多 算法 都是几代人努力, 研究出来的, 我们可以直接研究学习, 站在巨人的肩膀上成就自我, 何乐而不为呢
  5. 算法 是一种解决问题的思路和方法, 也许有机会应用到生活和事业的其他方面呢
  6. 长期来看, 大脑思考能力是个人最重要的核心竞争力, 而算法是为数不多的能够有效训练大脑思考能力的途径之一

所以相信我, 学习 算法 应该是一件很酷的事情, 所以这里我专门整了个专栏, 希望我能坚持下去吧....

一、题目介绍

地址: leetcode.cn/problems/ro...

1.1 描述

  1. 罗马数字包含七种字符: IVXLCDM
  2. 罗马字符和数字之间的映射关系如下, 这里映射关系有两种情况: 单字符、双字符串
单字符 数值
I 1
V 5
X 10
L 50
C 100
D 500
M 1000
双字符 数值
IV 4
IX 9
XL 40
XC 90
CD 400
CM 900
  1. 举个例子: 罗马数字 2 写做 II, 即为两个并列的 1; 12 写做 XII 即为 X + II; 27 写做 XXVII, 即为 XX + V + II; 1994 写做 MCMXCIV 即为 M + CM + XC + IV

1.2 更多示例

js 复制代码
示例 1:
输入: s = "III"
输出: 3

示例 2:
输入: s = "IV"
输出: 4

示例 3:
输入: s = "IX"
输出: 9

示例 4:
输入: s = "LVIII"
输出: 58
解释: L = 50, V = 5, III = 3

示例 5:
输入: s = "MCMXCIV"
输出: 1994
解释: M = 1000, CM = 900, XC = 90, IV = 4

1.3 提示

  1. 输入字符串 s 长度限制 1 ~ 15 位, 即: 1 <= s.length <= 15
  2. 输入字符串 s 仅含字符: IVXLCDM
  3. 题目数据保证输入字符串 s 是一个有效的罗马数字, 且表示整数在范围 [1, 3999]
  4. 题目所给测试用例皆符合罗马数字书写规则, 不会出现跨位等情况。
  5. ILIM 这样的例子并不符合题目要求, 49 应该写作 XLIX, 999 应该写作 CMXCIX

二、暴力破解(使用正则进行匹配)

这是我一开始拿到题目的一个思路:

  • 创建两个 罗马字符数值 的映射表, 一个是 单个字符罗马字符 映射表, 一个是 双字符
  • 这里我们需要优先处理, 输入字符串 s 中符合要求的 双字符罗马字符, 然后再处理单字节的
  • 这里我的做法就是直接循环两个映射表, 使用 正则 以及 s.replace 依次将字符串中符合要求的罗马字符去除, 同时累加结果值
js 复制代码
const MAP_SINGLE = {
  'I': 1,
  'V': 5,
  'X': 10,
  'L': 50,
  'C': 100,
  'D': 500,
  'M': 1000,
}

const MAP_GROUP = {
  'IV': 4,
  'IX': 9,
  'XL': 40,
  'XC': 90,
  'CD': 400,
  'CM': 900,
}

/**
 * @param {string} s
 * @return {number}
 */
var romanToInt = function(s) {
  let res = 0

  Object.entries(MAP_GROUP).forEach(([key, value]) => {
    s = s.replace(new RegExp(`${key}`, 'g'), () => {
      res += value
      return ''
    })
  })

  Object.entries(MAP_SINGLE).forEach(([key, value]) => {
    s = s.replace(new RegExp(`${key}`, 'g'), () => {
      res += value
      return ''
    })
  })

  return res
};

romanToInt('MCMXCIV')

执行结果如下:

三、常规做法

上文做法可能并不算常规的做法, 毕竟是需要依赖 JSs.replace 特性来实现的, 下面介绍一个比较常规的做法:

  • 大体思路其实和上面的差不多, 主要策略就是优先处理双字节的情况
  • 这里使用 while 对输入字符串 s 进行一个循环, 在最外层通过 index 来记录当前循环的位置(指针), 当 index 小于字符串的长度则继续循环, 否则结束循环
  • 循环内部, 优先获取后两个字符, 判断这两个字符是否是符合条件的罗马字符, 如果是就按双字节进行处理, 否则按单字节进行处理
js 复制代码
const map = new Map([
  // 单字符
  ['I', 1],
  ['V', 5],
  ['X', 10],
  ['L', 50],
  ['C', 100],
  ['D', 500],
  ['M', 1000],
  // 双字符
  ['IV', 4],
  ['IX', 9],
  ['XL', 40],
  ['XC', 90],
  ['CD', 400],
  ['CM', 900],
])

/**
 * @param {string} s
 * @return {number}
 */
var romanToInt = function(s) {
  let res = 0
  let index = 0

  while (index < s.length) {
    // 从当前索引开始取两个字符
    const doubleChar = s.slice(index, index + 2)

    if (map.has(doubleChar)) {  
      // 优先按双字节进行处理
      res += map.get(doubleChar) // 累加值
      index += 2  // 索引 + 2
    } else {  
      // 按单字节进行处理: 从当前索引开始取一个字符
      const singleChar = s.slice(index, index + 1)
      res += map.get(singleChar) // 累加值
      index += 1 // 索引 + 1
    }
  }

  return res
};

romanToInt('MCMXCIV')

执行结果如下:

四、另类解法(相减)

上面我们分了两种情况, 即针对字符串分为 双字符单字符 两种情况来进行处理, 循环匹配到相应的映射值后都是通过累加来计算数值!!

下面我们来看一种另类的解题思路, 按照题目的描述, 我们可以总结如下规则:

  • 只考虑罗马数字由 IVXLCDM 构成情况, 即不考虑双字节情况
  • 从左往右, 当 下一个 字符对应的数字 比当前的大, 则当前值需要采用减法, 即减去当前字符对应的值, 如 IV = - 1 + 5 = 4,
  • 从左往右, 当 下一个 字符对应的数字 比当前的小, 则当前值需要采用加法, 即累加当前字符对应的值, 如 VI = 5 + 1 = 6
  • 最后一位默认采用加法

看些示例:

js 复制代码
示例 1:
输入: s = "III"
输出: 1 + 1 + 1 = 3

示例 2:
输入: s = "IV"
输出: - 1 + 5 = 0

示例 3:
输入: s = "IX"
输出: -1 + 10 = 9

示例 4:
输入: s = "LVIII"
输出: 50 + 5 + 1 + 1 + 1 = 58

示例 5:
输入: s = "MCMXCIV"
输出: 1000 - 100 + 1000 - 10 + 100 - 1 + 5 = 1994

代码实现:

js 复制代码
const map = new Map([
  // 单字符
  ['I', 1],
  ['V', 5],
  ['X', 10],
  ['L', 50],
  ['C', 100],
  ['D', 500],
  ['M', 1000],
])

/**
 * @param {string} s
 * @return {number}
 */
var romanToInt = function(s) {
  let res = 0
  // 从后往前正常正常应该是越来越大, 如果后面一位比当前的数字小, 则当前数字则是减法
  for (let i = 0; i < s.length; i ++) {
    const currentChar = map.get(s.charAt(i)) // 获取当前索引罗马字符对应的数字
    const nextChar = map.get(s.charAt(i + 1)) // 获取下一个索引罗马字符对应的数字

    // 下一位存在, 并且下一位对应数值比当前的还要大, 在说明需要采用减法进行计算
    if (nextChar && nextChar > currentChar) {
      res -= currentChar
    } else {
      // 否则采用加法
      res += currentChar
    }
  }

  return res
};

执行结果如下:

end....

相关推荐
前端郭德纲12 分钟前
深入浅出ES6 Promise
前端·javascript·es6
就爱敲代码17 分钟前
ES6 运算符的扩展
前端·ecmascript·es6
天天进步201532 分钟前
Lodash:现代 JavaScript 开发的瑞士军刀
开发语言·javascript·ecmascript
王哲晓38 分钟前
第六章 Vue计算属性之computed
前端·javascript·vue.js
假装我不帅41 分钟前
js实现类似与jquery的find方法
开发语言·javascript·jquery
究极无敌暴龙战神X44 分钟前
CSS复习2
前端·javascript·css
西几1 小时前
代码训练营 day48|LeetCode 300,LeetCode 674,LeetCode 718
c++·算法·leetcode
风清扬_jd1 小时前
Chromium HTML5 新的 Input 类型week对应c++
前端·c++·html5
liuyang-neu1 小时前
力扣第420周赛 中等 3324. 出现在屏幕上的字符串序列
java·算法·leetcode
Ellie陈1 小时前
Java已死,大模型才是未来?
java·开发语言·前端·后端·python