引言
对于程序员来说, 算法二字应该都不陌生!!
首先什么是 算法
? 它是解决问题的一个思路, 至于使用什么语言去完成不是它的重点, 你可以使用 Java
也可以使用 JS
甚至可以是任何你所熟悉的语言!!
那么我们为什么要学习 算法
呢? 即便我们没研究过它, 我们不是照样一样能完成手头的工作嘛? 其实在工作中, 业务开发过程中, 我们肯定会遇到一个个问题, 并通过编码解决它, 这中间其实或多或少都会用到 算法
只是我们可能对 算法
没怎么研究, 所以并没有感知到!!!
那么学习 算法
到底能给我带来什么呢?
- 对于求职者而,
算法
能力能成为你拿到优质offer
的敲门砖, 不管是大厂还是国外面试都要求有一定算法
能力 算法
是解决某类问题的的思路, 通过它可以很好培养我们的逻辑思维能力- 也许在平常工作中你用不到你所学的
算法
, 但是当遇到一些复杂问题, 它可以帮助你在复杂的情况中更好地分析问题、解决问题、从而寻找出最优的解决问题的思路 - 让我们自己研究
算法
很难, 但是目前很多算法
都是几代人努力, 研究出来的, 我们可以直接研究学习, 站在巨人的肩膀上成就自我, 何乐而不为呢 算法
是一种解决问题的思路和方法, 也许有机会应用到生活和事业的其他方面呢- 长期来看, 大脑思考能力是个人最重要的核心竞争力, 而算法是为数不多的能够有效训练大脑思考能力的途径之一
所以相信我, 学习
算法
应该是一件很酷的事情, 所以这里我专门整了个专栏, 希望我能坚持下去吧....
一、题目介绍
1.1 描述
- 罗马数字包含七种字符:
I
、V
、X
、L
、C
、D
和M
- 罗马字符和数字之间的映射关系如下, 这里映射关系有两种情况: 单字符、双字符串
单字符 | 数值 |
---|---|
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 |
- 举个例子: 罗马数字
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 提示
- 输入字符串
s
长度限制1 ~ 15
位, 即:1 <= s.length <= 15
- 输入字符串
s
仅含字符:I
、V
、X
、L
、C
、D
、M
- 题目数据保证输入字符串
s
是一个有效的罗马数字, 且表示整数在范围[1, 3999]
内 - 题目所给测试用例皆符合罗马数字书写规则, 不会出现跨位等情况。
IL
和IM
这样的例子并不符合题目要求,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')
执行结果如下:
三、常规做法
上文做法可能并不算常规的做法, 毕竟是需要依赖 JS
中 s.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')
执行结果如下:
四、另类解法(相减)
上面我们分了两种情况, 即针对字符串分为 双字符
和 单字符
两种情况来进行处理, 循环匹配到相应的映射值后都是通过累加来计算数值!!
下面我们来看一种另类的解题思路, 按照题目的描述, 我们可以总结如下规则:
- 只考虑罗马数字由
I
、V
、X
、L
、C
、D
、M
构成情况, 即不考虑双字节情况 - 从左往右, 当
下一个
字符对应的数字比当前的大
, 则当前值需要采用减法, 即减去当前字符对应的值, 如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....