字符串
无重复字符的最长子串
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
分析
首先我们看看这个题的示例3,该示例中提示我们这个题需要求的字符串的子串而不是子序列,我们具体来看看什么是子串,什么是子序列。
子串:字符串中任意个连续的字符组成的子序列称为该串的子串。注意子串强调字符的连续性。
子串.png
子序列:字符串中去掉任意个元素后得到的结果即为该字符串的子序列。注意子序列中字符出现的次序与原字符串中字符出现的次序要保持一致。
子序列.png
区分子串和子序列后,我们再回过头来看看这个题。我们先动手画一画示例1的解题过程,如下图所示:
示例1分析过程.png
从上图我们可以观察出,可以使用双指针(left
指针和right
指针)来维护一个滑动窗口,这个窗口内的字符都是没有重复的,遍历一趟字符串后就可以得到最大的子串,因此时间复杂度为O(n)
。现在想一个问题:**right
指针指向的字符怎么确定它在前面是否出现过,若出现过,它出现的位置在哪儿?**我们可以使用HashMap
记录一个字符是否出现以及出现后的位置。对于重复多次出现的字符,我们是保留所有出现的位置还是只记录一个位置?观察示例1分析过程可以知道,我们只需要保存一个最大位置即可。还有一个关键点,我们如何确定left
指针的位置?这一点非常重要,需要分情况讨论。
- 当目前
right
指针指向的字符未出现过,left
指针不需要移动; - 当目前
right
指针指向的字符出现过,如果该字符在窗口中,即该字符出现在当前left
指针的右边,则通过HashMap
获取字符的位置并向右移动一位即为更新后left
的位置;如果该字符在窗口外面,即在当前left
指针的左边,则不需要移动left
的位置。
具体实现
class Solution {
public int lengthOfLongestSubstring(String s) {
int res = 0;
if(s.length() == 0)
return res;
// 创建HashMap,用来保存字符与位置之间的对应关系
HashMap<Character, Integer> hashMap = new HashMap<>();
// 初始化左指针和右指针,并遍历字符串
for(int left = 0, right = 0; right < s.length(); right++){
// 判断右指针指向的字符是否出现过
if(hashMap.containsKey(s.charAt(right))){
// 确定左指针的位置
left = Math.max(left, hashMap.get(s.charAt(right))+1);
}
// 对于第一次出现的字符,保存该字符的位置;对于多次出现的字符,更新该字符出现的位置
hashMap.put(s.charAt(right), right);
// 更新窗口的大小,保存最大的窗口大小
res = Math.max(res, right-left+1);
}
return res;
}
}
简化路径
以 Unix 风格给出一个文件的绝对路径,你需要简化它。或者换句话说,将其转换为规范路径。
在 Unix 风格的文件系统中,一个点(.
)表示当前目录本身;此外,两个点 (..
) 表示将目录切换到上一级(指向父目录);两者都可以是复杂相对路径的组成部分。更多信息请参阅:Linux / Unix中的绝对路径 vs 相对路径
请注意,返回的规范路径必须始终以斜杠 /
开头,并且两个目录名之间必须只有一个斜杠 /
。最后一个目录名(如果存在)不能以 /
结尾。此外,规范路径必须是表示绝对路径的最短字符串。
示例 1:
输入:"/home/"
输出:"/home"
解释:注意,最后一个目录名后面没有斜杠。
示例 2:
输入:"/../"
输出:"/"
解释:从根目录向上一级是不可行的,因为根是你可以到达的最高级。
示例 3:
输入:"/home//foo/"
输出:"/home/foo"
解释:在规范路径中,多个连续斜杠需要用一个斜杠替换。
示例 4:
输入:"/a/./b/../../c/"
输出:"/c"
示例 5:
输入:"/a/../../b/../c//.//"
输出:"/c"
示例 6:
输入:"/a//bc/d//././/.."
输出:"/a/b/c"
思路
1.此题主要考察的是栈,所以定义一个辅助栈;
2.先把字符串以"/“为分隔符分割成数组,此时数组有"路径”、“”、“.”、“…“这四种情况;
3.遍历数组,当s[i].equals(”…”)并且栈不空时pop,当!s[i].equals(“”) && !s[i].equals(“.”) && !s[i].equals(“…”),即s[i]是路径入栈;
4.栈空,返回"/",栈非空,用StringBuffer做一个连接返回即可;
5完结。
实现
class Solution {
public String simplifyPath(String path) {
String[] s = path.split("/");
Stack<String> stack = new Stack<>();
for (int i = 0; i < s.length; i++) {
if (!stack.isEmpty() && s[i].equals(".."))
stack.pop();
else if (!s[i].equals("") && !s[i].equals(".") && !s[i].equals(".."))
stack.push(s[i]);
}
if (stack.isEmpty())
return "/";
StringBuffer res = new StringBuffer();
for (int i = 0; i < stack.size(); i++) {
res.append("/" + stack.get(i));
}
return res.toString();
}
}
复原IP地址
给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。
有效的 IP 地址正好由四个整数(每个整数位于 0 到 255 之间组成),整数之间用 '.'
分隔。
示例:
输入: "25525511135"
输出: ["255.255.11.135", "255.255.111.35"]
思路
利用回溯法穷举所有可能的ip地址
代码实现
class Solution {
public List<String> restoreIpAddresses(String s) {
List<String> res = new ArrayList<>();
dfsRestoreIpAddresses(s, res, 0, 1, new ArrayList<>());
return res;
}
private void dfsRestoreIpAddresses(String s, List<String> res, int start, int end, ArrayList<String> r) {
if (start < 0 || start > s.length() || end < 0 || end > s.length() + 1 || r.size() > 4) {
return;
}
if (r.size() == 4 && start == s.length()) {
String rs = "";
for (int i = 0; i < r.size(); i++) {
if (i < r.size() - 1) {
rs += r.get(i) + ".";
} else {
rs += r.get(i);
}
}
res.add(rs);
return;
}
for (int i = 0; i < 3; i++) {
int ends = end + i;
if (start < 0 || start > s.length() || ends < 0 || ends > s.length()) {
continue;
}
String juge = s.substring(start, ends);
Integer jugeInt = Integer.valueOf(juge);
if (juge.charAt(0) == '0' && juge.length() > 1) {
continue;
}
if (jugeInt <= 255) {
r.add(juge);
dfsRestoreIpAddresses(s, res, ends, ends + 1, r);
r.remove(r.size() - 1);
}
}
}
}