【算法题型总结】--5、字符串【欢迎留言补充其他题目】
一、目录
- 反转字符串
- 反转字符串Ⅱ
- 替换空格
- 翻转字符串里的单词
- 左旋转字符串
- 实现strStr()/indexOf()字符串位置寻找
- 重复的子串
- 反转字符串
- NC17 最长回文子串
- NC1 大数加法
- NC55 最长公共前缀
- NC52 括号序列
- NC149 kmp算法
- NC28 最小覆盖子串
- NC49 最长的括号子串
- NC31 第一个只出现一次的字符
- NC100 将字符串转化为整数
- NC121 字符串的排列
- NC113 验证IP地址
- NC104 比较版本号
- NC142 最长重复子串
- NC20 数字字符串转化成IP地址
- NC10 大数乘法
二、题目
1、反转字符串
解法:双指针,无需考虑n-i+1
class Solution {
public void reverseString(char[] s) {
int l = 0;
int r = s.length - 1;
while (l < r) {
s[l] ^= s[r]; //构造 a ^ b 的结果,并放在 a 中
s[r] ^= s[l]; //将 a ^ b 这一结果再 ^ b ,存入b中,此时 b = a, a = a ^ b
s[l] ^= s[r]; //a ^ b 的结果再 ^ a ,存入 a 中,此时 b = a, a = b 完成交换
l++;
r--;
}
}
}
2、反转字符串Ⅱ
给定一个字符串 s 和一个整数 k,你需要对从字符串开头算起的每隔 2k 个字符的前 k 个字符进行反转。
如果剩余字符少于 k 个,则将剩余字符全部反转。
如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。
示例:
输入: s = "abcdefg", k = 2
输出: "bacdfeg"
解法:移动i+=(2*k)
//题目的意思其实概括为 每隔2k个反转前k个,尾数不够k个时候全部反转
class Solution {
public String reverseStr(String s, int k) {
char[] ch = s.toCharArray();
for(int i = 0; i < ch.length; i += 2 * k){
int start = i;
//这里是判断尾数够不够k个来取决end指针的位置
int end = Math.min(ch.length - 1, start + k - 1);
//用异或运算反转
while(start < end){
ch[start] ^= ch[end];
ch[end] ^= ch[start];
ch[start] ^= ch[end];
start++;
end--;
}
}
return new String(ch);
}
}
3、替换空格
把空格替换成%20
示例 1: 输入:s = "We are happy."
输出:"We%20are%20happy."
解法:
首先扩充数组到每个空格替换成"%20"之后的大小。
然后从后向前替换空格,也就是双指针法,过程如下:
i指向新长度的末尾,j指向旧长度的末尾。
//使用一个新的对象,复制 str,复制的过程对其判断,是空格则替换,否则直接复制,类似于数组复制
public static String replaceSpace(StringBuffer str) {
if (str == null) {
return null;
}
//选用 StringBuilder 单线程使用,比较快,选不选都行
StringBuilder sb = new StringBuilder();
//使用 sb 逐个复制 str ,碰到空格则替换,否则直接复制
for (int i = 0; i < str.length(); i++) {
//str.charAt(i) 为 char 类型,为了比较需要将其转为和 " " 相同的字符串类型
if (" ".equals(String.valueOf(str.charAt(i)))){
sb.append("%20");
} else {
sb.append(str.charAt(i));
}
}
return sb.toString();
}
给定一个字符串,逐个翻转字符串中的每个单词。
示例 1:
输入: "the sky is blue"
输出: "blue is sky the"
解题思路如下:
移除多余空格--双指针
将整个字符串反转--双指针
将每个单词反转
class Solution {
/**
* 不使用Java内置方法实现
* <p>
* 1.去除首尾以及中间多余空格
* 2.反转整个字符串
* 3.反转各个单词
*/
public String reverseWords(String s) {
// System.out.println("ReverseWords.reverseWords2() called with: s = [" + s + "]");
// 1.去除首尾以及中间多余空格
StringBuilder sb = removeSpace(s);
// 2.反转整个字符串
reverseString(sb, 0, sb.length() - 1);
// 3.反转各个单词
reverseEachWord(sb);
return sb.toString();
}
private StringBuilder removeSpace(String s) {
// System.out.println("ReverseWords.removeSpace() called with: s = [" + s + "]");
int start = 0;
int end = s.length() - 1;
while (s.charAt(start) == ' ') start++;
while (s.charAt(end) == ' ') end--;
StringBuilder sb = new StringBuilder();
while (start <= end) {
char c = s.charAt(start);
if (c != ' ' || sb.charAt(sb.length() - 1) != ' ') {
sb.append(c);
}
start++;
}
// System.out.println("ReverseWords.removeSpace returned: sb = [" + sb + "]");
return sb;
}
/**
* 反转字符串指定区间[start, end]的字符
*/
public void reverseString(StringBuilder sb, int start, int end) {
// System.out.println("ReverseWords.reverseString() called with: sb = [" + sb + "], start = [" + start + "], end = [" + end + "]");
while (start < end) {
char temp = sb.charAt(start);
sb.setCharAt(start, sb.charAt(end));
sb.setCharAt(end, temp);
start++;
end--;
}
// System.out.println("ReverseWords.reverseString returned: sb = [" + sb + "]");
}
private void reverseEachWord(StringBuilder sb) {
int start = 0;
int end = 1;
int n = sb.length();
while (start < n) {
while (end < n && sb.charAt(end) != ' ') {
end++;
}
reverseString(sb, start, end - 1);
start = end + 1;
end = start + 1;
}
}
}
5、左旋转字符串
字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。
示例 1:
输入: s = "abcdefg", k = 2
输出: "cdefgab"
方法:局部反转+整体反转
反转区间为前n的子串
反转区间为n到末尾的子串
反转整个字符串
class Solution {
public String reverseLeftWords(String s, int n) {
int len=s.length();
StringBuilder sb=new StringBuilder(s);
reverseString(sb,0,n-1);
reverseString(sb,n,len-1);
return sb.reverse().toString();
}
public void reverseString(StringBuilder sb, int start, int end) {
while (start < end) {
char temp = sb.charAt(start);
sb.setCharAt(start, sb.charAt(end));
sb.setCharAt(end, temp);
start++;
end--;
}
}
}
给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回 -1。
示例 1: 输入: haystack = "hello", needle = "ll" 输出: 2
示例 2: 输入: haystack = "aaaaa", needle = "bba" 输出: -1
class Solution {
/**
* 基于窗口滑动的算法
* <p>
* 时间复杂度:O(m*n)
* 空间复杂度:O(1)
* 注:n为haystack的长度,m为needle的长度
*/
public int strStr(String haystack, String needle) {
int m = needle.length();
// 当 needle 是空字符串时我们应当返回 0
if (m == 0) {
return 0;
}
int n = haystack.length();
if (n < m) {
return -1;
}
int i = 0;
int j = 0;
while (i < n - m + 1) {
// 找到首字母相等
while (i < n && haystack.charAt(i) != needle.charAt(j)) {
i++;
}
if (i == n) {// 没有首字母相等的
return -1;
}
// 遍历后续字符,判断是否相等
i++;
j++;
while (i < n && j < m && haystack.charAt(i) == needle.charAt(j)) {
i++;
j++;
}
if (j == m) {// 找到
return i - j;
} else {// 未找到
i -= j - 1;
j = 0;
}
}
return -1;
}
}
7、重复的子串
给定一个非空的字符串,判断它是否可以由它的一个子串重复多次构成。给定的字符串只含有小写英文字母,并且长度不超过10000。
示例 1:
输入: "abab"
输出: True
解释: 可由子字符串 "ab" 重复两次构成。
class Solution {
public boolean repeatedSubstringPattern(String s) {
if (s.equals("")) return false;
int len = s.length();
// 原串加个空格(哨兵),使下标从1开始,这样j从0开始,也不用初始化了
s = " " + s;
char[] chars = s.toCharArray();
int[] next = new int[len + 1];
// 构造 next 数组过程,j从0开始(空格),i从2开始
for (int i = 2, j = 0; i <= len; i++) {
// 匹配不成功,j回到前一位置 next 数组所对应的值
while (j > 0 && chars[i] != chars[j + 1]) j = next[j];
// 匹配成功,j往后移
if (chars[i] == chars[j + 1]) j++;
// 更新 next 数组的值
next[i] = j;
}
// 最后判断是否是重复的子字符串,这里 next[len] 即代表next数组末尾的值
if (next[len] > 0 && len % (len - next[len]) == 0) {
return true;
}
return false;
}
}
8、反转字符串
sb.reverse();
9、NC17 最长回文子串
方法1:动态规划
public int getLongestPalindrome(String A, int n) { // 第 i 个字符到第 j 个字符是否是回文串 boolean[][] dp = new boolean[n][n]; int max = 0; // 字符串首尾字母长度差 (d = j-i) for (int d = 0; d < n; d++) { // 字符串起始位置 i for (int i = 0; i < n-d; i++) { // 字符串结束位置 j int j = i+d; // 如果字符串 i 到 j 的首尾相等,再根据字符串 i-1 到 j-1 来确定,即得到递推公式 if(A.charAt(i) == A.charAt(j)) { if(d == 0 || d == 1) { dp[i][j] = true; } else { dp[i][j] = dp[i+1][j-1]; } if(dp[i][j]) { // 更新最大长度 max = Math.max(max, d+1); } } } } return max; }
方法 2:暴力
import java.util.*; public class Solution { //判断回文的函数 public boolean isHuiWen(String A, int n){ int k = n / 2; for (int i = 0; i < k; ++i) { if (A.charAt(i) != A.charAt(n-1-i)) return false; } return true; } public int getLongestPalindrome(String A, int n) { int maxlen=0; for(int i=0 ;i< n ;i++){ for(int j=i+1 ;j<=n ;j++){ //两层循环遍历出所有的子串,并且逐一判断是否是回文 if(isHuiWen(A.substring(i, j),j-i)){ if(j-i>maxlen) maxlen=j-i; } } } return maxlen; } }
方法3:从中间往两边扩散
10、NC1 大数加法
public String solve (String s, String t)
import java.util.*; public class Solution { /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * 计算两个数之和 * @param s string字符串 表示第一个整数 * @param t string字符串 表示第二个整数 * @return string字符串 */ public String solve (String s, String t) { // write code here StringBuilder s1=new StringBuilder(s); StringBuilder s2=new StringBuilder(t); StringBuilder res=new StringBuilder(); s1=s1.reverse(); s2=s2.reverse(); int x=0; int i=0; int j=0; while(i<s1.length() || j<s2.length() || x!=0){ int sum=x; if(i<s1.length() && j<s2.length()){ sum+=(s1.charAt(i++)-'0')+(s2.charAt(j++)-'0'); }else if(i<s1.length() && j>=s2.length()){ sum+=(s1.charAt(i++)-'0'); }else if(i>=s1.length() && j<s2.length()){ sum+=(s2.charAt(j++)-'0'); }else{ res.append(sum%10); break; } res.append(sum%10); x=sum/10; } return res.reverse().toString(); } }
或:可以用栈和StringBuilder实现
11、NC55 最长公共前缀
public String longestCommonPrefix (String[] strs)
方法1:调用string.startWith("")
方法2:循环找长度最小,遍历最小,值不相同则跳出循环 loop for & break loop;
import java.util.*; public class Solution { /** * * @param strs string字符串一维数组 * @return string字符串 */ public String longestCommonPrefix (String[] strs) { // write code here if(strs == null || strs.length == 0) return ""; if(strs.length == 1) return strs[0];//strs只有一个元素的话直接返回 //方法一:逐个比对法 /* int minLength = strs[0].length(); for(String str : strs){//得到数组元素中长度最短的字符串对应的长度 minLength = Math.min(minLength,str.length()); } StringBuilder result = new StringBuilder(); loop:for(int i = 0; i < minLength; i++){//遍历strs数组每个元素的每个字符 for(int j = 1; j < strs.length; j++){//遍历strs数组每个元素 if(strs[0].charAt(i) != strs[j].charAt(i)){ break loop; } } result.append(strs[0].charAt(i)); } return result.toString(); */ ///* //方法二:调用startsWith方法 String res = strs[0]; for(int i = 1; i < strs.length; i++){ while(!res.equals("") && !strs[i].startsWith(res)){ res = res.substring(0,res.length() - 1); } if(res.equals("")) break; } return res; //*/ } }
12、NC52 括号序列
public boolean isValid (String s)
方法:用栈存,(入栈),如果不是(则出栈,出栈顺序和数组顺序不同,则无效
import java.util.*; public class Solution { public boolean isValid (String s) { Stack<Character> stack = new Stack<Character>(); char[] arr = s.toCharArray(); int len = arr.length; for(int i=0;i<len;i++){ if(arr[i]=='('){ stack.push(')'); }else if(arr[i]=='{'){ stack.push('}'); }else if(arr[i]=='['){ stack.push(']'); }else if(stack.isEmpty()||stack.pop()!=arr[i]){ return false; } } return stack.isEmpty(); } }
13、NC149 kmp算法
public int kmp (String S, String T)
标准kmp
import java.util.*; public class Solution { /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * 计算模板串S在文本串T中出现了多少次 * @param S string字符串 模板串 * @param T string字符串 文本串 * @return int整型 */ public int kmp (String S, String T) { char[] t = T.toCharArray(), p = S.toCharArray(); int i1 = 0, i2 = 0, res = 0; int[] next = next(p); while(i1 < t.length && i2 < p.length){ if(t[i1] == p[i2]){ i1 ++; i2 ++; }else if(next[i2] == -1){ i1 ++; }else { i2 = next[i2]; } if(i2 == p.length){ res ++; i2 = next[i2-1]; i1 --; } } if(i2 == p.length){ } return res; } int[] next(char[] p){ if(p.length == 1){ return new int[]{-1}; } int[] next = new int[p.length]; next[0] = -1; next[1] = 0; //cn 表示next[i-1] int i = 2, cn = 0; while(i < p.length){ if(p[i - 1] == p[cn] ){ next[i ++] = ++cn; }else if(cn > 0){ cn = next[cn]; }else { next[i++] = 0; } } return next; }
}
方法2:截断字符串
public class Solution { public int kmp (String S, String T) { // write code here int result=0; for(int i=0;i<T.length();i++) { int ff=i+S.length(); if(ff<=T.length()) { String fsString= T.substring(i, ff); if(S.equals(fsString)) { result++; } } } return result; } }
14、NC28 最小覆盖子串
方法:滑动窗口-right小于len,记录map
15、NC49 最长的括号子串
public int longestValidParentheses (String s)
方法:括号匹配问题用栈(左括号入栈,右括号出栈或比较),右括号且出栈后不空,则计算最大len为i - stack.peek()
import java.util.*; public class Solution { public int longestValidParentheses (String s) { Stack<Integer> stack = new Stack<>(); // 设定栈,存储左括号 stack.push(-1); // 压入-1,处理边界问题 int res = 0; // 结果存储变量 for (int i = 0;i < s.length();i++) { // 如果是左括号则直接入栈 if (s.charAt(i) == '(') { stack.push(i); }else { // 如果是右括号,则弹栈 stack.pop(); // 判空,若栈为空,则说明i左侧已经没有可用的左括号,此时将i压入栈中,防止空栈异常 if (stack.isEmpty()) { stack.push(i); }else { // 长度计算时无需加1,因为预先弹栈,相当于已经加过1,且在01边界上因为初始化时压入-1进栈,因此也得以解决 res = Math.max(res, i - stack.peek()); } } } return res; } }
16、NC31 第一个只出现一次的字符
public int FirstNotRepeatingChar(String str)
方法1:使用字符串或map记录
import java.util.HashMap; import java.util.Map; public class Solution { public int FirstNotRepeatingChar(String str) { char [] tmp = new char[128]; for(int i = 0;i<str.length();i++){ char c = str.charAt(i); tmp[c]++; } for(int i = 0;i<str.length();i++){ if(tmp[str.charAt(i)] == 1){ return i; } } return -1; } }
方法2:substring一个参数表示从当前位置到最后,两个参数到不了最后
public class Solution { public int FirstNotRepeatingChar(String str) { if(str.length()==1) return 0; for(int i=0;i<str.length();i++){ String s=str.substring(0,i)+str.substring(i+1);//获取不包含第i个字符的字符 if(s.contains(str.charAt(i)+"")) continue; return i; } return -1; } }
17、NC100 将字符串转化为整数
public int atoi (String str)
方法:用long存,最后转int,记得与最大值比较
import java.util.*; public class Solution { /** * * @param str string字符串 * @return int整型 */ public int atoi (String str) { // write code here long n=0; int j=0; if(str==""||str.length()<1) return 0; for(int i=0;i<str.length();i++){ if(str.charAt(i)=='-'){ j=1; continue; } if(str.charAt(i)=='+'||str.charAt(i)=='-'||str.charAt(i)==' '){ continue; }else if(str.charAt(i)<'0'||str.charAt(i)>'9'){ break; }else n=n*10+str.charAt(i)-'0'; } if(j==1){ long k; k=n; n=0-k; } if(n>Integer.MAX_VALUE){ return Integer.MAX_VALUE; }else if(n<Integer.MIN_VALUE){ return Integer.MIN_VALUE; } return (int)n; } }
18、NC121 字符串的排列
public ArrayList<String> Permutation(String str)
方法:想象成树,回溯,结束条件、记录路径,选择列表
import java.util.*; public class Solution { ArrayList<String> res = new ArrayList<>(); StringBuilder sb = new StringBuilder(); public ArrayList<String> Permutation(String str) { //回溯 char[] cs = str.toCharArray(); boolean[] used = new boolean[cs.length]; Arrays.sort(cs); backtrack(cs, used, 0); return res; } public void backtrack(char[] cs, boolean[] used, int start) { if (sb.length() == cs.length) { res.add(sb.toString()); return; } for (int i = 0; i < cs.length; i++) { if (i > 0 && cs[i] == cs[i - 1] && !used[i - 1]) continue; //重复的要进行排序 if (!used[i]) { used[i] = true; sb.append(cs[i]); backtrack(cs, used, i + 1); used[i] = false; sb.setLength(sb.length() - 1); // 也可以是path.deleteCharAt(path.length()-1); ??????????????? } } } }
19、NC113 验证IP地址
public String solve (String IP)
解法:V6是8位,V4是4位<256
public String solve (String IP) { // write code here String[] c = IP.split("\\."); if(c.length != 4) { c = IP.split(":"); if(c.length != 8) { return "Neither"; } else { String regex="^[A-Fa-f0-9]+$"; for(int i = 0; i < 8; i++) { String s = c[i]; if(s.length() > 4 || !s.matches(regex)) { return "Neither"; } } return "IPv6"; } } else { for(int i = 0; i < 4; i++) { String s = c[i]; int num = Integer.parseInt(s); if(num > 255 || (c[i].charAt(0) == '0' && num > 0)) { return "Neither"; } } return "IPv4"; } }
20、NC104 比较版本号
public int compare (String version1, String version2)
方法:字符串分割、依次比较
import java.util.*; public class Solution { public int compare (String version1, String version2) { // write code here String[] numsOfV1 = version1.split("\\."); // 记得这里分割的时候需要加两个斜杠 String[] numsOfV2 = version2.split("\\."); int index = 0; while (index < numsOfV1.length && index < numsOfV2.length) { int num1 = Integer.parseInt(numsOfV1[index]); int num2 = Integer.parseInt(numsOfV2[index]); if (num1 > num2) { return 1; } else if (num1 < num2) { return -1; } index ++; } while (index < numsOfV1.length) { int num1 = Integer.parseInt(numsOfV1[index]); if (num1 > 0) { return 1; } index ++; } while (index < numsOfV2.length) { int num2 = Integer.parseInt(numsOfV2[index]); if (num2 > 0) { return -1; } index ++; } return 0; } }
21、NC142 最长重复子串
public int solve (String a)
自己想法:暴力求解,双指针,比较两个list内容,通过max得到最大值
22、NC20 数字字符串转化成IP地址
public ArrayList<String> restoreIpAddresses (String s)
方法:回溯
import java.util.*; public class Solution { private ArrayList<String> res; private String[] arr; private String str; /** * * @param s string字符串 * @return string字符串ArrayList * 深搜 * 递归方法的设计:dfs(int index,int level)其中index表示访问到的字符串的索引,level表示层级即String数组中现在的实际字符串长度 * 递归出口:index == str.length() || level == 4 * 递归组合方式:每次遍历1,2,3个字符 */ public ArrayList<String> restoreIpAddresses (String s) { // write code here this.res = new ArrayList<>(); this.arr = new String[4]; this.str = s; dfs(0,0); return res; } public void dfs(int index,int level){ // 递归截止条件 if(index==str.length() || level==4){ if(level == 4 && index == str.length()){ StringBuilder sb = new StringBuilder(); for(int i=0;i<3;i++){ sb.append(arr[i]+"."); } sb.append(arr[3]); res.add(sb.toString()); } return; } // 递归组合方式 for(int j=1;j<=3;j++){ String s = ""; if(index+j<=str.length()){ s = str.substring(index,index+j); if(isRight(s)){ arr[level] = s; dfs(index+j,level+1); arr[level] = ""; } } } } /* * 判断拼接的IP是否合法 */ public boolean isRight(String s){ if(s.equals("0") || (s.charAt(0)!='0'&& Integer.parseInt(s)<256)){ return true; } return false; } }
23、NC10 大数乘法
public String solve (String s, String t)
解法:依次相乘,然后进位
import java.util.*; public class Solution { /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * @param s string字符串 第一个整数 * @param t string字符串 第二个整数 * @return string字符串 */ public String solve (String s, String t) { int len1 = s.length(); int len2 = t.length(); int[] nums1 = new int[len1]; int[] nums2 = new int[len2]; for(int i = 0; i < len1; i++){ nums1[i] = s.charAt(i) - '0'; } for(int i = 0; i < len2; i++){ nums2[i] = t.charAt(i) - '0'; } int[] result = new int[len1 + len2]; // 1. 逐位相乘 for(int i = 0; i < len1; i++){ for(int j = 0; j < len2; j++){ result[i + j] += nums1[i] * nums2[j]; } } // 2. 从后往前进位 for(int k = result.length - 1; k > 0; k--){ result[k - 1] += result[k] / 10; result[k] = result[k] % 10; } // 输出字符串 StringBuilder stringBuilder = new StringBuilder(); for(int i = 0; i < result.length - 1; i++){ stringBuilder.append(result[i]); } return stringBuilder.toString(); } }
本文来自博客园,作者:哥们要飞,转载请注明原文链接:https://www.cnblogs.com/liujinhui/p/15140537.html