1.路径加密
假定一段路径记作字符串 path
,其中以 ".
" 作为分隔符。现需将路径加密,加密方法为将 path
中的分隔符替换为空格 "
",请返回加密后的字符串。
示例 1:
输入:path = "a.aef.qerf.bb"
输出:"a aef qerf bb"
限制:
0 <= path.length <= 10000
1.1遍历添加
解题思路
题目要求将路径字符串 path
中的每个 .
替换为空格 。因为字符串是不可变的,我们需要新建一个字符串或容器,在遍历 path
的每个字符时逐一进行判断和添加。
算法流程
具体的算法流程如下:
- 初始化一个
StringBuilder
(Java)或list
(Python),用于存放加密后的字符,命名为res
。 - 遍历字符串
path
的每一个字符c
:- 如果
c
是.
,则将空格 添加到res
。 - 如果
c
不是.
,则直接将c
添加到res
。
- 如果
- 遍历结束后,将
res
转化为字符串并返回。
这种逐字符处理的方式适用于不修改原字符串的情况,有助于我们根据条件自由地构建新的字符串。
复杂度分析
- 时间复杂度:O(N),其中 N 为
path
的长度。遍历path
中的每个字符的时间复杂度是 O(N),每个字符的处理(判断与添加)为 O(1)。 - 空间复杂度:O(N)。创建了一个
StringBuilder
或list
来存储新字符串,相当于与path
等长的额外空间开销。
代码示例
class Solution {
// 定义一个方法,用于加密路径字符串 path,将所有的 "." 替换为空格 " "
public String pathEncryption(String path) {
// 创建一个 StringBuilder 对象,用于构建新的字符串
StringBuilder res = new StringBuilder();
// 将字符串 path 转换为字符数组,逐个字符进行遍历
for (Character c : path.toCharArray()) {
// 如果当前字符是 '.',将空格 ' ' 添加到 StringBuilder 中
if (c == '.') {
res.append(' ');
} else {
// 如果当前字符不是 '.',直接将该字符添加到 StringBuilder 中
res.append(c);
}
}
// 将 StringBuilder 转换为字符串并返回结果
return res.toString();
}
}
2.有效数字
有效数字(按顺序)可以分成以下几个部分:
- 若干空格
- 一个 小数 或者 整数
- (可选)一个
'e'
或'E'
,后面跟着一个 整数 - 若干空格
小数(按顺序)可以分成以下几个部分:
- (可选)一个符号字符(
'+'
或'-'
) - 下述格式之一:
- 至少一位数字,后面跟着一个点
'.'
- 至少一位数字,后面跟着一个点
'.'
,后面再跟着至少一位数字 - 一个点
'.'
,后面跟着至少一位数字
- 至少一位数字,后面跟着一个点
整数(按顺序)可以分成以下几个部分:
- (可选)一个符号字符(
'+'
或'-'
) - 至少一位数字
部分有效数字列举如下:["2", "0089", "-0.1", "+3.14", "4.", "-.9", "2e10", "-90E3", "3e+7", "+6e-1", "53.5e93", "-123.456e789"]
部分无效数字列举如下:["abc", "1a", "1e", "e3", "99e2.5", "--6", "-+3", "95a54e53"]
给你一个字符串 s
,如果 s
是一个 有效数字 ,请返回 true
。
示例 1:
输入:s = "0"
输出:true
示例 2:
输入:s = "e"
输出:false
示例 3:
输入:s = "."
输出:false
提示:
1 <= s.length <= 20
s
仅含英文字母(大写和小写),数字(0-9
),加号'+'
,减号'-'
,空格' '
或者点'.'
。
解题思路
这道题的目标是验证一个字符串是否为有效数字。有效数字的定义比较复杂,因此我们使用**有限状态自动机(Finite State Machine, FSM)**来设计算法,将不同字符的输入分成多个状态来判断有效性。
算法流程
1. 字符类型划分
根据题目中的描述,将字符分为以下几类:
- 空格:
' '
,允许出现在开头或结尾。 - 数字:
'0'
到'9'
。 - 符号:
'+'
或'-'
,可以出现在数字前面。 - 幂符号:
'e'
或'E'
,表示科学计数法的指数部分。 - 小数点:
.
,表示小数的整数部分与小数部分之间的分隔。 - 非法字符:任何不属于以上类别的字符会导致判定为无效。
2. 状态定义
我们定义九种状态:
- 状态 0:起始的空格。
- 状态 1:数字或小数点前的符号。
- 状态 2:小数点前的数字。
- 状态 3:小数部分的数字。
- 状态 4:只有小数点后的数字。
- 状态 5:幂符号。
- 状态 6:幂符号后的符号。
- 状态 7:幂符号后的数字。
- 状态 8:结尾的空格。
3. 状态转移表
基于以上状态,构建状态转移表 states
。该表用于记录每个状态下遇到特定类型字符后的转移情况。以 HashMap
来存储,每个 key-value
对表示一个字符类型(key
)和目标状态(value
)。
4. 算法步骤
- 初始化
states
数组,定义从每个状态到下一个状态的可能转移。 - 遍历字符串
s
的每个字符,根据字符类型确定转移类型t
。 - 检查当前状态
p
下是否存在该字符类型的转移;若不存在,则返回false
。 - 若存在转移,则根据
states
中的映射进行状态更新。 - 遍历结束后,如果状态
p
是有效的结束状态(2, 3, 7, 8),则返回true
,否则返回false
。
复杂度分析
- 时间复杂度:O(N),其中 N 为字符串
s
的长度。遍历字符串,每个字符的状态转移操作为 O(1)。 - 空间复杂度:O(1),
states
和状态变量p
使用常数大小的空间。
代码示例
import java.util.HashMap;
import java.util.Map;
class Solution {
public boolean validNumber(String s) {
// 状态转移表,states[i] 表示在状态 i 时,根据输入字符的类型选择下一状态
Map<Character, Integer>[] states = new Map[]{
// 状态 0:起始的空格,可以转移到状态 0(继续空格)、状态 1(符号)、状态 2(数字)、状态 4(小数点)
new HashMap<>() {{ put(' ', 0); put('s', 1); put('d', 2); put('.', 4); }},
// 状态 1:符号后,可以转移到状态 2(数字)、状态 4(小数点)
new HashMap<>() {{ put('d', 2); put('.', 4); }},
// 状态 2:小数点前的数字,可以转移到状态 2(继续数字)、状态 3(小数部分的数字)、状态 5(幂符号)、状态 8(结尾空格)
new HashMap<>() {{ put('d', 2); put('.', 3); put('e', 5); put(' ', 8); }},
// 状态 3:小数部分的数字,可以转移到状态 3(继续小数数字)、状态 5(幂符号)、状态 8(结尾空格)
new HashMap<>() {{ put('d', 3); put('e', 5); put(' ', 8); }},
// 状态 4:只有小数点后的数字,可以转移到状态 3(小数数字)
new HashMap<>() {{ put('d', 3); }},
// 状态 5:幂符号,可以转移到状态 6(幂后的符号)、状态 7(幂后的数字)
new HashMap<>() {{ put('s', 6); put('d', 7); }},
// 状态 6:幂符号后的符号,可以转移到状态 7(幂后的数字)
new HashMap<>() {{ put('d', 7); }},
// 状态 7:幂符号后的数字,可以转移到状态 7(继续幂数字)、状态 8(结尾空格)
new HashMap<>() {{ put('d', 7); put(' ', 8); }},
// 状态 8:结尾的空格,可以继续保持在状态 8
new HashMap<>() {{ put(' ', 8); }}
};
int p = 0; // 初始状态
char t; // 当前字符类型
// 遍历字符串中的每个字符
for (char c : s.toCharArray()) {
// 判断字符类型:数字('d')、符号('s')、幂符号('e')、小数点或空格
if (c >= '0' && c <= '9') t = 'd';
else if (c == '+' || c == '-') t = 's';
else if (c == 'e' || c == 'E') t = 'e';
else if (c == '.' || c == ' ') t = c;
else t = '?'; // 非法字符
// 如果当前状态下没有该字符类型的转移,则直接返回 false
if (!states[p].containsKey(t)) return false;
// 状态转移
p = states[p].get(t);
}
// 检查是否为合法的结束状态,只有状态 2(整数部分)、状态 3(小数部分)、状态 7(幂数字)、状态 8(结尾空格)为合法
return p == 2 || p == 3 || p == 7 || p == 8;
}
}
3.套餐内的商品的排列顺序
某店铺将用于组成套餐的商品记作字符串 goods
,其中 goods[i]
表示对应商品。请返回该套餐内所含商品的 全部排列方式 。
返回结果 无顺序要求,但不能含有重复的元素。
示例 1:
输入:goods = "agew"
输出:["aegw","aewg","agew","agwe","aweg","awge","eagw","eawg","egaw","egwa","ewag","ewga","gaew","gawe","geaw","gewa","gwae","gwea","waeg","wage","weag","wega","wgae","wgea"]
提示:
1 <= goods.length <= 8
解题思路
对于一个包含不同商品的字符串 goods
,可以生成所有可能的排列组合。这道题的主要思路是使用**深度优先搜索(DFS)**来遍历字符串的所有排列组合,并通过剪枝来避免重复的组合。
对于给定长度为 n
的字符串(假设字符互不重复),共有 n!
种排列方式。具体地,我们通过递归固定字符串中的每一位字符,并在后续步骤中固定剩余字符的位置。这样可以得到所有可能的排列组合。
算法流程
-
初始化:将
goods
转换为字符数组arr
便于交换字符,创建res
列表用于存储所有无重复排列组合。 -
递归与回溯:
- 递归函数
dfs(x)
用于确定字符串arr
的第x
位字符。 - 当
x
达到arr.length - 1
时,意味着所有位都已经固定,因此将arr
转换为字符串并加入结果集中res
。 - 初始化一个
HashSet
集合set
,用于记录已经在当前位置固定过的字符。如果字符已经在set
中,则跳过此字符,避免生成重复排列。 - 遍历
i ∈ [x, arr.length)
范围内的字符,将字符arr[i]
和arr[x]
交换位置以固定arr[i]
在第x
位,随后递归调用dfs(x + 1)
固定下一个位置。 - 完成递归后,将
arr[i]
和arr[x]
位置还原,以恢复之前的状态(回溯)。
- 递归函数
-
剪枝:在每一层递归中使用
HashSet
记录已固定的字符。如果字符已经出现,则跳过(剪枝),这样可以避免生成重复排列。
复杂度分析
- 时间复杂度:
O(N! * N)
,其中N
为字符串goods
的长度。总共有N!
种排列方案,每种排列的拼接操作join()
复杂度为O(N)
。 - 空间复杂度:
O(N^2)
。递归深度为N
,占用栈空间为O(N)
。在每一层递归中,HashSet
用于存储当前字符的不同排列组合,最多占用N + (N - 1) + ... + 2 + 1 = (N + 1) * N / 2
个字符,即O(N^2)
。
代码实现
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
class Solution {
List<String> res = new LinkedList<>(); // 存储所有的排列结果
char[] arr; // 字符数组,用于存储输入的商品字符串
// 主函数,将输入字符串转换为字符数组并开始 DFS
public String[] goodsOrder(String goods) {
arr = goods.toCharArray(); // 将输入字符串转换为字符数组
dfs(0); // 从第一个字符开始进行深度优先搜索
return res.toArray(new String[res.size()]); // 返回所有排列结果的数组
}
// 深度优先搜索,固定第 x 位字符
void dfs(int x) {
// 当 x 达到 arr 的最后一位时,说明所有字符都已固定
if (x == arr.length - 1) {
res.add(String.valueOf(arr)); // 将当前排列转换为字符串并添加到结果中
return; // 返回上层递归
}
HashSet<Character> set = new HashSet<>(); // 创建一个集合用于记录已固定的字符
// 遍历当前位置 x 到 arr.length 的所有字符
for (int i = x; i < arr.length; i++) {
// 如果集合中已存在当前字符,则跳过此循环(剪枝)
if (set.contains(arr[i])) continue;
set.add(arr[i]); // 将当前字符加入集合,标记为已使用
swap(i, x); // 交换字符,将 arr[i] 固定在第 x 位
dfs(x + 1); // 递归调用,固定下一个字符的位置
swap(i, x); // 恢复交换(回溯),以便进行下一个排列
}
}
// 辅助函数,用于交换字符数组中的两个字符
void swap(int a, int b) {
char tmp = arr[a]; // 暂存 a 位置的字符
arr[a] = arr[b]; // 将 b 位置的字符放到 a 位置
arr[b] = tmp; // 将暂存的字符放到 b 位置
}
}