目录
- 数据结构——字符串
- 直接解
- 【剑指offer】05. 替换空格
- 【剑指offer】17. 打印从1到最大的n位数
- 【剑指offer】20. 表示数值的字符串
- 【剑指offer】37. 序列化二叉树
- 【剑指offer】50. 第一个只出现一次的字符
- 【剑指offer】58. 翻转单词顺序
- 【剑指offer】58.2 左旋转字符串
- 【剑指offer】67. 把字符串转换成整数
- 特殊解——动态规划
- 【剑指offer】19. 正则表达式匹配
- 特殊解——回溯搜索
- 【剑指offer】38. 字符串的排列
- 特殊解——双指针
- 【剑指offer】48. 最长不含重复字符的子字符串
数据结构——字符串
直接解
【剑指offer】05. 替换空格
题目描述
—
//05. 替换空格
//请实现一个函数,把字符串 s 中的每个空格替换成"%20"。s
题解
// 遍历拼接法
// 遇到空格就加%20,空格左右重新拼接。
// 时间复杂度:5 ms , 在所有 Java 提交中击败了 5.50% 的用户
// 空间复杂度:38.1 MB , 在所有 Java 提交中击败了 5.00% 的用户
class Solution {
public String replaceSpace(String s) {
String ns = s;
for (int i=0; i<=ns.length() - 1; i++) {
if (ns.charAt(i) == ' ') {
ns = ns.substring(0,i) + "%20" + ns.substring(i+1, ns.length());
}
}
return ns;
}
}
// 利用StringBuilder一个个拼接
class Solution {
public String replaceSpace(String s) {
StringBuilder res = new StringBuilder();
int start = 0;
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == ' ') {
res.append(s.substring(start, i) + "%20");
start = i + 1;
}
}
if (start != s.length())
res.append(s.substring(start, s.length()));
return res.toString();
}
}
// 一个个遍历,遇到空格加%20
// 时间复杂度O(N):0 ms , 在所有 Java 提交中击败了 100.00% 的用户
// 空间复杂度O(N):36 MB , 在所有 Java 提交中击败了 98.40% 的用户
class Solution {
public String replaceSpace(String s) {
StringBuilder sb = new StringBuilder();
for (int i=0; i < s.length(); i++) {
char c = s.charAt(i); // 遍历所有的字符
if (c == " ") { // 是空格就放%20
sb.append("%20");
}
else { // 不是空格就直接放
sb.append(c);
}
}
return sb.toString();
}
}
// 调包法
// 时间复杂度:0 ms , 在所有 Java 提交中击败了 100.00%的用户
// 空间复杂度:36.3 MB , 在所有 Java 提交中击败了90.21% 的用户
class Solution {
public String replaceSpace(String s) {
return s.replace(" ", "%20");
}
}
// 双指针法
// 时间复杂度:1 ms , 在所有 Java 提交中击败了 26.74% 的用户
// 空间复杂度:36.3 MB , 在所有 Java 提交中击败了 91.47%的用户
class Solution {
public String replaceSpace(String s) {
StringBuffer ns = new StringBuffer(s);
int p1 = ns.length() - 1;
for (int i = 0; i <= p1; i++) {
if (ns.charAt(i) == ' ') {
ns.append(" ");
}
}
System.out.println(ns);
int p2 = ns.length() - 1; // 新字符串的末位
while (p1 >= 0 && p2 >= p1) {
char c = ns.charAt(p1--);
if (c == ' ') {
ns.setCharAt(p2--, '0');
ns.setCharAt(p2--, '2');
ns.setCharAt(p2--, '%');
System.out.println(ns);
}
else {
ns.setCharAt(p2--, c);
System.out.println(ns);
}
}
System.out.println(ns);
return ns.toString();
}
}
【剑指offer】17. 打印从1到最大的n位数
题目描述
// 力扣
// 输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,
// 则打印出 1、2、3 一直到最大的 3 位数 999。
题解
// 直接法(不推荐,毫无意义)
// 力扣
// 执行用时:1 ms, 在所有 Java 提交中击败了100.00%的用户
// 内存消耗:46.3 MB, 在所有 Java 提交中击败了90.56%的用户
class Solution {
public int[] printNumber(int n) {
int end = (int) Math.pow(10, n) - 1;
int[] res = new int[end + 1];
for (int i = 0; i < end; i++) {
res[i] = i + 1;
}
return res;
}
}
打印形式
class Solution {
public static void printNumbers(int n) {
StringBuilder str = new StringBuilder();
// 初始化,n为多少就拓展为n位的0
// 如n=3,则str为'000'
for (int i = 0; i < n; i++) {
str.append('0');
}
while (!addOne(str)) { // 如果+1之后没有进位
int index = 0; // index初始化为0
// index从左到右(高位到低位)遍历str的每一位字符,直到不为0的字符值
// 如果index超过str长度边界(不可能,所以这句可以不要),则跳出while
// 或如果index遍历的str字符不为0,则跳出while(主要用这句控制)
while (index < str.length() && str.charAt(index) == '0') {
index++;
}
// 打印str,index的位置为str不为0的位置
// substring(index)会保留str在index位置以后的字符
// 所以就能够保证非n位数前面的0被去除,比如原来是02,只打印2
System.out.println(str.toString().substring(index));
}
}
// 执行+1操作的函数,返回进位标识符,
// 如果+1进位,返回true,否则返回false
public static boolean addOne(StringBuilder str) {
// 进位标识符,进位true,不进位false
boolean isOverflow = false;
// 从字符串str右边开始往左遍历,即从低位往高位遍历
// 当前遍历位索引记为i
// 1:如果第i位字符的值+1之后不进位,则循环结束,函数返回false
// 2:如果第i位字符的值+1之后进位,则i左移,循环会遍历str高位,令高位执行+1操作。
for (int i = str.length() - 1; i >= 0; i--) {
char s = (char) (str.charAt(i) + 1); // 第i位字符值+1,记为字符s
if (s > '9') { // 如果第i为字符值+1之后大于9,需要进位
str.replace(i, i + 1, "0"); // 进位后,str中第i位设为0
if (i == 0)
isOverflow = true; // 此时将进位符isOverflow设为true,表示需要进位
} // 进位后,for循环会继续遍历str的高位,将高位的字符值+1,完成进位
else { // 如果s <= '9'
// 不进位,将第i位的字符值替换为已经+1的s
str.replace(i, i + 1, String.valueOf(s));
break; // 终止当前for循环(也就是不继续遍历字符串str的高位)
}
}
return isOverflow; // 函数结束时返回进位标识符
}
}
/ 数组形式
// 执行用时:20 ms, 在所有 Java 提交中击败了5.02%的用户
// 内存消耗:46.2 MB, 在所有 Java 提交中击败了93.47%的用户
class Solution {
int[] res;
int i = 0;
public int[] printNumbers(int n) {
StringBuilder str = new StringBuilder();
for (int i = 0; i < n; i++) {
str.append('0');
}
res = new int[(int) Math.pow(10, n) - 1];
while (!addOne(str)) {
int index = 0;
while (index < str.length() && str.charAt(index) == '0') {
index++;
}
// Integer.parseInt将StringBuilder中的字符串转为整数
res[i] = Integer.parseInt(str.substring(index));
i++;
}
return res;
}
public boolean addOne(StringBuilder str) {
boolean isOverflow = false;
for (int i = str.length() - 1; i >= 0; i--) {
char s = (char) (str.charAt(i) + 1);
if (s > '9') {
str.replace(i, i + 1, "0");
if (i == 0)
isOverflow = true;
}
else {
str.replace(i, i + 1, String.valueOf(s));
break;
}
}
return isOverflow;
}
}
【剑指offer】20. 表示数值的字符串
题目描述
// 力扣 & 牛客
// 请实现一个函数用来判断字符串是否表示数值(包括整数和
// 小数)。例如,字符串"+100"、"5e2"、"-123"、"3.1416"、
// "-1E-16"、"0123"都表示数值,但"12e"、"1a3.14"、"1.2.3
// "、"+-5"及"12e+5.4"都不是。
// 建议做牛客的题,这题在力扣里的测试用例会带空格,非常智障
题解
// 条件判断 /
// 牛客
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param str string字符串
* @return bool布尔型
*/
public boolean isNumeric (String str) {
if(str == null || str.length() == 0) return false;
char[] char_arr = str.toCharArray();
int len = char_arr.length;
boolean existE = false;
int existSign = 0;
boolean existSignM = false;
boolean existPoint = false;
boolean existNum = false;
// 遍历str元素索引记为i
for (int i = 0; i < len; i++) {
if (char_arr[i] == 'e' || char_arr[i] == 'E') {
if (!existNum) return false; // E e先于数字出现,false
if (existE) return false; // E e出现过,false
if (i == len - 1) return false; // 首尾不能有E e
existE = true;
}
else if (char_arr[i] == '+' || char_arr[i] == '-') {
if (i == len - 1) return false; // 尾不能有+ -
if (i != 0 && char_arr[i - 1] != 'E' && char_arr[i - 1] != 'e') {
return false; // 如果+-号前面没有E e,false
}
}
else if (char_arr[i] == '.') {
if (existPoint) return false; // 小数点出现两次,false
if (existE) return false; // 小数点在E e之前出现,false
if (i == 0 || i == len - 1) return false; // 小数点在头尾出现,false
existPoint = true;
}
else if (char_arr[i] <= '9' && char_arr[i] >= '0') {
existNum = true;
}
else {
return false;
}
}
return existNum;
}
}
/ 正则表达式
// 牛客
// 运行时间:14ms
// 占用内存:9892k
// [] : 字符集合
// () : 分组
// ? : 重复 0 ~ 1 次
// + : 重复 1 ~ n 次
// * : 重复 0 ~ n 次
// . : 任意字符
// \\. : 转义后的 .
// \\d : 数字
public class Solution {
public boolean isNumeric(char[] str) {
if (str == null || str.length == 0)
return false;
return new String(str).matches("[+-]?\\d*(\\.\\d+)?([eE][+-]?\\d+)?");
}
}
【剑指offer】37. 序列化二叉树
题目描述
// 37. 序列化二叉树
// 请实现两个函数,分别用来序列化和反序列化二叉树。
// 你可以将以下二叉树:
// 1
// / \
// 2 3
// / \
// 4 5
// 序列化为 "[1,2,3,null,null,4,5]"
// 牛客
// 请实现两个函数,分别用来序列化和反序列化二叉树
// 二叉树的序列化是指:把一棵二叉树按照某种遍历方式的结果以某种格
// 式保存为字符串,从而使得内存中建立
// 起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序
// 的二叉树遍历方式来进行修改,序列化的结果是一个字符串,序列化时
// 通过 某种符号表示空节点(#),以 ! 表示一个结点值的结束(valu
// e!)。
// 二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果str
// ,重构二叉树。
// 例如,我们可以把一个只有根节点为1的二叉树序列化为"1,",然后通过自己
// 的函数来解析回这个二叉树
题解
// 这题很难啊,
// 序列化是为了能够完整地保存树的结构信息,这个信息最后可以为
// 反序列化而服务,即根据这个序列化信息完整的还原这个二叉树。
// 按照题意,序列化使用层序遍历的同时需要将null也保存下来。
// 力扣
// 执行用时:23 ms, 在所有 Java 提交中击败了62.32%的用户
// 内存消耗:40.7 MB, 在所有 Java 提交中击败了47.02%的用户
import java.util.LinkedList;
public class Codec {
// 序列化
public String serialize(TreeNode root) {
if (root == null) // 若树为null直接返回"[]"
return "[]";
// 创建StringBuilder用于存string结构信息
StringBuilder res = new StringBuilder();
// 创建层序遍历要用的队列(用LinkedList模拟)
LinkedList<TreeNode> q = new LinkedList<>();
res.append("[");
q.add(root);
while(!q.isEmpty()) {
TreeNode node = q.remove(); // 结点出队即为node
// 如果遍历当前的node非空,将node.val和逗号","存入res
// 并将node的左右子结点入队(不管是不是null,因为null我们也要记录)
if (node != null) {
res.append(node.val + ",");
q.add(node.left);
q.add(node.right);
}
else
res.append("null,"); // 如果是null,把"null,"存入
}
// 存完res末尾会多一个逗号,删掉
res.deleteCharAt(res.length() - 1);
res.append("]");
// System.out.println(res.toString());
return res.toString(); // 转String
}
// 反序列化
public TreeNode deserialize(String data) {
if (data.equals("[]")) // 如果string是"[]",说明树是null
return null;
// 先掐头去尾把中括号"[]"去掉,然后将data中的字符按照逗号分隔
// 得到字符组vals
String[] vals = data.substring(1, data.length() - 1).split(",");
// parseInt将字符串参数解析为有符号的十进制数,即为结点值,
// 用结点值新建结点作为遍历的第一个点
TreeNode res = new TreeNode(Integer.parseInt(vals[0]));
// 初始化队列q,用于构建二叉树,构建过程类似层序遍历
LinkedList<TreeNode> q = new LinkedList<>();
q.add(res);
int i = 1; // 遍历字符组vals的索引指针i
// 若q为空(vals中的有效结点遍历完),循环结束
while (!q.isEmpty()) {
TreeNode node = q.remove(); // 结点出队
// 如果vals[i]元素不是null
if (!vals[i].equals("null")) {
// 解析结点值新建结点,使node.left指向该结点
node.left = new TreeNode(Integer.parseInt(vals[i]));
q.add(node.left); // 入队
}
i++; // 指针右移
// 如果下一个vals[i]元素不是null
if (!vals[i].equals("null")) {
// 同上
node.right = new TreeNode(Integer.parseInt(vals[i]));
q.add(node.right);
}
i++; // 指针右移
}
return res; // 最后返回树
}
}
// 牛客
// 运行时间:18ms
// 占用内存:10032k
import java.util.LinkedList;
public class Solution {
String Serialize(TreeNode root) {
if (root == null)
return "[]";
StringBuilder res = new StringBuilder();
LinkedList<TreeNode> q = new LinkedList<>();
res.append("[");
q.add(root);
while (!q.isEmpty()) {
TreeNode node = q.remove();
if (node != null) {
res.append(node.val + ",");
q.add(node.left);
q.add(node.right);
}
else
res.append("null,");
}
res.append("]");
return res.toString();
}
TreeNode Deserialize(String str) {
if (str.equals("[]"))
return null;
String[] vals = str.substring(1, str.length() - 1).split(",");
LinkedList<TreeNode> q = new LinkedList<>();
TreeNode res = new TreeNode(Integer.parseInt(vals[0]));
q.add(res);
int i = 1;
while (!q.isEmpty()) {
TreeNode node = q.remove();
if (!vals[i].equals("null")) {
node.left = new TreeNode(Integer.parseInt(vals[i]));
q.add(node.left);
}
i++;
if (!vals[i].equals("null")) {
node.right = new TreeNode(Integer.parseInt(vals[i]));
q.add(node.right);
}
i++;
}
return res;
}
}
【剑指offer】50. 第一个只出现一次的字符
题目描述
// 力扣
// 在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个
// 单空格。 s 只包含小写字母。
// 牛客
// 在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个
// 只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大
// 小写).(从0开始计数)
题解
/ 出现次数记录 /
// 力扣
// 执行用时:37 ms, 在所有 Java 提交中击败了16.73%的用户
// 内存消耗:38.9 MB, 在所有 Java 提交中击败了30.90%的用户
import java.util.HashMap;
class Solution {
public char firstUniqChar(String s) {
char[] strs = s.toCharArray();
// 构建HashMap用于存储
// key:strs中出现的字符,value:字符出现次数
HashMap<Character, Integer> map = new HashMap<Character, Integer>();
for (char str: strs) {
if (!map.containsKey(str))
map.put(str, 1); // 没出现过,置1
else
map.put(str, map.get(str)+1); // 出现过,value+1
}
for (char key: strs) {
if (map.get(key) == 1) // 按照strs的顺序,逐个检查map对应的value
return key; // 第一个出现1的,返回字符
}
return ' ';
}
}
// 牛客
// 运行时间:25ms,超过77.83%用Java提交的代码
// 占用内存:10112KB,超过9.32%用Java提交的代码
import java.util.HashMap;
public class Solution {
public int FirstNotRepeatingChar(String str) {
HashMap<Character, Integer> map = new HashMap<>();
char[] strs = str.toCharArray();
for (char s: strs) {
if (!map.containsKey(s))
map.put(s, 1);
else {
map.put(s, map.get(s) + 1);
}
}
for (int i = 0; i < strs.length; i++) {
if (map.get(strs[i]) == 1)
return i;
}
return -1;
}
}
// 出现状态记录 ///
// 力扣
import java.util.HashMap;
class Solution {
public char firstUniqChar(String s) {
// 将s转为字符组char[] strs
char[] strs = s.toCharArray();
// 构建HashMap用于存储
// key:strs中出现的字符,value:字符是否出现次数小于2次(是则true)
HashMap<Character, Boolean> map = new HashMap<Character, Boolean>();
for (char str: strs) {
if (!map.containsKey(str)) // 如果遍历字符str是第1次出现
map.put(str, true); // 将str对应的value置为true
else
map.put(str, false); // 如果出现次数大于等于2次,false
}
for (char key: strs) {
if (map.get(key)) // 按照strs的顺序,逐个检查map对应的value
return key; // 第一个出现true的,返回字符
}
return ' ';
}
}
// 牛客
// 运行时间:28ms,超过69.39%用Java提交的代码
// 占用内存:10116KB,超过9.24%用Java提交的代码
import java.util.HashMap;
public class Solution {
public int FirstNotRepeatingChar(String str) {
HashMap<Character, Boolean> map = new HashMap<Character, Boolean>();
char[] strs = str.toCharArray();
for (char s: strs) {
if (!map.containsKey(s))
map.put(s, true);
else {
map.put(s, false);
}
}
for (int i = 0; i < strs.length; i++) {
if (map.get(strs[i]))
return i;
}
return -1;
}
}
【剑指offer】58. 翻转单词顺序
题目描述
// 58. 翻转单词顺序
// 力扣
// 输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。
// 为简单起见,标点符号和普通字母一样处理。例如输入字符串"I am a st
// udent. ",则输出"student. a am I"。
// 牛客
// 牛客最近来了一个新员工Fish,每天早晨总是会拿着一本英文杂志,写些句
// 子在本子上。同事Cat对Fish写的内容颇感兴趣,有一天他向Fish借来翻看
// ,但却读不懂它的意思。例如,“student. a am I”。后来才意识到,这家伙
// 原来把句子单词的顺序翻转了,正确的句子应该是“I am a student.”。Cat
// 对一一的翻转这些单词顺序可不在行,你能帮助他么?
题解
// 力扣
// 力扣条件比牛客宽松,不需要考虑s=" "这种东西,遇到空格直接消除就行,
// 构建StringBuilder res保存答案。for循环顺序遍历s.split(" ")字符组的元素,
// 记为str,如果得到的str为" "或者"",直接continue跳过。
// 如果str是正常字符,使用insert插入到res的队首,然后再插入空格" ",
// 如此循环,每次顺序遍历的字符都被从队首插入,这样整个字符串都会变成
// 相反顺序。最后返回trim()修剪空格的res即可。
// 执行用时:3 ms, 在所有 Java 提交中击败了68.19%的用户
// 内存消耗:38.6 MB, 在所有 Java 提交中击败了18.18%的用户
class Solution {
public String reverseWords(String s) {
StringBuilder res = new StringBuilder();
for (String str : s.split(" ")) {
if (str.equals(" ") || str.equals(""))
continue;
else {
res.insert(0, str);
res.insert(0, " ");
}
}
return res.toString().trim();
}
}
// 牛客
// 牛客与力扣一样,但是多了一个特殊情况判定,特别是如果出现了s=" "这种东西,
// 需要直接返回s,不能把空格删掉了。
// 运行时间:15ms,超过75.79%用Java提交的代码
// 占用内存:10064KB,超过98.42%用Java提交的代码
public class Solution {
public String ReverseSentence(String s) {
if(s == null || s.length() == 0 || s.trim().length() == 0)
return s;
StringBuilder res = new StringBuilder();
for (String str : s.split(" ")) {
if (str.equals(" ") || str.equals(""))
continue;
else {
res.insert(0, str);
res.insert(0, " ");
}
}
return res.toString().trim();
}
}
// 整词处理 - 倒插法
// 力扣
// 执行用时:4 ms, 在所有 Java 提交中击败了43.47%的用户
// 内存消耗:38.4 MB, 在所有 Java 提交中击败了35.34%的用户
class Solution {
public String reverseWords(String s) {
s = s.trim();
if (s.length() == 0)
return "";
String[] strs = s.split(" ");
StringBuilder res = new StringBuilder();
for (int i = 0; i < strs.length; i++) {
String str = strs[i].trim();
if (str.equals(" ") || str.length() == 0) continue;
StringBuilder temp = new StringBuilder(str);
res.insert(0, temp.toString() + " ");
}
res.delete(res.length() - 1, res.length());
return res.toString();
}
}
// 整词处理 - 翻转法
// 力扣
// 执行用时:4 ms, 在所有 Java 提交中击败了43.47%的用户
// 内存消耗:38.4 MB, 在所有 Java 提交中击败了35.34的用户
class Solution {
public String reverseWords(String s) {
s = s.trim();
if (s.length() == 0)
return "";
String[] strs = s.split(" ");
StringBuilder res = new StringBuilder();
for (int i = 0; i < strs.length; i++) {
String str = strs[i].trim();
if (str.equals(" ") || str.length() == 0) continue;
StringBuilder temp = new StringBuilder(str);
temp.reverse();
res.append(temp.toString() + " ");
}
res.delete(res.length() - 1, res.length());
return res.reverse().toString();
}
}
【剑指offer】58.2 左旋转字符串
题目描述
// 58.2 左旋转字符串
// 力扣
// 字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。
// 请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcde
// fg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。
// 牛客
// 汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,
// 就是用字符串模拟这个指令的运算结果。对于一个给定的字符序列S,请你
// 把其循环左移K位后的序列输出。例如,字符序列S=”abcXYZdef”,要求输出
// 循环左移3位后的结果,即“XYZdefabc”。是不是很简单?OK,搞定它!
题解
StringBuilder辅助法
/// StringBuilder辅助法
// 使用StringBuilder会让这题变得异常简单
// 力扣
// 就是字面意思,以n为分界,n以前的元素移动到后面
// 执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
// 内存消耗:38.4 MB, 在所有 Java 提交中击败了19.52%的用户
class Solution {
public String reverseLeftWords(String s, int n) {
StringBuilder res = new StringBuilder();
String a = s.substring(0, n);
String b = s.substring(n, s.length());
res.append(b).append(a);
return res.toString();
}
}
class Solution {
public String reverseLeftWords(String s, int n) {
StringBuilder res = new StringBuilder();
res.append(s.substring(n, s.length()));
res.append(s.substring(0, n));
return res.toString();
}
}
// 牛客
// 跟力扣一样,但是多了一个特殊情况排查
// 运行时间:12ms,超过89.64%用Java提交的代码
// 占用内存:9744KB,超过8.61%用Java提交的代码
public class Solution {
public String LeftRotateString(String s,int n) {
if (n <= 0 || n >= s.length())
return s;
StringBuilder res = new StringBuilder();
String a = s.substring(0, n);
String b = s.substring(n, s.length());
res.append(b).append(a);
return res.toString();
}
}
字符操作法
/// 字符操作法 /
// 力扣
// 以n为界,翻转前半部字符顺序,再翻转后半部字符顺序,
// 最后整个字符组翻转顺序,返回即可。
// 执行用时:2 ms, 在所有 Java 提交中击败了41.86%的用户
// 内存消耗:38.4 MB, 在所有 Java 提交中击败了30.67%的用户
class Solution {
public String reverseLeftWords(String s, int n) {
if (n <= 0 || n >= s.length())
return s;
char[] chars = s.toCharArray();
reverse(chars, 0, n - 1);
reverse(chars, n, s.length() - 1);
reverse(chars, 0, s.length() - 1);
return new String(chars);
}
// 翻转函数reverse
// 左右双指针left right分别指向字符组char[] chars的头尾,
// 左右指针索引的元素调用交换位置函数swap,之后左指针右移left++
// 右指针左移right--,重复交换操作,可以把chars翻转。
private void reverse(char[] chars, int left, int right) {
while (left < right)
swap(chars, left++, right--);
}
// 交换位置函数
private void swap(char[] chars, int i, int j) {
char temp = chars[i];
chars[i] = chars[j];
chars[j] = temp;
}
}
// 牛客
// 运行时间:12ms超过89.64%用Java提交的代码
// 占用内存:9820KB超过5.40%用Java提交的代码
public class Solution {
public String LeftRotateString(String s,int n) {
if (n >= s.length() || n <= 0)
return s;
char[] chars = s.toCharArray();
reverse(chars, 0, n - 1);
reverse(chars, n, s.length() - 1);
reverse(chars, 0, s.length() - 1);
return new String(chars);
}
private void reverse(char[] chars, int left, int right) {
while (left < right)
swap(chars, left++, right--);
}
private void swap(char[] chars, int i, int j) {
char temp = chars[i];
chars[i] = chars[j];
chars[j] = temp;
}
}
【剑指offer】67. 把字符串转换成整数
题目描述
// 牛客
// 将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数。
// 数值为0或者字符串不是一个合法的数值则返回0
题解
// 牛客
// 牛客比力扣要简单(也更合理),出现其他字符统统不算int,直接返回0。
// 先排除特殊情况,初始化答案res=0。定义boolean值isNegative,
// 判断第一个字符是不是'-',最后返回答案时判断要不要负号。
// for循环遍历str字符,如果第一个字符是符号,跳过。
// 如果c小于0或大于9,直接返回0.
// 否则将当前c的值加到res,res之前的值乘10进位。遍历完,
// 字符回归它应该在的位数上。最后返回即可
// 运行时间:9ms,超过93.78%用Java提交的代码
// 占用内存:9656KB,超过70.77%用Java提交的代码
class Solution {
public int strToInt(String str) {
if (str == null || str.length() == 0)
return 0;
int res = 0;
boolean isNegative = str.charAt(0) == '-';
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (i == 0 && (c == '+' || c == '-'))
continue;
if (c < '0' || c > '9')
return 0;
res = res * 10 + (c - '0');
}
return isNegative ? -res : res;
}
}
特殊解——动态规划
【剑指offer】19. 正则表达式匹配
题目
// 牛客
// 请实现一个函数用来匹配包含'. '和'*'的正则表达式。模式中
// 的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现
// 任意次(含0次)。在本题中,匹配是指字符串的所有字符匹配整
// 个模式。例如,字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,但与
// "aa.a"和"ab*a"均不匹配。
// 力扣
// 请实现一个函数用来匹配包括'.'和'*'的正则表达式。模式中的字符
// '.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(包
// 含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例
// 如,字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,但是与"aa.a"和"ab*a"
// 均不匹配
题解
/// 递归法 //
// 牛客
// 运行时间:13ms
// 占用内存:9728k
public class Solution {
public boolean match(char[] str, char[] pattern) {
if (str == null || pattern == null)
return false;
int s = 0; // 初始化str的遍历指针s
int p = 0; // 初始化pattern的遍历指针p
// 匹配函数,匹配成功返回true
return matchFunc(str, s, pattern, p);
}
public boolean matchFunc(char[] str, int s, char[] pattern, int p) {
// 如果一起到达str和pattern的尾部,匹配成功,返回true
if (s == str.length && p == pattern.length)
return true;
// 如果pattern遍历完了(匹配符用完了)str没遍历完,返回false
if (s != str.length && p == pattern.length)
return false;
// 如果遍历指针p的下一个是'*'(根据'*'的定义,默认'*'不可能在第一个出现)
// 且p不超过遍历范围。那么有'*'的情况是最复杂的,这个题目也是主要解决这个问题。
if (p + 1 < pattern.length && pattern[p + 1] == '*') {
// 如果p和s遍历字符相匹配(不管是字符相同的匹配还是有万能符'.'的匹配)
// 此时有三种情况
if ((s < str.length && str[s] == pattern[p]) || (s < str.length && pattern[p] == '.')) {
// 第一种情况是:p当前遍历匹配了0个字符(即使p和s当前遍历字符相匹配,pattern[p]
// 和后面的'*'也有可能代表匹配了0个字符,而把str[s]交给后面字符来匹配)
// 第二种情况是:pattern[p]和'*'匹配了str的一个字符,此时s右移一次,
// p右移两次,跨过'*'。
// 第三种情况是:pattern[p]和'*'匹配了str的两个字符(或以上,但这个“以上”要留给下一个递归来判断)
// 所以此时s右移一次,p不动
// 取三种递归的逻辑与,只要其中一种情况是true就行。
return matchFunc(str, s, pattern, p + 2)
|| matchFunc(str, s + 1, pattern, p + 2)
|| matchFunc(str, s + 1, pattern, p);
}
// 如果p和s遍历字符不匹配,由于'*'可以代表前面字符出现0次
// 所以不能直接返回false,还要继续往下判断。因此p右移两格继续判断
// 所以这里跟if中返回的matchFunc(str, s, pattern, p + 2)是一样的
else {
return matchFunc(str, s, pattern, p + 2);
}
}
// 如果遍历指针p的下一个字符不是'*'
else {
// 若满足:(s没超过遍历范围,且s遍历值等于p遍历值),或者
// 满足:(s没超过遍历范围,且p遍历值此时是万能匹配符'.')
// 所以这里跟下一个是'*'情况中的第一个if是一样的。
if ((s < str.length && str[s] == pattern[p]) || (s < str.length && pattern[p] == '.')) {
// 递归调用matchFunc,s p指针一起右移一位。
return matchFunc(str, s + 1, pattern, p + 1);
}
}
return false; // 其他情况返回false
}
}
// 力扣
// 执行用时:879 ms, 在所有 Java 提交中击败了5.04%的用户
// 内存消耗:36.8 MB, 在所有 Java 提交中击败了96.62%的用户
class Solution {
public boolean isMatch(String string_s, String string_p) {
if (string_s == null || string_p == null)
return false;
char[] str = string_s.toCharArray();
char[] pattern = string_p.toCharArray();
int s = 0;
int p = 0;
return matchFunc(str, s, pattern, p);
}
public boolean matchFunc(char[] str, int s, char[] pattern, int p) {
if (s == str.length && p == pattern.length)
return true;
if (s < str.length && p == pattern.length)
return false;
// 如果pattern下一个是'*'
if (p + 1 < pattern.length && pattern[p + 1] == '*') {
// 且当前p和s遍历值匹配(不管是字符匹配还是万能符'.'匹配)
if ((s < str.length && str[s] == pattern[p]) || (s < str.length && pattern[p] == '.')) {
return matchFunc(str, s, pattern, p + 2)
|| matchFunc(str, s + 1, pattern, p + 2)
|| matchFunc(str, s + 1, pattern, p);
}
else {
return matchFunc(str, s, pattern, p + 2);
}
}
else {
if ((s < str.length && str[s] == pattern[p]) || s < str.length && pattern[p] == '.')
return matchFunc(str, s + 1, pattern, p + 1);
}
return false;
}
}
/// 动态规划 //
// 在斐波那契数列问题中,动态规划主要在一维数组中进行
// 数组元素中后一个元素状态,主要取决于前一个元素或前一些元素的状态
// 本题的矩阵形式动态规划,产生的主要原因是由于需要比对的对象不只有一个
// 而是有了两个(甚至两个以上),即需要确定的元素的状态,不仅取决于当前
// 数组A的元素状态,还取决于另外一个数组B的元素状态。
// 因此将一个数组A中的元素作为横轴,另一个数组B中元素作为竖轴,就需要在矩阵形式dp。
// 假设str长度为slen,pattern长度为plen,i表示str的遍历索引,
// j表示pattern的遍历索引,题目的子问题其实就是看str[:i]和pattern[:j]是否匹配,
// 而dp[i+1][j+1]就是这个意思,假设str表示纵轴,pattern表示横轴
// dp[i+1][j+1]==true则为匹配,也就 表示遍历str索引为i的元素,
// 与遍历pattern索引为j的元素是匹配的。
// 而dp[0][0]初始化为true,是表示str和pattern为空时也能匹配。
// 牛客
// 运行时间:11ms
// 占用内存:9680k
public class Solution {
public boolean match(char[] str, char[] pattern) {
int slen = str.length;
int plen = pattern.length;
// 定义dp动态规划矩阵,dp[i][j]表示的是p的前j个字符和s的前i个字符匹配的结果
boolean[][] dp = new boolean[slen + 1][plen + 1];
dp[0][0] = true; // dp[0][0]初始化为true
// 初始化第一行,首行str为空字符串
// 因此当pattern的偶数位为'*'时才能够匹配,这时'*'看做出现0次
for (int i = 1; i <= plen; i++) {
// 默认pattern第一个元素不会是'*',所以不用考虑超出边界
if (pattern[i - 1] == '*')
dp[0][i] = dp[0][i - 2];
}
// 从第1行第1列开始,从左到右,从上到下遍历矩阵每个位置
// 需要注意,遍历位置索引分别为i-1和j-1(而不是i j)
for (int i = 1; i <= slen; i++) { // 遍历str,索引记为i
for (int j = 1; j <= plen; j++) { // 遍历pattern,索引记为j
// 若遍历位置i-1和j-1的元素相匹配(不管是字符匹配还是万能符'.'匹配)
if (str[i - 1] == pattern[j - 1] || pattern[j - 1] == '.') {
dp[i][j] = dp[i - 1][j - 1]; // i和j都直接往下遍历一位
}
// 若j-1遍历元素为'*'
else if (pattern[j - 1] == '*') {
// 则如果j-2(j-1的上一个)元素与当前i-1元素匹配(不管
// 是字符匹配还是万能符'.'匹配)
if (pattern[j - 2] == str[i - 1] || pattern[j - 2] == '.') {
// A |= B为 A = A || B, 则下一个状态dp[i][j]为dp[i][j - 1],
// dp[i - 1][j]和dp[i][j - 2]这三个状态的逻辑与
dp[i][j] |= dp[i][j - 1]; // 表示匹配1个
dp[i][j] |= dp[i - 1][j]; // 表示匹配2个
dp[i][j] |= dp[i][j - 2]; // 表示匹配0个
}
else { // 如果j-2元素不匹配,则下一状态等于匹配0个的情况
dp[i][j] = dp[i][j - 2];
}
}
}
}
return dp[slen][plen]; // 最后返回最右下角状态
}
}
// 力扣
class Solution {
public boolean isMatch(String string_s, String string_p) {
if (string_s == null || string_p == null)
return false;
char[] str = string_s.toCharArray();
char[] pattern = string_p.toCharArray();
int slen = str.length;
int plen = pattern.length;
// 定义dp动态规划矩阵,dp[i][j]表示的是p的前j个字符和s的前i个字符匹配的结果
boolean[][] dp = new boolean[slen + 1][plen + 1];
dp[0][0] = true; // dp[0][0]初始化为true
for (int i = 1; i <= plen; i++) {
if (pattern[i - 1] == '*')
dp[0][i] = dp[0][i - 2];
}
for (int i = 1; i <= slen; i++) {
for (int j = 1; j <= plen; j++) {
// System.out.println(dpToString(dp));
if (str[i - 1] == pattern[j - 1] || pattern[j - 1] == '.') {
dp[i][j] = dp[i - 1][j - 1];
}
else if (pattern[j - 1] == '*') {
if (pattern[j - 2] == str[i - 1] || pattern[j - 2] == '.') {
dp[i][j] |= dp[i][j - 1];
dp[i][j] |= dp[i - 1][j];
dp[i][j] |= dp[i][j - 2];
}
else {
dp[i][j] = dp[i][j - 2];
}
}
}
}
return dp[slen][plen];
}
// 打印dpToString的toString函数(不是必须)
public String dpToString(boolean[][] dp) {
StringBuilder res = new StringBuilder();
for (int i = 0; i < dp.length; i++) {
res.append("[");
for (int j = 0; j < dp[0].length; j++) {
if (dp[i][j])
res.append(" true ");
else
res.append(" false ");
}
res.append("]");
res.append("\r\n");
}
return res.toString();
}
}
特殊解——回溯搜索
【剑指offer】38. 字符串的排列
题目描述
// 力扣
// 输入一个字符串,打印出该字符串中字符的所有排列。
// 你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。
// 牛客
// 输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输
// 入字符串abc,则按字典序打印出由字符a,b,c所能排列出来的所有字符
// 串abc,acb,bac,bca,cab和cba。
题解
// 乍一看是递归回溯,递归的时候不需要更新遍历起点start,
// 每次遍历都从头开始遍历。但是每次从头遍历一定会遇到重复字符,我们
// 定义一个boolean[] 记录被使用过的字符,避免重复。我们有:
import java.util.ArrayList;
public class Solution {
ArrayList<String> res = new ArrayList<>();
public ArrayList<String> Permutation(String str) {
if (str.length() == 0)
return res;
StringBuilder temp = new StringBuilder();
boolean[] used = new boolean[str.length()];
backtracking(str, used, temp);
return res;
}
private void backtracking(String str, boolean[] used, StringBuilder temp) {
if (temp.length() == str.length()) {
res.add(temp.toString());
return;
}
for (int i = 0; i < str.length(); i++) {
if (used[i])
continue;
temp.append(str.charAt(i));
used[i] = true;
backtracking(str, used, temp);
temp.delete(temp.length() - 1, temp.length());
used[i] = false;
}
}
}
// 跑出来发现通不过,像str="aa"这种字符就无法通过,我们的算法会输出["aa", "aa"],
// 这样的结果。但是系统想要的是["aa"],我们必须考虑str本身包含重复结点的情况。
// 因此需要将str中的字符存入char[]中,然后排序,排序好之后,
// 相同字符会分布到一起。这样就好处理了。
/// 递归回溯法
// 牛客
// 运行时间:73ms
// 占用内存:12272k
import java.util.ArrayList;
public class Solution {
// 定义ArrayList作答案存储字符组
ArrayList<String> res = new ArrayList<>();
public ArrayList<String> Permutation(String str) {
if (str.length() == 0) // 如果输入是空,直接返回res
return res;
char[] char_array = str.toCharArray(); // str转为char[]
// 定义boolean[]用于记录某字符是否被使用过
boolean[] used = new boolean[str.length()];
// 定义StringBuilder temp来存储当前字符的组成
StringBuilder temp = new StringBuilder();
// 调用递归函数
backtrack(char_array, used, temp);
return res;
}
// 字符组形式的char_array用于字母元素遍历
// bool值组的used用于记录对应索引上char_array元素是否用过
// StringBuilder的temp用于保存当前组合出来的字符
private void backtrack(char[] char_array, boolean[] used, StringBuilder temp) {
// 如果匹配出的字符temp长度与char_array长度相同,说明字符匹配完成
// 返回(递归回溯)
if (temp.length() == char_array.length) {
res.add(temp.toString());
return;
}
// for循环遍历char_array中的字符(可用于匹配的元素)、
// 遍历索引为i
for (int i = 0; i < char_array.length; i++) {
// 如果当前元素用过,当前for循环跳过,进入下一次for循环
if (used[i])
continue;
// 如果遍历字符非首字符,且当前遍历字符与前一字符相等
// 且前一字符未被用过(或用过后已回溯,被重新标记未使用)
// ,当前for循环跳过,进入下一次for循环
if (i != 0 && char_array[i] == char_array[i - 1] && !used[i - 1])
continue;
used[i] = true; // 标记i索引位字符已使用
temp.append(char_array[i]); // 如果字符符合条件,append进去
// 递归调用,继续排列temp字符组合,直到排出的字符串长度达到上限
backtrack(char_array, used, temp);
// 递归出来,删除刚刚已经使用过的字符(下一个for使用下一个字符)
temp.deleteCharAt(temp.length() - 1);
used[i] = false; // 回溯后,i索引位字符标记为未使用
}
}
}
// 力扣
// 执行用时:11 ms, 在所有 Java 提交中击败了64.52%的用户
// 内存消耗:42.8 MB, 在所有 Java 提交中击败了77.37%的用户
import java.util.ArrayList;
class Solution {
ArrayList<String> res = new ArrayList<>();
public String[] permutation(String s) {
if (s.length() == 0)
return new String[0];
char[] char_array = s.toCharArray();
boolean[] used = new boolean[s.length()];
StringBuilder temp = new StringBuilder();
Arrays.sort(char_array); // 力扣一定要加升序,不然过不了!!!
backtrack(char_array, used, temp);
String[] result = (String[]) res.toArray(new String[res.size]);
return result;
}
private void backtrack(char[] char_array, boolean[] used, StringBuilder temp) {
if (temp.length() == char_array.length) {
res.add(temp.toString());
return;
}
for (int i = 0; i < char_array.length; i++) {
if (used[i])
continue;
if (i != 0 && char_array[i] == char_array[i - 1] && !used[i - 1])
continue;
used[i] = true;
temp.append(char_array[i]);
backtrack(char_array, used, temp);
temp.deleteCharAt(temp.length() - 1);
used[i] = false;
}
}
}
特殊解——双指针
【剑指offer】48. 最长不含重复字符的子字符串
题目描述
// 48. 最长不含重复字符的子字符串
// 力扣
// 请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长
// 子字符串的长度。
题解
双指针滑窗法 + HashSet /
// 力扣
// 左右指针构成滑动窗口,并用一个HashSet维护已被遍历过的字符
// 执行用时:10 ms, 在所有 Java 提交中击败了20.89%的用户
// 内存消耗:38.6 MB, 在所有 Java 提交中击败了34.39%的用户
import java.util.HashSet;
class Solution {
public int lengthOfLongestSubstring(String s) {
int res = 0; // 答案存储
int right = 0, left = 0; // 初始化右指针right,左指针left
// HashSet标记以遍历过的字符,如果字符出现过,会被存进set
HashSet<Character> set = new HashSet<>();
while (right < s.length()) {
char c = s.charAt(right); // 提取当前右指针所在字符c
while (set.contains(c)) // 如果c已经出现过
// 左指针准备右移,先去掉左指针字符在set中的备份,
// 说明该字符不再标记为已被使用,之后左指针右移
set.remove(s.charAt(left++));
// 右指针将当前遍历字符存入set,表示该字符已被使用
set.add(s.charAt(right));
right++; // 然后右指针right右移
res = Math.max(res, right - left); // 窗口长度和res中较大者更新为res
}
return res;
}
}
// 执行用时:9 ms, 在所有 Java 提交中击败了30.63%的用户
// 内存消耗:38.2 MB, 在所有 Java 提交中击败了97.32%的用户
import java.util.HashSet;
class Solution {
public int lengthOfLongestSubstring(String s) {
if (s == null || s.length() == 0)
return 0;
HashSet<Character> set = new HashSet<>();
int left = 0, right = 0;
int res = 0;
while (right < s.length()) {
if (set.contains(s.charAt(right))) {
set.remove(s.charAt(left++));
}
else {
set.add(s.charAt(right));
res = Math.max(res, right - left + 1);
right++;
}
}
return res;
}
}
// 简化一下
class Solution {
public int lengthOfLongestSubstring(String s) {
char[] strs = s.toCharArray();
if (strs.length == 0)
return 0;
HashSet<Character> set = new HashSet<>();
int res = 0;
int left = 0, right = 0;
while (right < strs.length) {
while (left < right && set.contains(strs[right])) {
set.remove(strs[left++]);
}
if (!set.contains(strs[right])) {
set.add(strs[right++]);
res = Math.max(res, right - left);
}
}
return res;
}
}
// 力扣
// 布尔数组记录元素已被使用
// 执行用时:2 ms, 在所有 Java 提交中击败了100.00%的用户
// 内存消耗:38.5 MB, 在所有 Java 提交中击败了52.88%的用户
class Solution {
public int lengthOfLongestSubstring(String s) {
if (s.length() == 0)
return 0;
char[] chars = s.toCharArray(); // s转为字符数组
boolean[] used = new boolean[128]; // 布尔数组used记录元素已被使用
int left = -1, right = 0, length = chars.length;
int res = 1; // 答案保存
// 右指针往右遍历
while (right < length) {
if (!used[chars[right]]) { // 如果right遍历元素未被使用
used[chars[right]] = true; // used记录right当前位置已被使用
right++; // right右移
}
else { // 如果right遍历元素已被使用
// 当left右移至right遍历位置,将使用标记转false后,循环停止
while(used[chars[right]]) {
left++; // left右移
// 第一次whilw循环时,因为right和left为相同元素,
// 所以left左移一次后,窗口长度才为不重复字符长度,
// 取窗口长度和res最大者更新res
res = Math.max(res, right-left);
used[chars[left]] = false; // left右移后遍历位标记转false
}
}
}
// 最后返回res和right-left-1最大者
return Math.max(res, right-left-1);
}
}
// 简化一下
// 执行用时:2 ms, 在所有 Java 提交中击败了100.00%的用户
// 内存消耗:38.6 MB, 在所有 Java 提交中击败了48.11%的用户
class Solution {
public int lengthOfLongestSubstring(String s) {
char[] strs = s.toCharArray();
if (strs.length == 0)
return 0;
boolean[] used = new boolean[128];
int res = 0;
int left = 0, right = 0;
while (right < strs.length) {
while (left < right && used[strs[right]]) {
used[strs[left++]] = false;
}
if (!used[strs[right]]) {
used[strs[right++]] = true;
res = Math.max(res, right - left);
}
}
return res;
}
}