一 字符串的排列与组合
1.字符串的组合【子序列】
题目:输入一个字符串,输出该字符串中字符的所有组合。
例子:输入:abc,它的组合有:a、b、c、ab、ac、bc、abc
分析:我们可以将字符串中的每个字符看成二叉树的一个节点,根节点为空,每个节点都会有两种选择:要 和 不要 两种选择 。那么我们就可以利用递归实现。
public class 字符串子序列 {
public static void printAllSubString(String str){
if(str == null){
return;
}
char[] chars = str.toCharArray();
if(chars.length > 0){
String pre = new String(""); // pre:用于表示从0到i-1位置上形成的结果
printAllSubString(0, pre, chars);
}else{
System.out.println(""); // 输入空字符串也会打印空
}
}
public static void printAllSubString(int i, String pre, char[] chars){
// 迭代终止条件
if(i == chars.length){
// 说明已经到最后一个字符了,所有的选择都已经做完了,应该返回了
System.out.println(pre);
return;
}
// 如果没有到最后一个字符,那么当前字符有两种选择:选择要 和 选择不要
printAllSubString(i + 1, pre, chars); // 不要当前字符
printAllSubString(i + 1, pre + String.valueOf(chars[i]), chars); // 要当前字符
}
// 测试
public static void main(String[] args) {
printAllSubString("abc");
}
}
2、字符串的排列
题目:输入一个字符串,打印出该字符串中字符的所有排列。
举例:输入字符串 abc,则输出由字符 a、b、c 所能排列出来的所有字符串 abc、acb、bac、bca、cab 和 cba。
分析:排列和上面的组合问题思想是一样的:上面的组合问题,每个节点只有 “要” 和 “不要” 两种选择,而排列这里每个节点 i 有 n - i 种选择。
排列问题:所有的排列都是包含该字符串中所有的字符,所以不需要像组合那样利用额外的空间 pre 记录选择的过程。
需要注意的是:i 位置在进行选择的时候,会先和 i + 1 位置交换位置,搞定 i + 1 后面的排列后,会再和 i + 2 ~ n - 1 位置上的每个元素交换一次,所以为了保证都是和 i 位置上的元素进行交换,每次递归一次后,必须要将 i 位置元素再换回原来的位置。
可以直观的理解下:加入现在搞定 i 位置上元素,i 一共有 n - i 种选择,每次 i 位置固定后,i + 1 ~ n - 1 位置上的元素都是同样递归实现。
public class 字符串排列 {
public static void printAllSort(String string){
if(string == null){
return;
}
char[] chars = string.toCharArray();
if(chars.length > 0){
printAllSort(0, chars);
}
}
// 对i及i以后的字符进行全排列
public static void printAllSort(int i, char[] chars){
// 递归终止条件
if(i == chars.length){
System.out.println(String.valueOf(chars));
}
for(int j = i; j < chars.length; j++){
swap(i, j, chars); // 第 i 个位置有 i ~ (n - 1) 种选择。n 为字符串的长度
printAllSort(i + 1, chars);
swap(i, j, chars); // 保证 i 后面的字符每次都是和 i 位置上的元素进行的交换,还需要将 i 和 j 交换回来
}
}
public static void swap(int i, int j, char[] chars){
int temp = chars[i];
chars[i] = chars[j];
chars[j] = chars[i];
}
public static void main(String[] args) {
printAllSort("abcc");
}
}
运行结果:从运行结果可以看出来,很多排列的结果是重复的,这是全排列的效果。
只需要增加一个 hashset,用于保证重复字符不会被再次交换。
public class 字符串排列_去重 {
public static void printAllSort(String string){
if(string == null){
return;
}
char[] chars = string.toCharArray();
if(chars.length > 0){
printAllSort(0, chars);
}
}
// 对i及i以后的字符进行全排列
public static void printAllSort(int i, char[] chars){
// 递归终止条件
if(i == chars.length){
System.out.println(String.valueOf(chars));
}
// 用于保证每次交换的字符不存在重复的字符
HashSet<Character> set = new HashSet<>();
for(int j = i; j < chars.length; j++){
if(!set.contains(chars[j])){
set.add(chars[j]);
swap(i, j, chars); // 第 i 个位置有 i ~ (n - 1) 种选择。n 为字符串的长度
printAllSort(i + 1, chars);
swap(i, j, chars); // 保证 i 后面的字符每次都是和 i 位置上的元素进行的交换,还需要将 i 和 j 交换回来
}
}
}
public static void swap(int i, int j, char[] chars){
int temp = chars[i];
chars[i] = chars[j];
chars[j] = chars[i];
}
public static void main(String[] args) {
printAllSort("abcc");
}
}
二 最长回文子串
先说明几个概念:
1. 子串:小于等于原字符串长度,由原字符串中任意个连续字符组成的子序列;
2. 回文:关于中间字符对称的字符串,例如:"ababa"(单核)、"abccba"(双核);
3. 最长回文子串:回文子串中最长的子串。
1、暴力解法【时间复杂度:O()】
基本思路:遍历该字符串所有的子串,找出其中是回文子串且长度最长的那个。所以,我们可以倒着遍历该字符串,从最长的子串开始,这样只用找到第一个是回文字符串就可以了,它一定是原字符串最长的回文子串。
- 从最长的子串开始,遍历该字符串的所有子串(时间复杂度为:O(n^2));
- 判断当前子串是否为回文串(时间复杂度为:O(n));
- 当前子串为回文时,则找到了原字符串的最长回文子串,结束遍历;否则,继续遍历,直到遍历完所有子串。
public class LongestPalindrome_5 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String str = sc.next();
getLongestPalindrome1(str);
}
// 暴力解法
public static String getLongestPalindrome1(String str){
if(str.length() <= 1){
return str;
}
// 前两层循环是求字符串的所有子串
for(int i = str.length(); i > 0; i--){
for(int j = 0; j <= str.length() - i; j++){
String sub = str.substring(j, i + j);
int count = 0;
// 检验当前的子串是否为回文串
for(int k = 0; k < sub.length() / 2; k++){
// k是从0开始的,所以是sub.length-k-1
if(sub.charAt(k) == sub.charAt(sub.length() - k - 1)){
count++;
}
}
if(count == sub.length() / 2){
System.out.println(sub);
return sub;
}
}
}
return ""; // 没有
}
}
从分析和代码实现上,可以很明显的看出,暴力解法的时间复杂度是:O(n^3),无论是笔试还是面试显然都是不行的。
2、动态规划【时间复杂度:O(n^2)】
回文字符串的子串也是回文,比如P[i, j](表示以 i 开始,以 j 结束的子串)是回文字符串,那么P[i+1, j-1]也是回文字符串。这样最长回文子串就能分解成一系列子问题了。这样需要额外的空间O(N^2),算法复杂度也是O(N^2)。
首先定义状态方程和转移方程:
- P[i, j] = false:表示子串[i, j]不是回文串;P[i, j] = true:表示子串[i, j]是回文串。
- P[i, i] = true:当且仅当P[i+1, j-1] = true && (s[i] == s[j])否则p[i,j] =false;
public class LongestPalindrome_5 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String str = sc.next();
String subStr = getLongestPalindrome3(str);
System.out.println(subStr);
}
// 动态规划
public static String getLongestPalindrome2(String str){
if(str == null && str.length() <= 0){
return "";
}
int len = str.length();
int start = 0; // 记录字符串起始的位置
int maxLength = 1; // 记录回文串的最大长度
boolean dp[][] = new boolean[str.length()][str.length()];
// 长度为1和2的子串的初始化
for(int i = 0; i < len; i++){
// 初始化所有长度为1的子串
dp[i][i] = true;
// 初始化所有长度为2的子串
if(i < len - 1 && str.charAt(i) == str.charAt(i + 1)){
dp[i][i + 1] = true;
start = i;
maxLength = 2;
}
}
// 以字符串长度为1和2的子串为基础,推导长度:3~len 的子串的dp
for(int strlen = 3; strlen <= len; strlen++){
// 从头开始,遍历长度为strlen的子串,并判断它们是否为回文串
for(int i = 0; i <= len - strlen; i++){
int j = i + strlen - 1; // 子串结束位置的下标
if(dp[i + 1][j - 1] && str.charAt(i) == str.charAt(j)){
dp[i][j] = true;
// 更新最大回文子串长度为当前子串长度,因为子串长度是不断增加的,所以最后一个回文串肯定是最长的
maxLength = strlen;
start = i; // 记录回文串开始位置的下标
}
}
}
if(maxLength > 0){
return str.substring(start, start + maxLength);
}
return "";
}
}
3、中心扩展法【时间复杂度:O()】
事实上,只需使用恒定的空间,我们就可以在 O(n^2) 的时间内解决这个问题。
我们观察到回文中心的两侧互为镜像。因此,回文可以从它的中心展开,并且只有 2n −1 个这样的中心。
你可能会问,为什么会是 2n - 1 个,而不是 n 个中心?原因在于所含字母数为偶数的回文的中心可以处于两字母之间(例如 {“abba”}的中心在两个{‘b’}之间)。【和在两个字符之间添加“#”是一个道理】
public class LongestPalindrome_5 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String str = sc.next();
String subStr = getLongestPalindrome3(str);
System.out.println(subStr);
}
// 中心扩展法
public static String getLongestPalindrome3(String str){
if(str == null && str.length() <= 0){
return "";
}
int maxLength = 1;
int start = 0;
// 类似于aba这种情况,以i为中心向两边扩展
for(int i = 0; i < str.length(); i++){
int j = i - 1;
int k = i + 1;
while((j >= 0 && k < str.length()) && str.charAt(j) == str.charAt(k)){
if(k - j + 1 > maxLength){
maxLength = k - j + 1;
start = j;
}
j--;
k++;
}
}
// 类似于abba这种情况,以i,i+1为中心向两边扩展
for(int i = 0; i < str.length(); i++){
int j = i;
int k = i + 1;
while((j >= 0 && k < str.length()) && str.charAt(j) == str.charAt(k)){
if(k - j + 1 > maxLength){
maxLength = k - j + 1;
start = j;
}
j--;
k++;
}
}
if(maxLength > 0){
return str.substring(start, start + maxLength);
}
return "";
}
}
可以看到上面代码非常长,可以发现主要就是奇回文和偶回文两种情况的处理,代码高度一致。可以精简下,这里贴出 leetcode 官方提供的代码。
其实对于笔试来说,通过最重要,还是推荐上面的代码,虽然长,但是思路比较清晰。
public class LongestPalindrome_5 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String str = sc.next();
String subStr = getLongestPalindrome4(str);
System.out.println(subStr);
}
// 中心扩展法:精简版
public static String getLongestPalindrome4(String str){
if(str == null || str.length() < 1){
return "";
}
int start = 0, end = 0;
for(int i = 0; i < str.length(); i++){
int len1 = expandAroundCenter(str, i, i); // 偶数回文
int len2 = expandAroundCenter(str, i, i + 1); // 奇数回文
int len = Math.max(len1,len2);
if(len > end - start){
start = i - (len - 1) / 2;
end = i + len / 2;
}
}
return str.substring(start, end + 1);
}
public static int expandAroundCenter(String str, int left, int right){
while(left >= 0 && right < str.length() && str.charAt(left) == str.charAt(right)){
left--;
right++;
}
// 算的是左右两边的中间的长度
return right - left - 1;
}
}
4.Manacher算法(马拉车算法)
字符串初始化处理
奇数长和偶数长的回文子串的字符中心是不一样的,不好判断,为了解决这个问题,Manacher算法将所有字符串的长度都变成奇数,方法就是在原字符串的相邻字符之间加分隔符(比如说#),例如ababc,变成#a#b#a#b#c#;abab,变成#a#b#a#b#。所有字符串长度都是奇数,还不影响回文性。
回文半径存储数组的定义
Manacher算法还建立了一个辅助数组,用来存储以str[i]为中心的回文字符串的回文半径的长度。例如回文字符串:#a#b#a#b#a#,以第三个a为中心,即str[5]为中心,这个回文字符串的长度为11,而回文半径是回文字符串长度的一半,即(11+1)/2=6,(要包括回文中心字符),所以len[5]=6。
这里要注意一点的是:调整后加入#的字符串str,以str[i]为中心的回文字符串的长度为2×len[i]-1,而其中有len[i]个分隔符#,所以原字符串的长度为len[i]-1。也就是说最后只要输出len中最大的数字-1就是最长回文子串的长度。
回文半径存储数组的计算
Manacher算法的精髓就是只需要从头到尾遍历str一次!!!
如何实现呢?我们需要设置两个标志位,centre和right,centre是当前回文子串的中心点,right是当前回文子串的右边界(right并不包含在回文子串中!)。一开始初始化,centre=right=-1。当i从0遍历到str.length-1时,需要动态的改变centre和right值,就可以计算出以str[i]为中心的回文半径存入len数组中。
这里需要注意的是,i一定>=centre,有了i的推进,centre和right才会更新。
什么时候要改变centre和right值呢,有两大情况:
当i值在right的左边:即 centre
图中 l 是 以cen为中心的回文子串的左边界,j 为 i 以 cen 为中心的对称点。我们要时刻注意到回文特性,即 str[l+1...cen-1] 与 str[r-1...cen+1]是相同的,所以以 i 为中心的回文子串有可能与以 j 为中心的回文子串是一样的,至少有一部分是相同的。
接下来,这里根据 以 j 为中心的回文子串的长度不同,会再细分成两种情况:
(1)以 j 为中心的回文子串很短,如下图
这种情况下,len[i]至少跟len[j]一样大,为什么至少一样大,什么时候len[i]比len[j]大呢,以上图为例,以 i 为中心的回文子串的右边界还可以向右扩展,这扩展的部分在 j 是无法匹配的。所以可以先将len[i]保存为len[j],再以i为中心左右两边扩展。
(2)以 j 为中心的回文子串很长,如下图
以 j 为中心的回文子串很长,可以看出左边界已经超过了 l ,但是以 i 为中心的回文字符长度,如果只按照回文性来判断,len[i]的值现在只能确定为 r-i ,len[i]
上面两个情况合并一下,即当i值在right的左边时,因为不知道当前j符合哪一种情况,所以 len=Math.min(len[2*cen-i], right-i),取两个中间的最小值即可。
当i值在right的右边:即 i>=right
说明以 i 为中心的回文子串还没有被访问过,所以当前len[i]的长度为1,即为i本身,再以i为中心左右两边扩展。
三 正则表达式匹配
题目:请实现一个函数用来匹配包括'.'和'*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。
例如,字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,但是与"aa.a"和"ab*a"均不匹配
当模式中的第二个字符不是“*”时:
1、如果字符串第一个字符和模式中的第一个字符相匹配,那么字符串和模式都后移一个字符,然后匹配剩余的。
2、如果 字符串第一个字符和模式中的第一个字符相不匹配,直接返回false。
而当模式中的第二个字符是“*”时:
如果字符串第一个字符跟模式第一个字符不匹配,则模式后移2个字符,继续匹配。如果字符串第一个字符跟模式第一个字符匹配,可以有3种匹配方式:
1、模式后移2字符,相当于x*被忽略(因为*前面可以是0个);
2、字符串后移1字符,模式后移2字符(相当于第3位是.匹配1个);
3、字符串后移1字符,模式不变,即继续匹配字符下一位,因为*可以匹配多位;
public class StringMatching {
public boolean match(char[] str, char[] pattern){
if(str == null || pattern == null){
return false;
}
int strIndex = 0;
int patternIndex = 0;
return matchCore(str, strIndex, pattern, patternIndex);
}
private boolean matchCore(char[] str, int strIndex, char[] pattern, int patternIndex) {
// 有效性检验,str到尾、pattern到尾,则匹配成功
if(strIndex == str.length && patternIndex == pattern.length){
return true;
}
// pattern先到尾,匹配失败
if(strIndex != str.length && patternIndex == pattern.length){
return false;
}
// 模式第2个是*,且字符串第1个跟模式串第1个匹配,分3种匹配模式;如果不匹配,则模式后移2位
if(patternIndex + 1 < pattern.length && pattern[patternIndex + 1] == '*'){
if((strIndex != str.length && pattern[patternIndex] == str[strIndex]) ||
(pattern[patternIndex] == '.' && strIndex != str.length)){
return matchCore(str, strIndex, pattern, patternIndex + 2) // 模式后移2,视为x*匹配0个字符
|| matchCore(str, strIndex + 1, pattern, patternIndex + 2) // 视为模式匹配1个字符
|| matchCore(str, strIndex + 1, pattern, patternIndex); // *匹配1个,再匹配str中的下一个
}else{
return matchCore(str, strIndex, pattern, patternIndex + 2);
}
}
// 模式第2个不是*,且字符串第1个跟模型第1个匹配,则都后移1位,否则直接返回false
if((strIndex != str.length && pattern[patternIndex] == str[strIndex])
|| pattern[patternIndex] == '.' && strIndex != str.length){
return matchCore(str, strIndex + 1, pattern, patternIndex + 1);
}
return false;
}
}
情况2可以被情况1和情况3包含。执行一次情况3,再执行一次情况1,就相当于情况2。所以上诉代码中的下面这种情况可以删去。
matchCore(str, strIndex + 1, pattern, patternIndex + 2) // 视为模式匹配1个字符
四 替换空格
题目:请实现一个函数,把字符串中的每个空格替换成 “%20”。 例如输入 “We are happy.”,则输出”We%20are%20happy.”。
1.时间复杂度为O(n^2)的解法
最直观的做法就是从头到尾扫描字符串,每次碰到空格字符的时候就进行替换。由于是把1个字符替换成3个字符,因此必须把空格后面的所有字符都要后移2个字节,否则就会有两个字符被覆盖了。
假设字符串的长度是n,对每个空格字符,需要移动后面O(n)个字符,因此对于含有O(n)个空格字符的字符串而言,总的时间效率是O(n^2)。
public class BlankReplace {
public static String replaceBlank(String input){
if(input == null){
return null;
}
StringBuffer outputBuffer = new StringBuffer();
for (int i = 0; i < input.length(); i++) {
if(input.charAt(i) == ' '){
outputBuffer.append("%");
outputBuffer.append("2");
outputBuffer.append("0");
}else{
outputBuffer.append(String.valueOf(input.charAt(i)));
}
}
return new String(outputBuffer);
}
public static void main(String[] args) {
String a = "We are happy.";
System.out.println(replaceBlank(a));
}
}
上面的思路是:从前往后扫描,下面我们改变下思路,从后往前扫描。
2.时间复杂度为O(n)的解法
我们可以先遍历一次字符串,这样就能够统计出字符串中的空格总数,则就可以计算出替换之后的字符串的总长度。每替换一个空格,长度增加2,因此替换以后字符串的长度等于原来的长度加上2乘以空格数目。
我们从字符串的后面开始复制和替换,首先准备两个指针,P1和P2,P1指向原始字符串的末尾,而P2指向替换之后的字符串的末尾。
接下来我们向前移动指针P1,逐个把它指向的字符复制到P2指向的位置,直到碰到空格为止。碰到空格后,把P1向前移动1格,在P2之前插入字符串”%20“,由于”%20“的长度为3,同时也要把P2向前移动3格。后面每次遇到空格后都这样做,直到P1和P2指向同一个位置,表明所有的空格已经替换完毕。
这种从后外向前的替换过程,所有的字符串只复制(移动)一次,因此这个算法的时间效率是O(n)。代码如下:
public class replaceSpace {
public static void replaceSpace(String str){
if(str == null || str.length() <= 0){
throw new IllegalArgumentException("输入的参数有问题");
}
// 字符串的初始长度
int length = str.length();
// 字符串替换后的长度
int newLength = str.length() + getBlankNum(str) * 2;
char[] tempArr = new char[newLength];
// 将str复制到新的 tempArr 数组中
System.arraycopy(str.toCharArray(), 0, tempArr, 0, str.toCharArray().length);
int indexOfOriginal = length - 1;
int indexOfNew = newLength - 1;
System.out.println("未替换空格时的字符串:");
printArray(str.toCharArray());
while(indexOfOriginal >= 0 && indexOfOriginal != indexOfNew){
if(tempArr[indexOfOriginal] == ' '){
tempArr[indexOfNew--] = '0';
tempArr[indexOfNew--] = '2';
tempArr[indexOfNew--] = '%';
}else{
tempArr[indexOfNew--] = tempArr[indexOfOriginal];
}
indexOfOriginal--; // 从后先前遍历
}
System.out.println("替换后的字符串为:");
printArray(tempArr);
}
// 获取空格数
private static int getBlankNum(String str) {
int count = 0; // 空格数
for (int i = 0; i < str.length(); i++) {
// 获取str中下标为i的元素,判断它是否为空格
String tempStr = String.valueOf(str.charAt(i));
if(tempStr.equals(" ")){
count++;
}
}
return count;
}
// 打印char[]数组
public static void printArray(char[] arr){
for(char i : arr){
System.out.print(i);
}
System.out.println();
}
// 测试
public static void main(String[] args) {
String str = "We are happy";
replaceSpace(str);
}
}
五 字符串的翻转和旋转及其应用
一、字符串的翻转
1、StringBuilder 实现
可以直接利用 StringBuilder 类的 reverse 方法直接实现。
public class StringReverse {
public static String reverseString(String str){
if(str == null || str.length() < 1){
return str;
}
StringBuilder sb = new StringBuilder(str);
return sb.reverse().toString();
}
}
2、双指针实现
public class StringReverse {
public static String reverseString(String str){
if(str == null || str.length() == 0){
return str;
}
char[] res = str.toCharArray();
int left = 0;
int right = str.length() - 1;
while(left < right){
char temp = res[left];
res[left++] = res[right];
res[right--] = temp;
}
return String.valueOf(res);
}
}
3、字符串翻转应用
LeetCode 151题:https://leetcode-cn.com/problems/reverse-words-in-a-string/
题目:给定一个字符串,逐个翻转字符串中的每个单词。
示例 1:
输入: "the sky is blue"
输出: "blue is sky the"
示例 2:
输入: " hello world! "
输出: "world! hello"
解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
示例 3:
输入: "a good example"
输出: "example good a"
解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。
说明:
1、无空格字符构成一个单词;
2、输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括;
3、如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。
public class ReverseWords_151 {
public String reverseWords(String s) {
if(s == null || s.length() < 1){
return s;
}
// split("\s+") 按空格,制表符等进行拆分
String str[] = s.trim().split("\\s+");
String res = "";
for(int i = str.length - 1; i > 0; i--){
res += str[i] + " ";
}
// str[0]在这里加是避免在循环中加了后会在新的字符串结尾多一个空格
return res + str[0];
}
}
知识点:
- \s表示 空格、回车、换行等空白符,+号表示一个或多个的意思;
- split("\s+") 按空格、制表符等进行拆分;
- split(" +") 按空格进行拆分(也就是说只有按空格键流出来的空白才会是拆分的一句),这里用这个也可以。
Example 1: Input: "Let's take LeetCode contest"
Output: "s'teL ekat edoCteeL tsetnoc"
public class Solution {
public String reverseWords(String s) {
String[] strs = s.split(" ");
StringBuilder sb = new StringBuilder();
for(String str: strs){
StringBuilder temp = new StringBuilder(str);
sb.append(temp.reverse());
sb.append(" ");
}
// 去掉最后一个空格
return sb.toString().trim();
}
}
二、字符串的旋转
旋转字符串:给定一个字符串和一个偏移量,根据偏移量旋转字符串(从左向右旋转)。
样例
对于字符串 "abcdefg".
offset = 0 => "abcdefg"
offset = 1 => "gabcdef"
offset = 2 => "fgabcde"
offset = 3 => "efgabcd"
1、三步翻转实现旋转【重点】
三步反转的思维比较巧妙,做法分为三步:
- 将字符串分为两部分,要移位的 m 个字符 X 和剩下的字符 Y。如 “abcdef”,则 X=“abc”,Y=“def”;
- 将 X 进行反转,得到 “cba”;将 Y 进行反转,得到 “fed”;
- 将反转后的 XY 合并字符串在进行反转,最后得到 “defabc”。
public class RotateString {
/**
* 字符串旋转
* @param str :原字符串
* @param m :要旋转的字符串长度
* @return
*/
private static String rotateString(String str, int m){
char[] chars = str.toCharArray();
reverseString(chars, 0, m - 1); // 旋转前半部分
reverseString(chars, m, str.length() - 1); // 旋转后半部分
reverseString(chars, 0, str.length() - 1); // 旋转整个数组
return String.valueOf(chars);
}
private static void reverseString(char[] chars, int left, int right){
while(left < right){
char temp = chars[left];
chars[left++] = chars[right];
chars[right--] = temp;
}
}
public static void main(String[] args) {
System.out.println(rotateString("abcdef", 2)); // cdefab
}
}
2、String 的 substring 方法实现
public class RotateString {
public static String rotateString2(String str, int m){
if(str == null || str.length() < 1 || m < 0){
return str;
}
if(m == str.length() - 1){
return str;
}else{
return str.substring(m, str.length()) + str.substring(0, m);
}
}
public static void main(String[] args) {
System.out.println(rotateString2("abcdef", 3)); // cdefab
}
}