前言
今天刷的是13.罗马数字转整数、6.Z 字形变换、76.最小覆盖子串。
13.罗马数字转整数(容易):
题目描述:
罗马数字包含以下七种字符: 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 的范围内。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/roman-to-integer
题解:
这道题虽然归在简单里面,不过我觉得也是比较难的。不过发现规律之后就很好做了,里面比较特殊的情况是IV、IX、XL、XC、CD、CM这六个,其实就是把一个较小的罗马数字放在了较大的罗马数字之前。我们可以在遍历时判断相邻字符,在遇到这种情况时减去上一个字符代表的数字,其余时候只要累加。
Code
// 第一次AC
var romanToInt = function(s) {
let sum=0;
let preNum=getValue(s[0]);
for(let i=1;i<s.length;i++){
let num=getValue(s[i]);
if(preNum<num){
sum-=preNum;
}else{
sum+=preNum;
}
preNum=num;
}
sum+=preNum;
return sum;
};
function getValue(char){
switch(char){
case 'I': return 1;
case 'V': return 5;
case 'X': return 10;
case 'L': return 50;
case 'C': return 100;
case 'D': return 500;
case 'M': return 1000;
default: return 0;
}
}
// console.log(romanToInt("MCMXCIV"))
6.Z 字形变换(中等):
题目描述:
将一个给定字符串根据给定的行数,以从上往下、从左到右进行 Z 字形排列。
详细题意见leetcode链接。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/zigzag-conversion
题解:
这道题是一道思维题,我的做法是用字符串数组存下每一行从左到右的读取结果,最后进行拼接。而遍历字符串时我们也只需要按顺序,然后判断当前字符位于哪一行,塞到当前行的字符串的末尾即可。因此时间复杂度为O(n),n是输入字符串长度。
Code
// 第一次AC
var convert = function(s, numRows) {
if(numRows==1)
return s;
const len=Math.min(s.length,numRows);
const rows=[];
for(let i=0;i<len;i++)
rows[i]="";
let loc=0;
direction=false;
for(const c of s){
rows[loc]+=c;
if(loc==0||loc==numRows-1)
direction=!direction;
loc+=direction?1:-1;
}
let res="";
rows.forEach(item=>{
res+=item;
})
return res;
};
76.最小覆盖子串(困难):
题目描述:
给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字符的最小子串。
示例:
输入: S = “ADOBECODEBANC”, T = “ABC”
输出: “BANC”
说明:
如果 S 中不存这样的子串,则返回空字符串 “”。
如果 S 中存在这样的子串,我们保证它是唯一的答案。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/minimum-window-substring
题解:
这道题试过穷举(超时)之后,观摩了讨论区大佬的滑动窗口解法,豁然开朗。
滑动窗口解法使用左右两个指针来控制子串的起点和终点,在搜索的过程中我们可以通过哈希表存储缺失的字符,在匹配的情况下减去对应的哈希表项。
同时,通过缺失的种类数来控制左指针的移动,当缺失种类数为0,即不缺失要求的字符时,我们认为当前的子串已经达到了一个局部的优解,为了验证它是否是最优的,我们需要缩小窗口,即移动左指针,寻找有没有更短的子串。在移动左指针的过程中,假如失去了已经匹配好的字符,就令缺失字符的种类数+1,使得程序停止移动左指针,开始移动右指针达到符合条件的状态。循环反复,直至右指针最终到达字符串的末端。
在这一过程中,我们可以发现,字符串中的每个字符至多会被指针指向并判断两次,时间复杂度远比罗列所有的子串的时间复杂度要低。
Code
// 滑动窗口
var minWindow = function(s, t) {
// if(s.length==0||t.length==0||t.length>s.length) return "";
let minLen=Infinity,resL;
let map={};
let missingType=0;//当前缺失的字符种类
for(const char of t){
if(!map[char]){
missingType++;
map[char]=1;
}else{
map[char]++;
}
}
let left=0,right=0;
for(;right<s.length;right++){
let rightChar=s[right];
if(map[rightChar]!==undefined)//当前字符是T包含的字符
map[rightChar]--;
if(map[rightChar]===0)
missingType--;
while(missingType===0){
if(right-left+1<minLen){
minLen=right-left+1;
resL=left;
}
let leftChar=s[left]
if(map[leftChar]!==undefined)
map[leftChar]++;
if(map[leftChar]>0)
missingType++;
left++;
}
}
return s.substring(resL,resL+minLen);
};