文章目录
面试经典150题0425
Leetcode042 接雨水
双指针
设置左右指针,分别指向左边界和右边界操作的列。
设置变量leftMax
和rightMax
代表左边遍历过的最大高度和右边遍历过的最大高度。
另外,最左边和最右边的列不能存水。
如果leftMax < rightMax
,说明当前左指针的右边至少有一个板子,其高度大于左指针及遍历过的左边所有位置;根据木桶效应,左指针当前列的储水量的决定权在左边,即leftMax - height[left]
;
如果leftMax >= rightMax
,那么右指针当前列的储水量的决定权在右边,即rightMax - height[right]
;
public static int trap(int[] height){
int n = height.length;
int res = 0;
// 定义左右指针
int left = 0, right = n - 1;
int leftMax = height[left];
int rightMax = height[right];
// 最左边和最右边的列不能储水
left++;
right--;
while (left <= right){
leftMax = Math.max(leftMax, height[left]);
rightMax = Math.max(rightMax, height[right]);
if(leftMax < rightMax){
res += leftMax - height[left];
left++;
}
else {
res += rightMax - height[right];
right--;
}
}
return res;
}
Leetcode013 罗马数字转整数
public static int romanToInt(String s){
Map<Character, Integer> map = new HashMap<>();
map.put('I', 1);
map.put('V', 5);
map.put('X', 10);
map.put('L', 50);
map.put('C', 100);
map.put('D', 500);
map.put('M', 1000);
int ptr = 0;
int res = 0;
while (ptr < s.length()){
char tmp = s.charAt(ptr);
// V L D M 放在前面不涉及特例
if(tmp == 'V' || tmp == 'L' || tmp == 'D' || tmp == 'M'){
res += map.get(tmp);
ptr++;
}
else {
// 尝试读取下一个字符
ptr++;
int a = map.get(tmp);
if(ptr >= s.length()){
// 不存在下一个字符,直接将当前字符数值添加并结束循环
res += map.get(tmp);
break;
} else if (map.get(s.charAt(ptr)) / a != 5 && map.get(s.charAt(ptr)) / a != 10) {
// 根据题目要求,当后一个字符表示数值/当前字符表示数值 != 5 && != 10,不属于特殊情况
res += a;
}
else {
int b = map.get(s.charAt(ptr));
// 特殊情况,后面字符数值 - 当前字符数值
// 另外,指针需要前进两步
res += b - a;
ptr++;
}
}
}
return res;
}
Leetcode012 整数转罗马数字
使用贪心算法,从大到小处理。(类似找零钱)
public String intToRoman(int num) {
// 把阿拉伯数字与罗马数字可能出现的所有情况和对应关系,放在两个数组中
// 并且按照阿拉伯数字的大小降序排列
int[] nums = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};
String[] romans = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"};
int index = 0;
StringBuilder result = new StringBuilder();
while (index < 13) {
if (num >= nums[index]) {
result.append(romans[index]);
num -= nums[index];
} else {
index ++;
}
}
return result.toString();
}
Leetcode058 最后一个单词的长度
从后面遍历,找到第一个非空格元素,记录该位置;然后再继续向左遍历,找到第一个空格元素或者首位置为止。
public static int lengthOfLastWord(String s){
int right = s.length() - 1;
while (right >= 0 && s.charAt(right) == ' '){
right--;
}
if(right < 0){
return 0;
}
int left = right - 1;
while (left >= 0 && s.charAt(left) != ' '){
left--;
}
return right - left;
}
Leetcode014 最长公共前缀
先求出所有所有字符串的最小长度minLen
,最长公共前缀长度一定小于等于minLen
纵向比较每个字符串,比较第j
个字符串和j-1
个字符串在第i
个位置的字符是否相同。相同则继续,否则跳出循环,返回结果。
public static String longestCommonPrefix(String[] strs){
int len = strs.length;
if(len <= 1)
return strs[0];
int minLen = Integer.MAX_VALUE;
for(String s : strs){
minLen = Math.min(minLen, s.length());
}
StringBuilder sb = new StringBuilder();
for(int i = 0; i < minLen; i++){
boolean flag = true;
for(int j = 1; j < len; j++){
flag = strs[j-1].charAt(i) == strs[j].charAt(i);
if(!flag){
break;
}
}
if(!flag){
break;
}
sb.append(strs[0].charAt(i));
}
return sb.toString();
}
Leetcode151 反转字符串中的单词
整体反转+单词反转
注意空格处理
public static String reverseWords(String s) {
// 整体反转,去掉首尾空格,按照空格进行分割
String[] strs = new StringBuilder(s).reverse().toString().trim().split(" ");
StringBuilder res = new StringBuilder();
for(String str : strs){
// 中间多个连续空格会造成出现空字符串
if(str.isEmpty()) {
continue;
}
res.append(new StringBuilder(str).reverse()).append(" ");
}
// 返回字符串去除首尾空格
return res.toString().trim();
}
Leetcode006 Z字形变换
- 当
numRows==0
时,在第一行上每一步需要加1; - 当
numRows!=0
时,第一行和最后一行每一步需要跳sumJump = 2 * (numRows - 1)
,对于中间的行,偶数次跳为even = 2 * i
,奇数次跳为odd = sumJump - even
- 然后依次遍历
[0, numRows - 1]
,对获得的字符进行拼接。
public static String convert(String s, int numRows){
StringBuilder sb = new StringBuilder();
for(int i = 0; i < numRows; i++){
// 总跳数
int sumJump = numRows == 1 ? 1 : 2 * (numRows - 1);
// 偶次跳数
int even = 2 * i;
// 奇次跳数
int odd = sumJump - even;
// 以此拼接第i行
int start = i;
if(i == 0 || i == numRows - 1){
while (start < s.length()){
sb.append(s.charAt(start));
start += sumJump;
}
}
int cnt = 1;
while (start < s.length()){
sb.append(s.charAt(start));
if(cnt % 2 == 1){
start += odd;
}
else {
start += even;
}
cnt++;
}
}
return sb.toString();
}
Leetcode028 找出字符串中第一个匹配项的下标
KMP算法:能够快速在原字符串中找到匹配字符串
**KMP能够在O(m+n)
**复杂度内完成查找,是因为其能在非完全匹配的过程中提取到有效信息进行复用,以减少重复匹配的时间消耗。
两个概念:
- 前缀:对于字符串
abcxxxefg
,其中abc
为abcxxxefg
的某个前缀 - 后缀:对于字符串
abcxxxefg
,其中efg
为abcxxxefg
的某个后缀
设原串为abeababeabf
,匹配串为abeabf
KMP匹配过程:
首先匹配串会检查之前已经匹配成功的部分中是否存在相同的前缀和后缀,如果存在,则跳转到前缀的下一位置继续往下匹配。
跳转到下一匹配位置后,尝试匹配a
和e
,发现匹配不上,并且已经匹配上的字符串ab
不存在相同的前缀和后缀,此时只能回到匹配串的起始位置重新开始匹配。
- KMP利用已经匹配部分中相同的前缀和后缀来加速下一次的匹配
- KMP的原串指针不会进行回溯。
分析实现:
扫描完整原串操作是不可避免的,只能去优化**[检查已经匹配部分的相同前缀和后缀]**这一过程。
检查前缀和后缀的目的是为了确定匹配串中的下一段开始匹配的位置
另外,对于匹配串的任意一个位置而言,由该位置发起的下一个匹配点位置其实与原串无关。
例如,对于匹配串abcabd
的字符d
来说,由它发起的下一个匹配点跳转必然是字符c
的位置。因为字符d
位置的相同前缀和后缀字符ab
的下一位置字符为c
可以看出匹配串某个位置跳转到下一个匹配位置这一过程是与原串无关的,将这一过程称为寻找next
点。
显然我们可以预处理next
数组,数组中的每个位置的值是该下标应该跳转的目标位置(next
点)
next
数组的构建
如下是一个字串的next
数组的值,可以看到这个字串的对称度很高,所以next
的值都很大。
位置i
位置i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
前缀next[i] | 0 | 0 | 0 | 0 | 1 | 2 | 3 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 4 | 0 |
字串 | a | g | c | t | a | g | c | a | g | c | t | a | g | c | t | g |
**对称度:**这里的对称度指的是abcabc
**前缀:**指出了最后一个字符以外,一个字符串的全部头部组合;
**后缀:**指除了第一个字符以外,一个字符串的全部尾部组合;
next
的值就是前缀和后缀的最长的共有元素的长度。
以ABCDABD
为例:
- "A"的前缀和后缀都为空集,共有元素的长度为0;
- "AB"的前缀为
A
,后缀为B
,共有元素的长度为0; - "ABC"的前缀为
A,AB
,后缀为BC,C
,共有元素的长度为0; - "ABCD"的前缀为
A, AB, ABC
,后缀为BCD, CD, D
,共有元素长度为0; - "ABCDA"的前缀为
A, AB, ABC, ABCD
,后缀为BCDA, CDA, DA, A
,共有元素长度为1; - …………
编程推理过程:
-
当前字符的前一个字符的对称度为0的时候,只要将当前字符与字串第一个字符进行比较。因为前面都是0,说明都不对称,如果多加一个字符,只能和第一个字符形成对称。例如
agcta
,其中t
的next
为0,后面字符a
只能和第一个字符对称。 -
如果前面一个字符的
next
为1,那么就可以把当前字符与子串第二个字符进行比较,因为前面字符的next
为1,说明已经和第一个字符相等了。例如agctag
。 -
按照上面推理,如果一直相等,就可以一直累加。
-
如果遇到不相等的,那么说明不能继承前面的对称性了。需要回头重新找对称性:例如
[agctagc][agctagc]t
,其中t
之前的对称度分别为1,2,3,4,5,6,7;到最后一个字符t
没有继承前一个字符的next
值,所以这个t
的对称性必须重新求。- 要找更小的对称,必然在对称内部还存在子对称,而且这个t必须紧接着在子对称之后。
参考链接:https://cloud.tencent.com/developer/article/1706743
public static int strStr(String haystack, String needle){
int[] next = new int[needle.length()];
nextArray(needle, next);
for(int i = 0, j = 0; i < haystack.length() && j < needle.length(); i++){
while (j > 0 && haystack.charAt(i) != needle.charAt(j)){
// pattern回溯
// 下一轮使用原串的下一个字符和模式串的next[j-1]位置的字符比较
j = next[j-1];
}
if(haystack.charAt(i) == needle.charAt(j)){
j++;
}
if(j == needle.length()){
// 找到返回匹配位置
return i - needle.length() + 1;
}
}
return -1;
}
public static void nextArray(String pattern, int[] next){
for(int i = 1, j = 0; i < pattern.length(); i++){
while (j > 0 && pattern.charAt(i) != pattern.charAt(j)){
// 不相等时,寻找子对称
// 参考最后一张图
j = next[j - 1];
}
if(pattern.charAt(i) == pattern.charAt(j)){
j++;
}
next[i] = j;
}
}