目录
一.最长公共子序列(一)(动态规划)
1.题目说明
一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。
两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。
2.图解说明
3.代码实现
//求最长公共子序列
public int longestCommonSubsequence(String text1, String text2) {
int row = text1.length();
int col = text2.length();
//这里使用行列多一个是因为防止第一个元素dp[i-1][j-1]越界
int[][] dp = new int[row+1][col+1];
for(int i=1; i<=row; i++) {
for(int j=1; j<=col; j++) {
//表示当前行列元素相等
if(text1.charAt(i-1)==text2.charAt(j-1)) {
dp[i][j] = dp[i-1][j-1]+1;
}else {
//当前行列元素不等
dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]);
}
}
}
//数组最后元素位置就是最大公共子序列数
return dp[row][col];
}
二.最长公共子序列(二)
1.题目
给定两个字符串str1和str2,输出两个字符串的最长公共子序列。如果最长公共子序列为空,则返回"-1"。目前给出的数据,仅仅会存在一个最长的公共子序列。(相当于是找最长子序列的结果)
示例:
输入:"1A2C3D4B56","B1D23A456A"
返回值:"123456"
2.思路图解
首先还是根据动态规划来找最长子序列,在寻找最长子序列的时候,使用二维数组b需要保存走过的路径位置,这里b中1表示来自于左上方,b中为2表示来自于左边,b中3表示来自于上方,之后通过递归来进行还原。
3.代码
//保存源字符串,之后通过该串进行还原
private String x = "";
//获取最长公共子序列
String ans(int i, int j, int[][] b){
String res = "";
//递归终止条件
if(i == 0 || j == 0)
return res;
//根据方向,往前递归,然后添加本级字符
//说明来自左上方
if(b[i][j] == 1){
res += ans(i - 1, j - 1, b);//递归右边1
res += x.charAt(i - 1);
}
else if(b[i][j] == 2)//左边,递归左边
res += ans(i - 1, j, b);
else if(b[i][j] == 3)//上边,递归上面
res += ans(i,j - 1, b);
return res;
}
public String LCS (String s1, String s2) {
//特殊情况
if(s1.length() == 0 || s2.length() == 0)
return "-1";
int len1 = s1.length();
int len2 = s2.length();
x = s1;
y = s2;
//dp[i][j]表示第一个字符串到第i位,第二个字符串到第j位为止的最长公共子序列长度
int[][] dp = new int[len1 + 1][len2 + 1];
//动态规划数组相加的方向
int[][] b = new int[len1 + 1][len2 + 1];
//遍历两个字符串每个位置求的最长长度
for(int i = 1; i <= len1; i++){
for(int j = 1; j <= len2; j++){
//遇到两个字符相等
if(s1.charAt(i - 1) == s2.charAt(j - 1)){
//考虑由二者都向前一位
dp[i][j] = dp[i - 1][j - 1] + 1;
//来自于左上方
b[i][j] = 1;
}
//遇到的两个字符不同
else{
//保存来自上边
if(dp[i - 1][j] > dp[i][j - 1]){
dp[i][j] = dp[i - 1][j];
b[i][j] = 2;
}
//保存来自左边
else{
dp[i][j] = dp[i][j - 1];
b[i][j] = 3;
}
}
}
}
//获取答案字符串
String res = ans(len1, len2, b);
//检查答案是否位空
if(res.isEmpty())
return "-1";
else
return res;
}
三.最长公共子串
1.题目说明
最长公共子串指的是两个字符串连续的子串。
例如abcde,与bcd这两个字符串的最长公共子串就是bcd。
2.思路图解
3.代码
//最长公共子串
public String LCS (String str1, String str2) {
int r = str1.length();
int c = str2.length();
int[][] dp = new int[r+1][c+1];
int maxLen = 0;//保存最大长度
int lastIndex = 0;//保存最后子序列结束的索引位置
for(int i=1; i<=r; i++) {
for(int j=1; j<=c; j++) {
//从字符串首位置开始查找是否相等的元素
if(str1.charAt(i-1) == str2.charAt(j-1)) {
dp[i][j] = dp[i-1][j-1]+1;
if(dp[i][j]>maxLen) {
//修改最长连续子串的长度
maxLen = dp[i][j];
//修改结束位置
lastIndex = i-1;
}
}else {
//当前不存在在公共子串
dp[i][j] = 0;
}
}
}
//截取最长公共子串
return str1.substring(lastIndex-maxLen+1, lastIndex+1);
}
四.判断一个字符串是否为另一个字符串的子序列
1.题目描述
给定字符串 s 和 t ,判断 s 是否为 t 的子序列。
字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。
2.图解
3.代码
//判断s是否为t的子串
public boolean isSubsequence(String s, String t) {
//双指针法
int sL = s.length();
int tL = t.length();
int i = 0;//表示s的下标
int j = 0;//表示t的下标
while(i<sL && j<tL) {
if(s.charAt(i) == t.charAt(j)) {
i++;
}
j++;
}
//如果i指针走到最后,就说明是子串,时间复杂度为O(max(s.length(), t.length()))
return i==sL;
}
五.求最长无公共子串问题
1.题目描述
给定一个字符串 s
,请你找出其中不含有重复字符的 最长连续子字符串 的长度。
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子字符串是 "abc",所以其长度为 3。
2.思路分析
(1)首先需要保存无重复子字符串开始(begin)和结束(end)的位置。
(2)每次向后查找时需要与前面没有重复的子串进行比较,如果没有重复,就继续;遇到重复的就结束当前循环,从begin的下一个位置再开始进行查找。在进行查找的时候需要使用set集合来判断是否重复。
3.代码实现
(1)实现方式一
图解:
代码:
public int lengthOfLongestSubstring(String s) {
if(s.length()==0) {
return 0;
}
//保存最大长度
int maxLen = 0;
//从字符串首位置开始依次向后查找
for(int i=0; i<s.length(); i++) {
//set集合保存没有重复的子字符串
HashSet<Character> set = new HashSet<>();
//相当于从begin开始向后查找
for(int j=i;j<s.length(); j++) {
//没有重复就添加到集合中
if(!set.contains(s.charAt(j))) {
set.add(s.charAt(j));
//防止字符串本身就是一个无重复子串,所以每添加一个就保存最长的子串
maxLen = Math.max(maxLen, j-i+1);
}else {
//说明遇到重复的子串,就保存最长子串的长度,然后退出当前循环
maxLen = Math.max(maxLen, j-i);
break;
}
}
}
return maxLen;
}
(2)方式二实现
举例如:求“abccd的不重复的最长子串”
图解:
代码:
public int lengthOfLongestSubstring(String s) {
Set<Character> set = new HashSet<>();
//保存最长无重复子字符串
int maxLen = 0;
//保存最右边当前元素
int j=0;
//从左到右依次寻找
for(int i=0; i<s.length(); i++) {
//每次从头删除set集合中的元素,因为要保持set中没有连续子字符串没有重复元素
if(i!=0) {
set.remove(s.charAt(i-1));
}
while(j<s.length() && !set.contains(s.charAt(j))) {
//当没有重复子串j就一直后移,如果遇到重复的就结束
set.add(s.charAt(j));
j++;
}
//找到了从i-j的子串,然后于最大值进行比较
maxLen = Math.max(maxLen, j-i);
}
return maxLen;
}
六.求字符串的全部子序列
1.题目描述
给定一个字符串s,长度为n,求s的所有子序列
1.子序列: 指一个字符串删掉部分字符(也可以不删)形成的字符串,可以是不连续的,比如"abcde"的子序列可以有"ace","ad"等等
2.将所有的子序列的结果返回为一个字符串数组
3.字符串里面可能有重复字符,但是返回的子序列不能有重复的子序列,比如"aab"的子序列只有"","a","aa","aab","ab","b",不能存在2个相同的"ab"
4.返回字符串数组里面的顺序可以不唯一
例子:
输入:"aab"
返回值:["","a","aa","aab","ab","b"],返回的字符串数组里面不能存在"ab","ab"这样2个相同的子序列。
2.图解
3.代码实现
//将获取到的结果存储到set中,也是去重
Set<String> set = new HashSet<>();
public String[] generatePermutation (String s) {
// write code here
dfs(new StringBuilder(), 0, s);
//将set转化为字符串数组
return set.toArray(new String[set.size()]);
}
public void dfs(StringBuilder sb, int index, String str) {
//sb每次变化后,都会添加到set中,这里set会去重
set.add(sb.toString());
for(int i=index; i<str.length(); i++) {
//添加当前位置字符
sb.append(str.charAt(i));
//递归向一个字符进行dfs
dfs(sb, i+1, str);
//结束后面递归就删除最后一个元素
sb.deleteCharAt(sb.length()-1);
}
}
七.压缩字符串
1.题目
利用字符重复出现的次数,编写一种方法,实现基本的字符串压缩功能。比如,字符串aabcccccaaa会变为a2bc5a3。
1.如果只有一个字符,1不用写
2.字符串中只包含大小写英文字母(a至z)
2.图解
3.代码
public String compressString (String param) {
// write code here
int left = 0;
int right = 0;
int n = param.length();
StringBuilder sb = new StringBuilder();
while(left<n) {
char tmp = param.charAt(left);
//right从left的后一个开始
right = right+1;
//查找连续重复的最终位置
while(right<n) {
if(tmp==param.charAt(right)){
right++;
}else {
break;
}
}
//根据后面元素个数来判断是否压缩
sb.append(tmp);
if(right-left>1) {
sb.append(right-left);
}
//找完后,更新左边界
left = right;
}
return sb.toString();
}
八.长度为k的重复字符的子串
1.题目
给你一个由小写字母组成的长度为n的字符串 S ,找出所有长度为 k 且包含重复字符的子串,请你返回全部满足要求的子串的数目。
例子:
输入:"createfunonyoka",4
返回值:4
分别为:eate, unon, nony,onyo.
2.图解
3.代码
public int numKLenSubstrRepeats (String s, int k) {
// write code here
int res = 0;
//存储滑动窗口的元素
int[] arr = new int[26];
//构造初始的滑动窗口
for(int i=0; i<k; i++) {
arr[s.charAt(i)-'a']++;
}
if(isRepeat(arr)) res++;
for(int i=k; i<s.length(); i++) {
//滑动窗口思想,滑动窗口后移
arr[s.charAt(i)-'a']++;
arr[s.charAt(i-k)-'a']--;
//窗口每移动一次,就判断是否有重复的字符
if(isRepeat(arr)) res++;
}
return res;
}
//判断是否有重复字符的子串
public boolean isRepeat(int[] arr){
for(int i=0; i<arr.length; i++) {
if(arr[i]>1) {
return true;
}
}
return false;
}
九.字符串解码
1.题目
给一个加密过的字符串解码,返回解码后的字符串。
加密方法是:k[c] ,表示中括号中的 c 字符串重复 k 次,例如 3[a] 解码结果是 aaa ,保证输入字符串符合规则。不会出现类似 3a , 3[3] 这样的输入。
例子:
输入:"3[3[b]]"
返回值:"bbbbbbbbb"
2.图解
3.代码
//设置成员变量为了之后对字符串好操作
int i = 0;
public String decodeString (String s) {
// write code here
LinkedList<String> stack = new LinkedList<>();
while(i<s.length()) {
char ch = s.charAt(i);
//处理数字
if(Character.isDigit(ch)) {
//将数字放入栈中,在放之前防止是一个连续的数字,所以需要将
//所有数字都访问完,这里使用一个函数来处理连续数字
String nums = getNums(s);
//将拼接好的数字添加到栈中
stack.addLast(nums);
}else if(Character.isLetter(ch) || ch=='['){//处理字符或者左括号
stack.addLast(String.valueOf(s.charAt(i)));
i++;
}else {//遇到右括号,就处理栈中的数据,直到遇到左括号为止
LinkedList<String> sub = new LinkedList<>();
while(!"[".equals(stack.peekLast())) {
//处理栈顶字符串
sub.addLast(stack.removeLast());
}
//对字符串链表反转,恢复原来的顺序
Collections.reverse(sub);
//将左括号出栈
stack.removeLast();
//栈顶现在就是需要拼接的次数(数字)
int num = Integer.parseInt(stack.removeLast());
//在拼接之前,首先将sub的字符串链表拼接成一个完整的字符串
String str = getString(sub);
//将需要还原的倍数进行还原
StringBuilder sb = new StringBuilder();
while(num-->0) {
sb.append(str);
}
//将拼接好的字符串再重新入栈
stack.addLast(sb.toString());
i++;//指针后移
}
}
//将栈中单个字符串进行拼接
return getString(stack);
}
public String getNums(String s) {
StringBuilder sb = new StringBuilder();
//说明是数字,就一直拼接
while(Character.isDigit(s.charAt(i))) {
sb.append(s.charAt(i));
i++;
}
return sb.toString();
}
public String getString(LinkedList<String> sub) {
StringBuilder sb = new StringBuilder();
for(String str:sub) {
sb.append(str);
}
return sb.toString();
}