目录
1.1 无重复字符的最长字串(中等):滑动窗口/双指针
1.2 N字形变换(中等):很绕,理解即可
1.3 字符串转换整数(中等):实现API题
1.4 整数转罗马数字(中等):
1.5 最长公共前缀(简单):依次遍历
1.6 有效括号(简单):栈的先入后出特性
1.7 外观数列(中等):遍历 + 双指针
1.8 找出字符串中第一个匹配项的下标(简单):一次遍历
1.9 字符串相乘(中等):一次遍历
1.10 字母异位词分组(中等):排序
1.11 最后一个单词的长度(简单):从后遍历
1.12 简化路径(中等):双端队列
1.13 解码方法(中等):动态规划
1.14 复原IP地址(中等):回溯法
1.15 字符串总结
题目:给定一个字符串 s
,请你找出其中不含有重复字符的 最长子串 的长度。
思想:
滑动窗口作法:
通过枚举其中一个字符串可以发现:
假设选择字符串中的第k
个字符作为起始位置,并且得到了不包含重复字符的最长字串的结束位置为rk
当我们选择第k + 1
个字符作为起始位置时,首先从k + 1
到rk
的字符显然是不重复的,并且少了原本的第k
个字符,因此可以继续增大rk
,直到右侧出现了重复字符为止
采用滑动窗口:
用两个指针表示字符串中的某个字串的左右边界,左指针表示字串的起始位置,右指针为上文的rk
每一次将左指针向右移动一格,开始枚举下一个字符作为起始位置吗,不断地向右移动右指针,保证两个指针对应的字串中没有重复的元素
每轮结束后,找到的最长的子串的长度即为答案
去重操作:
在判断是否具有重复的字符,常用的数据结构为哈希集合
当左指针向右移动时,从哈希集合中移除一个字符,当右指针向右移动时,向哈希集合中添加一个字符
双指针做法:
开始时,左指针移动,并记录字符出现过的次数;
此时记录的巧妙方法:设置一个int
数组,数组开始时长度为128
,对应所有字符数,当对应字符出现过,将数组默认值+1
当有一个字符出现超过一次时,右指针右移,并将对应位置元素出现次数减一,直到字符出现次数不再大于1
总结:理解什么时候移动指针,如何计算长度,如何删除原有元素
代码:
class Solution {
public int lengthOfLongestSubstring(String s) {
//设置一个哈希集合存储元素:可以去重
Set set = new HashSet<>();
//设置右指针rk:开始时右指针位于数组的左侧(还未开始移动)
int rk = -1;
int res = 0;
for(int i = 0; i < s.length(); i++){
//此时i就是左指针,每次左指针右移时,哈希集合中删除上一个元素
if(i > 0){
set.remove(s.charAt(i - 1));
}
//每次右指针右移时,尝试增大rk,并向哈希集合中添加元素(此时要判断是否重复, 不重复才添加元素)
while(rk + 1 < s.length() && !set.contains(s.charAt(rk + 1))){
set.add(s.charAt(rk + 1));
rk++;
}
//此时的长度就是从 i 到 rk个字符,长度为 rk - i + 1
res = Math.max(res, rk - i + 1);
}
return res;
}
}
//双指针
class Solution {
public int lengthOfLongestSubstring(String s) {
//假设初始字符串的出现的次数全为0
int[] count = new int[128];
int res = 0;
//设置左右指针,开始时左指针右移
for(int i = 0, j = 0; i < s.length(); i++){
//对于出现过的字符,将其出现次数 + 1
count[s.charAt(i)]++;
//当字符重复出现时,右指针不断右移,直到删掉了重复元素
while(count[s.charAt(i)] > 1){
--count[s.charAt(j++)];
}
res = Math.max(res, i - j + 1);
}
return res;
}
}
题目:将一个给定字符串 s
根据给定的行数 numRows
,以从上往下、从左到右进行 Z 字形排列。
比如输入字符串为 "PAYPALISHIRING"
行数为 3
时,排列如下:
行数为3: P A H N A P L S I I G Y I R 行数为4 P I N A L S I G Y A H R P I
之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:"PAHNAPLSIIGYIR"
。
思想:按顺序遍历字符串s
res[i] += c
:将字符c
存入对应行si
i += flag
:更新当前字符c
对应的行索引
flag = -flag
,达到Z
字形转折点时,执行反向
总结:这里的方法过于巧妙,一般人想不出,记录一下
代码:
class Solution {
public String convert(String s, int numRows) {
//如果给定行数小于2,直接返回
if(numRows < 2){
return s;
}
//创建一个列表存储最终结果
List rows = new ArrayList<>();
//根据给定的numRows,确定共有几行,即有几个si
for(int i = 0; i < numRows; i++){
rows.add(new StringBuilder());
}
//设置flag,开始时为-1
int flag = -1;
//设置行索引
int i = 0;
for(char c : s.toCharArray()){
//res[i] += c
rows.get(i).append(c);
//达到z字形转折点:0 或者最后一个索引位置;将flag变为相反数,达到Z字形的目的
if(i == 0 || i == numRows - 1){
flag = -flag;
}
i += flag;
}
StringBuilder res = new StringBuilder();
for(StringBuilder row : rows){
res.append(row);
}
return res.toString();
}
}
题目:请你来实现一个 myAtoi(string s)
函数,使其能将字符串转换成一个 32 位有符号整数(类似 C/C++ 中的 atoi
函数)。
函数 myAtoi(string s)
的算法如下:
读入字符串并丢弃无用的前导空格
检查下一个字符(假设还未到字符末尾)为正还是负号,读取该字符(如果有)。 确定最终结果是负数还是正数。 如果两者都不存在,则假定结果为正。
读入下一个字符,直到到达下一个非数字字符或到达输入的结尾。字符串的其余部分将被忽略。
将前面步骤读入的这些数字转换为整数(即,"123" -> 123, "0032" -> 32)。如果没有读入数字,则整数为 0
。必要时更改符号(从步骤 2 开始)。
如果整数数超过 32 位有符号整数范围 [−231, 231 − 1]
,需要截断这个整数,使其保持在这个范围内。具体来说,小于 −231
的整数应该被固定为 −231
,大于 231 − 1
的整数应该被固定为 231 − 1
。
返回整数作为最终结果。
思想:根据给的步骤,我们可以得出几点:
根据步骤1
,需要去除前导空格
根据步骤2
,设置一个变量sign
,初始化为1
,若存在负号,则变为-1
根据步骤3
,判断是否为数字则直接使用ASCII码比较,'0'<= char <= '9'
根据步骤3
,当遇到第一个不是数字的字符时,退出循环
根据步骤5
,转换后数字超过int
需要截取,对于所有结果都要考虑索引越界的情况
总结:根据题目,找出题目已有限制,基于限制进行求解
代码:
class Solution {
public int myAtoi(String s) {
//先将字符串转换为数组,方便计算
char[] chars = s.toCharArray();
int len = s.length();
//1.去掉前置空格,使用index来判断有多少个空格,从index后取值即可去掉前置空格
int index = 0;
while(index < len && chars[index] == ' '){
index++;
}
//若字符串中都是空格,直接返回0
if(index == len){
return 0;
}
//从index开始取值,就能去掉前置空格
char start = chars[index];
//记录一个sign,判断为正还是为负,若为空,则不处理
int sign = 1;
if(start == '+'){
index++;
}else if(start == '-'){
index++;
sign = -1;
}
//若不包含正负号,则直接跳过if-else if判断
int res = 0;
while(index < len){
char currChar = chars[index];
//如果不是数字,结束循环
if(currChar < '0' || currChar > '9'){
break;
}
//索引越界的判断
//当前res*10已经大于最大值,或者res*10刚好等于最大值,但个位数大于int限制,直接返回最大值
if(res > Integer.MAX_VALUE / 10 || (res == Integer.MAX_VALUE / 10 && (currChar - '0') > Integer.MAX_VALUE % 10)){
return Integer.MAX_VALUE;
}
//当前res*10已经小于最小值,或者res*10刚好等于最小值,但个位数大于int限制,直接返回最小值
if(res < Integer.MIN_VALUE / 10 || (res == Integer.MIN_VALUE / 10 && (currChar - '0') > -(Integer.MIN_VALUE % 10))){
return Integer.MIN_VALUE;
}
res = res * 10 + sign * (currChar - '0');
index++;
}
return res;
}
}
题目:罗马数字包含以下七种字符: 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。
给定一个罗马数字,将其转换成整数。
思想:将罗马数字对应的所有数值表示出来,共13
个字符,将它们按照降序排列,每次寻找到最大的一个不超过num
的值
由于罗马数字的唯一表示法,给定的一个整数num
,求得它的罗马数字值需要经过两步:
先找到第一个不超过num
的最大数值value
,然后用num
减去这个最大数值,将value
记录
继续寻找不超过num - balue
的最大数值,将接下来的数值继续记录,直到num = 0
总结:最大值一定是峰值,可将该题转换思路进行解决
代码:
class Solution {
//按照降序排列,然后依次寻找不大于num的最大值
int[] values = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};
String[] strs = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"};
public String intToRoman(int num) {
StringBuffer res = new StringBuffer();
//遍历这个value
for(int i = 0; i < values.length; i++){
int value = values[i];
String str = strs[i];
//如果当前值大于等于value,说明找到了第一个不大于num的最大值
//这里用while而不是if:比如3 : III, 只能使用while才能将所有I存入数组,如果是其它判断完就结束
while(num >= value){
num -= value;
res.append(str);
}
if(num == 0){
break;
}
}
return res.toString();
}
}
题目:编写一个函数来查找字符串数组中的最长公共前缀。如果不存在公共前缀,返回空字符串 ""
。
思想:依次遍历字符串数组中的每个字符串,对于每个遍历到的字符串,更新到最长公共前缀;
当尚未遍历完所有的字符串时,最长公共前缀已经是空字符串,则最长公共前缀一定是空字符串,直接返回
步骤:
从数组中的第一个字符串开始,先比较第一个字符串和第二个字符串的公共前缀,并将公共前缀更新,依次遍历下去
比较公共前缀时,注意最多比较到两个字符串中较小长度的那一个
总结:依次比较每一个字符串与下一个字符串的最长公共前缀,然后每次更新最长公共前缀并返回即可
代码:
class Solution {
public String longestCommonPrefix(String[] strs) {
//若strs为空,直接返回
if(strs == null || strs.length == 0){
return "";
}
//从第一个字符串开始比较
String prefix = strs[0];
int len = strs.length;
//依次比较字符串每次的最长公共前缀,并用一个prefix存储每次的值
for(int i = 1; i < len; i++){
prefix = getPrefix(prefix, strs[i]);
//若某一次最长公共前缀已经为空,直接结束循环
if(prefix.length() == 0){
break;
}
}
return prefix;
}
public String getPrefix(String str1, String str2){
//从最小长度开始寻找
int len = Math.min(str1.length(), str2.length());
int index = 0;
//确定 str1 和 str2 的最长前缀长度
while(index < len && str1.charAt(index) == str2.charAt(index)){
index++;
}
return str1.substring(0, index);
}
}
题目:给定一个只包括 '('
,')'
,'{'
,'}'
,'['
,']'
的字符串 s
,判断字符串是否有效。
思想:使用栈来进行有效判断:先将有效括号存入一个Map
集合中,方便计算
开始时,栈为空,将s
的第一个字符存入
第二次循环,判断栈顶元素和s
的第二个字符是否匹配,是则将元素出栈,否则继续入栈;依此类推
最终检查栈中是否有元素,有就说明不匹配
总结:依次比较每一个字符串与下一个字符串的最长公共前缀,然后每次更新最长公共前缀并返回即可
代码:
class Solution{
Map map = new HashMap<>(){{
put(')', '(');
put('}', '{');
put(']', '[');
}};
public boolean isValid(String s){
Deque stack = new LinkedList<>();
int len = s.length();
for(int i = 0; i < len; i++){
//判断栈顶元素和`s`的第二个字符是否匹配,是则将元素出栈,否则继续入栈;依此类推
if(stack.size() > 0 && map.get(s.charAt(i)) == stack.peek()){
stack.pop();
}else{
stack.push(s.charAt(i));
}
}
//检查栈中是否有元素,有就说明不匹配
return stack.size() == 0;
}
}
题目:给定一个正整数 n
,输出外观数列的第 n
项。
「外观数列」是一个整数序列,从数字 1 开始,序列中的每一项都是对前一项的描述。
你可以将其视作是由递归公式定义的数字字符串序列:
countAndSay(1) = "1"
countAndSay(n)
是对 countAndSay(n-1)
的描述,然后转换成另一个数字字符串。
例如,数字字符串 "3322251"
的描述如下图:
思想:外观数列,其实就是依次统计字符串中连续相同字符的个数;因此:定义Si
代表countAndSay(i)
,若要求出Sn
,则需要先求出Sn-1
,也就是从左到右扫描字符串Sn-1
中连续相同字符的最大数量,然后将字符串的统计数目转换为数字字符串再连接上对应的字符
从S1 = 1
开始,按照上述方法依次生成S2,S3,...,Sn
总结:重点是想清楚每次分别实现每一次的Sn
的搜寻工作,两个指针的方式是一个选择
代码:
class Solution {
public String countAndSay(int n) {
//S1 = 1
String str = "1";
for(int i = 2; i <= n; i++){
//设置一个存储Sn的字符串
StringBuffer sn = new StringBuffer();
//声明两个指针,都从字符串的第一个值开始搜索,判断有几个str.charAt(start)
int start = 0;
int pos = 0;
while(pos < str.length()){
//当pos小于str.length()时,判断有几个str.charAt(start)
while(pos < str.length() && str.charAt(pos) == str.charAt(start)){
pos++;
}
sn.append(Integer.toString(pos - start)).append(str.charAt(start));
//此时说明已找到了pos个相同的start,从start后接着找
start = pos;
}
str = sn.toString();
}
return str;
}
}
题目:给你两个字符串 haystack
和 needle
,请你在 haystack
字符串中找出 needle
字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle
不是 haystack
的一部分,则返回 -1
。
思想:将字符串neddle
和字符串 haystack
的所有长度为 m
的子串均匹配一次;
具体实现:让 haystack
每次的值为 i + j
, needle
值为 j
即可
总结:重点如何让长度为 m
的子串均匹配一次;
代码:
class Solution {
public int strStr(String haystack, String needle) {
int n = haystack.length();
int m = needle.length();
//将字符串needle和haystack的所有长度为m的子串均匹配一次
for(int i = 0; i + m <= n; i++){
boolean flag = true;
//将needle和haystack长度为m的子串匹配
for(int j = 0; j < m; j++){
if(haystack.charAt(i + j) != needle.charAt(j)){
flag = false;
break;
}
}
if(flag){
return i;
}
}
return -1;
}
}
题目:给定两个以字符串形式表示的非负整数 num1
和 num2
,返回 num1
和 num2
的乘积,它们的乘积也表示为字符串形式。
注意:不能使用任何内置的 BigInteger
库或直接将输入转换为整数。
思想:num1
和num2
可以每次分别取个位、十位、百位等等等相乘,将相对应的值存储在一个结果数组res
中;结果数组的长度为 num1 + num2
的长度之和
比如num1
的第i
个数和num2
的第j
个数的乘积可以使用如下方式给出:
int sum = res[i + j + 1] + value1 * value2;
res[i + j + 1] = sum % 10;
res[i + j] += sum / 10;
总结:重点是每次的乘积如何存储在结果数组中
代码:
class Solution {
public String multiply(String num1, String num2) {
//如果 num1 或者 num2 有一个为0,结果一定为0
if(num1.equals("0") || num2.equals("0")){
return "0";
}
//取得 num1 和 num2 的值
int len1 = num1.length();
int len2 = num2.length();
//创建一个数组记录最终的结果值
int[] res = new int[len1 + len2];
//从每个数的个位开始相乘,并从右向左移动
for(int i = len1 - 1; i >= 0; i--){
int value1 = num1.charAt(i) - '0';
for(int j = len2 - 1; j >= 0; j--){
int value2 = num2.charAt(j) - '0';
//将 value1 * value2 并加上每次的进位
int sum = res[i + j + 1] + value1 * value2;
res[i + j + 1] = sum % 10;
res[i + j] += sum / 10;
}
}
StringBuffer sb = new StringBuffer();
for(int i = 0; i < res.length; i++){
//判断最终结果的第一个数字是否为 0;不能写为res[0] == 0;不然有时候数组第一个值为0,每次都会continue
if(i == 0 && res[i] == 0){
continue;
}
sb.append(res[i]);
}
return sb.toString();
}
}
题目:给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
字母异位词 是由重新排列源单词的所有字母得到的一个新单词。
思想:两个字符串互为字母异位词,则两个字符串包含的字符一定相同,同一组字母异位词中的字符串排序后具备相同的字符,可以使用排序后的字符作为一组字母异位词的标志
使用哈希表存储每一组字母异位词,哈希表的键为一组字母异位词的排序后的字符,哈希表的值为一组字母异位词列表
遍历每个字符串,对于每个字符串得到该字符串所在的一组字母异位词的标志,将当前字符串加入该族字母异位词的列表中,遍历全部字符串后,哈希表中的每个键值对即为一组字母异位词
总结:重点是想清楚每次分别实现每一次的Sn
的搜寻工作,两个指针的方式是一个选择
代码:
class Solution {
public List> groupAnagrams(String[] strs) {
//创建一个哈希表存储最终结果:键为一个字符串的所有字符,值为所有的字母异位词,因此应该是一个二维列表
Map> map = new HashMap<>();
//遍历字符串
for(String str : strs){
//将字符串的值重排序后,存入键中:如此一来,所有字母互位词的排序都是一样的,会存到一个list中
char[] chars = str.toCharArray();
Arrays.sort(chars);
String key = new String(chars);
//在map中寻找是否有key相关联的值,没有则返回一个新列表
List list = map.getOrDefault(key, new ArrayList());
list.add(str);
map.put(key, list);
}
return new ArrayList>(map.values());
}
}
题目:给你一个字符串 s
,由若干单词组成,单词前后用一些空格字符隔开。返回字符串中 最后一个 单词的长度。单词 是指仅由字母组成、不包含任何空格字符的最大子字符串。
思想:从后向前遍历,找到第一个不为空的字符,然后从该字符开始,统计最后一个单词的长度
总结:从后向前遍历是通用解法,只不过需要先排除最后一个单词后面也有为空的情况
代码:
class Solution {
public int lengthOfLastWord(String s) {
char[] cs = s.toCharArray();
int res = 0;
int index = s.length() - 1;
//从后往前找到第一个不为空的元素
while(s.charAt(index) == ' '){
index--;
}
//从这个元素开始,寻找不为空元素的个数
for(int i = index; i >= 0; i--){
if(cs[i] == ' '){
break;
}
res++;
}
return res;
}
}
题目:给你一个字符串 path
,表示指向某一文件或目录的 Unix 风格 绝对路径 (以 '/'
开头),请你将其转化为更加简洁的规范路径。在 Unix 风格的文件系统中,一个点(.
)表示当前目录本身;此外,两个点 (..
) 表示将目录切换到上一级(指向父目录);两者都可以是复杂相对路径的组成部分。任意多个连续的斜杠(即,'//'
)都被视为单个斜杠 '/'
。 对于此问题,任何其他格式的点(例如,'...'
)均被视为文件/目录名称。
请注意,返回的 规范路径 必须遵循下述格式:
始终以斜杠 '/'
开头。
两个目录名之间必须只有一个斜杠 '/'
。
最后一个目录名(如果存在)不能 以 '/'
结尾。
此外,路径仅包含从根目录到目标文件或目录的路径上的目录(即,不含 '.'
或 '..'
)。
返回简化后得到的 规范路径 。
思想:由于文件名经过 '/'
分割后只有四种情况:
空字符串
一个点 '.'
两个点 '..'
其他目录名
对于前两种,可以不进行处理,因为不会切换目录;对于后两种我们将其放入栈中,具体是:
两个点 '..'
,需要将目录切换到上一级,即弹出栈顶元素
目录,将其存入栈中
最终,从栈底到栈顶的字符串用 '/'
相连接,并在最前面加上 '/'
表示根目录即可
总结:从后向前遍历是通用解法,只不过需要先排除最后一个单词后面也有为空的情况
代码:
class Solution {
public String simplifyPath(String path) {
//将字符串按照'/'分开
String[] names = path.split("/");
//使用栈来存储元素
Deque deque = new LinkedList<>();
//只处理两种情况: ".." 和 "目录名"
for(String name : names){
if("..".equals(name)){
if(!deque.isEmpty()){
deque.pollLast();
}
}else if(name.length() > 0 && !".".equals(name)){
deque.offerLast(name);
}
}
//对栈中的元素进行处理,从栈底到栈顶依次加上'/'
StringBuffer sb = new StringBuffer();
if(deque.isEmpty()){
sb.append("/");
}else{
while(!deque.isEmpty()){
sb.append("/");
sb.append(deque.pollFirst());
}
}
return sb.toString();
}
}
题目:一条包含字母 A-Z
的消息通过以下映射进行了 编码 :
'A' -> "1" 'B' -> "2" ... 'Z' -> "26"
要 解码 已编码的消息,所有数字必须基于上述映射的方法,反向映射回字母(可能有多种方法)。例如,"11106"
可以映射为:
"AAJF"
,将消息分组为 (1 1 10 6)
"KJF"
,将消息分组为 (11 10 6)
注意,消息不能分组为 (1 11 06)
,因为 "06"
不能映射为 "F"
,这是由于 "6"
和 "06"
在映射中并不等价。
给你一个只含数字的 非空 字符串 s
,请计算并返回 解码 方法的 总数 。
题目数据保证答案肯定是一个 32 位 的整数。
思想:使用动态规划解决此问题即可:假设fi
表示字符串s
的前i
个字符s[1,2,...,i]
的解码方法数,我们针对最后一个字符解码时有两种情况:
情况一:最后一次解码时使用了一个字符,则只要s[i] != 0
,就可以被解码为某个字母,解码方法数可以表示为:fi = fi - 1, s[i] != 0
情况二:最后一次解码时使用了两个字符,则只要s[i - 1] != 0
,并且s[i] + s[i - 1]
组成的整数小于等于26
即可;解码方法数可以表示为:fi = fi - 2, s[i - 1] != 0 && 10*s[i − 1] + s[i]≤26
两种情况之和即为最终答案
边界条件为:f0 = 1
,空字符串可以解码为空字符串
总结:从后向前遍历是通用解法,只不过需要先排除最后一个单词后面也有为空的情况
代码:
class Solution {
public int numDecodings(String s) {
//设置一个数组,用来存储结果值
int n = s.length();
int[] f = new int[n + 1];
//空字符串解析为空字符串,有且仅有一种解码方法
f[0] = 1;
//利用动态规划方程求解
for(int i = 1; i <= n; i++){
//第一种情况:最后一次解码时使用了一个字符;注意此时下标减1,保持和语言中下标从0开始一致
if(s.charAt(i - 1) != '0'){
f[i] += f[i - 1];
}
//第二种情况: 最后一次解码时使用了两个字符
if(i > 1 && s.charAt(i - 2) != '0' && ((s.charAt(i - 2) - '0') * 10 + (s.charAt(i - 1) - '0') <= 26)){
f[i] += f[i - 2];
}
}
return f[n];
}
}
题目:有效 IP 地址 正好由四个整数(每个整数位于 0
到 255
之间组成,且不能含有前导 0
),整数之间用 '.'
分隔。
例如:"0.1.2.201"
和"192.168.1.1"
是 有效 IP 地址,但是 "0.011.255.245"
、"192.168.1.312"
和 "[email protected]"
是 无效 IP 地址。
给定一个只包含数字的字符串 s
,用以表示一个 IP 地址,返回所有可能的有效 IP 地址,这些地址可以通过在 s
中插入 '.'
来形成。你 不能 重新排序或删除 s
中的任何数字。你可以按 任何 顺序返回答案。
思想:题目要求寻找所有可能的IP地址
,因此可以采用回溯法,寻求所有的可行解,并返回
注意:从第二个元素开始,要排除前导为0的情况
总结:寻求所有可能结果,首选回溯法
代码:
class Solution {
//创建两个列表,一个用来存储最终结果,一个用来存储临时结果值
List res = new ArrayList<>();
List temp = new ArrayList<>();
public List restoreIpAddresses(String s) {
//从 第0个segId 开始寻找,地址一共有四段
dfs(s, 0, 4);
return res;
}
public void dfs(String s, int i, int k){
//当搜索到最后一个阶段,则将每一段的值用 "." 分开存入
if(k == 0){
if(i == s.length()){
res.add(String.join(".", temp));
}
return;
}
//若未到最后一个阶段,则从i开始进行搜索;每个地址只有三个数,因此j < i + 3
for(int j = i; j < s.length() && j < i + 3; j++){
//第二个数开始,前导不能为0
if(s.charAt(i) == '0' && j > i){
return;
}
//拿到第 i 到 j 个元素并转换为int类型
int v = Integer.parseInt(s.substring(i, j + 1));
if(v >= 0 && v <= 255){
temp.add(s.substring(i, j + 1));
dfs(s, j + 1, k - 1);
//回溯
temp.remove(temp.size() - 1);
}
}
}
}
字符串类题目可以有几种求解思路:
将字符串转换为数组,或者直接用字符串的charAt()
方法,将字符串内的元素当作一个个数组索引一样考虑
字符串问题可以多使用StringBuffer
等,具有丰富的API
,能够方便的求解