字符串转换的就地算法
给定一个字符串,将所有偶数定位的元素移动到字符串的末尾。在移动元素时,保持所有偶数定位和奇数定位元素的相对顺序相同。例如,如果给定的字符串是“a1b2c3d4e5f6g7h8i9j1k2l3m4”,则将其就地转换为“abcdefghijklm1234567891234”,时间复杂度为 O(n)。
以下是步骤:
1.切出大小为 3^k + 1 形式的最大前缀子串。在这一步中,我们找到最大的非负整数 k 使得 3^k+1 小于或等于 n (字符串的长度)
2.应用循环领导迭代算法(下面已经讨论过),从索引 1、3、9……开始到这个子字符串。循环引导迭代算法将这个子串的所有项目移动到它们正确的位置,即所有的字母都移动到子串的左半边,所有的数字都移动到这个子串的右半边。
3.使用步骤#1 和#2 递归处理剩余的子字符串。
4.现在,我们只需要将处理后的子字符串连接在一起。从任何一端开始(比如从左边开始),选择两个子字符串,然后应用以下步骤:
…… 4.1反转第一个子字符串的后半部分。
…… 4.2反转第二个子串的前半部分。
…… 4.3将第一个子串的后半部分和第二个子串的前半部分颠倒在一起。
5.重复步骤#4,直到所有子字符串都连接起来。它类似于 k 路合并,其中第一个子字符串与第二个子字符串连接。结果与第三个合并,依此类推。
让我们通过一个例子来理解它:
请注意,我们在下面的示例中使用了 10、11 12 等值。仅将这些值视为单个字符。这些值用于提高可读性。
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
a 1 b 2 c 3 d 4 e 5 f 6 g 7 h 8 i 9 j 10 k 11 l 12 m 13
在分解为 3^k + 1 形式的大小后,两个子字符串分别形成大小为 10。第三子串由尺寸 4 构成,第四子串由尺寸 2 构成。
0 1 2 3 4 5 6 7 8 9
a 1 b 2 c 3 d 4 e 5
10 11 12 13 14 15 16 17 18 19
f 6 g 7 h 8 i 9 j 10
20 21 22 23
k 11 l 12
24 25
m 13
将循环领导者迭代算法应用于第一个子字符串后:
0 1 2 3 4 5 6 7 8 9
a b c d e 1 2 3 4 5
10 11 12 13 14 15 16 17 18 19
f 6 g 7 h 8 i 9 j 10
20 21 22 23
k 11 l 12
24 25
m 13
将循环领导者迭代算法应用于第二个子字符串后:
0 1 2 3 4 5 6 7 8 9
a b c d e 1 2 3 4 5
10 11 12 13 14 15 16 17 18 19
f g h i j 6 7 8 9 10
20 21 22 23
k 11 l 12
24 25
m 13
将循环领导者迭代算法应用于第三个子字符串后:
0 1 2 3 4 5 6 7 8 9
a b c d e 1 2 3 4 5
10 11 12 13 14 15 16 17 18 19
f g h i j 6 7 8 9 10
20 21 22 23
k l 11 12
24 25
m 13
将循环领导者迭代算法应用于第四个子字符串后:
0 1 2 3 4 5 6 7 8 9
a b c d e 1 2 3 4 5
10 11 12 13 14 15 16 17 18 19
f g h i j 6 7 8 9 10
20 21 22 23
k l 11 12
24 25
m 13
连接第一个子字符串和第二个子字符串:
1. 第一个子串的后半部分和第二个子串的前半部分颠倒了。
0 1 2 3 4 5 6 7 8 9
a b c d e 5 4 3 2 1 <--------- First Sub-string
10 11 12 13 14 15 16 17 18 19
j i h g f 6 7 8 9 10 <--------- Second Sub-string
20 21 22 23
k l 11 12
24 25
m 13
2. 第一个子串的后半部分和第二个子串的前半部分颠倒在一起(它们合并了,即现在只有三个子串)。
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
a b c d e f g h i j 1 2 3 4 5 6 7 8 9 10
20 21 22 23
k l 11 12
24 25
m 13
连接第一个子字符串和第二个子字符串:
1. 第一个子串的后半部分和第二个子串的前半部分颠倒了。
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
a b c d e f g h i j 10 9 8 7 6 5 4 3 2 1 <--------- First Sub-string
20 21 22 23
l k 11 12 <--------- Second Sub-string
24 25
m 13
2. 第一个子串的后半部分和第二个子串的前半部分颠倒在一起(它们被合并,即现在只有两个子串)。
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
a b c d e f g h i j k l 1 2 3 4 5 6 7 8 9 10 11 12
24 25
m 13
连接第一个子字符串和第二个子字符串:
1. 第一个子串的后半部分和第二个子串的前半部分颠倒了。
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
a b c d e f g h i j k l 12 11 10 9 8 7 6 5 4 3 2 1 <----- First Sub-string
24 25
m 13 <----- Second Sub-string
2. 第一个子串的后半部分和第二个子串的前半部分颠倒在一起(它们被合并,即现在只有一个子串)。
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
a b c d e f g h i j k l m 1 2 3 4 5 6 7 8 9 10 11 12 13
由于所有子字符串都已连接在一起,我们就完成了。
循环领导者迭代算法如何工作?
让我们通过一个例子来理解它:
Input:
0 1 2 3 4 5 6 7 8 9
a 1 b 2 c 3 d 4 e 5
Output:
0 1 2 3 4 5 6 7 8 9
a b c d e 1 2 3 4 5
Old index New index
0 0
1 5
2 1
3 6
4 2
5 7
6 3
7 8
8 4
9 9
设 len 为字符串的长度。如果我们仔细观察,我们会发现新的指数由下式给出:
if( oldIndex is odd )
newIndex = len / 2 + oldIndex / 2;
else
newIndex = oldIndex / 2;
因此,问题归结为根据上述公式将元素转移到新的索引。
循环领导者迭代算法将从 3^k 形式的索引开始应用,从 k = 0 开始。
以下是步骤:
1.在位置 i 找到项目的新位置。在将此项目放在新位置之前,请将元素的备份保留在新位置。现在,将项目放在新位置。
2.对新位置重复步骤#1,直到完成一个循环,即直到程序回到起始位置。
3.将循环领导者迭代算法应用于 3^k 形式的下一个索引。重复此步骤直到 3^k < len。
考虑大小为 28 的输入数组:
第一次循环领导者迭代,从索引 1 开始:
1->14->7->17->22->11->19->23->25->26->13->20->10->5->16->8->4- >2->1
第二个循环领导者迭代,从索引 3 开始:
3->15->21->24->12->6->3
第三次循环领导者迭代,从索引 9 开始:
9->18->9
基于上述算法,代码如下:
C++
// C++ implementation of above approach
#include
using namespace std;
// A utility function to swap characters
void swap ( char* a, char* b )
{
char t = *a;
*a = *b;
*b = t;
}
// A utility function to reverse string str[low..high]
void reverse ( char* str, int low, int high )
{
while ( low < high )
{
swap( &str[low], &str[high] );
++low;
--high;
}
}
// Cycle leader algorithm to move all even
// positioned elements at the end.
void cycleLeader ( char* str, int shift, int len )
{
int j;
char item;
for (int i = 1; i < len; i *= 3 )
{
j = i;
item = str[j + shift];
do
{
// odd index
if ( j & 1 )
j = len / 2 + j / 2;
// even index
else
j /= 2;
// keep the back-up of element at new position
swap (&str[j + shift], &item);
}
while ( j != i );
}
}
// The main function to transform a string. This function
// mainly uses cycleLeader() to transform
void moveNumberToSecondHalf( char* str )
{
int k, lenFirst;
int lenRemaining = strlen( str );
int shift = 0;
while ( lenRemaining )
{
k = 0;
// Step 1: Find the largest prefix
// subarray of the form 3^k + 1
while ( pow( 3, k ) + 1 <= lenRemaining )
k++;
lenFirst = pow( 3, k - 1 ) + 1;
lenRemaining -= lenFirst;
// Step 2: Apply cycle leader algorithm
// for the largest subarrau
cycleLeader ( str, shift, lenFirst );
// Step 4.1: Reverse the second half of first subarray
reverse ( str, shift / 2, shift - 1 );
// Step 4.2: Reverse the first half of second sub-string.
reverse ( str, shift, shift + lenFirst / 2 - 1 );
// Step 4.3 Reverse the second half of first sub-string
// and first half of second sub-string together
reverse ( str, shift / 2, shift + lenFirst / 2 - 1 );
// Increase the length of first subarray
shift += lenFirst;
}
}
// Driver program to test above function
int main()
{
char str[] = "a1b2c3d4e5f6g7";
moveNumberToSecondHalf( str );
cout<
Java
// Java implementation of above approach
import java.util.*;
class GFG{
static char []str;
// A utility function to reverse
// String str[low..high]
static void reverse(int low, int high)
{
while (low < high)
{
char t = str[low];
str[low] = str[high];
str[high] = t;
++low;
--high;
}
}
// Cycle leader algorithm to move all even
// positioned elements at the end.
static void cycleLeader(int shift, int len)
{
int j;
char item;
for(int i = 1; i < len; i *= 3)
{
j = i;
item = str[j + shift];
do
{
// odd index
if (j % 2 == 1)
j = len / 2 + j / 2;
// even index
else
j /= 2;
// Keep the back-up of element at
// new position
char t = str[j + shift];
str[j + shift] = item;
item = t;
}
while (j != i);
}
}
// The main function to transform a String.
// This function mainly uses cycleLeader()
// to transform
static void moveNumberToSecondHalf()
{
int k, lenFirst;
int lenRemaining = str.length;
int shift = 0;
while (lenRemaining > 0)
{
k = 0;
// Step 1: Find the largest prefix
// subarray of the form 3^k + 1
while (Math.pow(3, k) + 1 <= lenRemaining)
k++;
lenFirst = (int)Math.pow(3, k - 1) + 1;
lenRemaining -= lenFirst;
// Step 2: Apply cycle leader algorithm
// for the largest subarrau
cycleLeader(shift, lenFirst);
// Step 4.1: Reverse the second half
// of first subarray
reverse(shift / 2, shift - 1);
// Step 4.2: Reverse the first half
// of second sub-String.
reverse(shift, shift + lenFirst / 2 - 1);
// Step 4.3 Reverse the second half
// of first sub-String and first half
// of second sub-String together
reverse(shift / 2, shift + lenFirst / 2 - 1);
// Increase the length of first subarray
shift += lenFirst;
}
}
// Driver code
public static void main(String[] args)
{
String st = "a1b2c3d4e5f6g7";
str = st.toCharArray();
moveNumberToSecondHalf();
System.out.print(str);
}
}
// This code is contributed by Princi Singh
Python3
# Python implementation of above approach
# A utility function to reverse string str[low..high]
def Reverse(string: list, low: int, high: int):
while low < high:
string[low], string[high] = string[high], string[low]
low += 1
high -= 1
# Cycle leader algorithm to move all even
# positioned elements at the end.
def cycleLeader(string: list, shift: int, len: int):
i = 1
while i < len:
j = i
item = string[j + shift]
while True:
# odd index
if j & 1:
j = len // 2 + j // 2
# even index
else:
j //= 2
# keep the back-up of element at new position
string[j + shift], item = item, string[j + shift]
if j == i:
break
i *= 3
# The main function to transform a string. This function
# mainly uses cycleLeader() to transform
def moveNumberToSecondHalf(string: list):
k, lenFirst = 0, 0
lenRemaining = len(string)
shift = 0
while lenRemaining:
k = 0
# Step 1: Find the largest prefix
# subarray of the form 3^k + 1
while pow(3, k) + 1 <= lenRemaining:
k += 1
lenFirst = pow(3, k - 1) + 1
lenRemaining -= lenFirst
# Step 2: Apply cycle leader algorithm
# for the largest subarrau
cycleLeader(string, shift, lenFirst)
# Step 4.1: Reverse the second half of first subarray
Reverse(string, shift // 2, shift - 1)
# Step 4.2: Reverse the first half of second sub-string
Reverse(string, shift, shift + lenFirst // 2 - 1)
# Step 4.3 Reverse the second half of first sub-string
# and first half of second sub-string together
Reverse(string, shift // 2, shift + lenFirst // 2 - 1)
# Increase the length of first subarray
shift += lenFirst
# Driver Code
if __name__ == "__main__":
string = "a1b2c3d4e5f6g7"
string = list(string)
moveNumberToSecondHalf(string)
print(''.join(string))
# This code is contributed by
# sanjeev2552
C#
// C# implementation of
// the above approach
using System;
class GFG{
static char []str;
// A utility function to
// reverse String str[low
// ..high]
static void reverse(int low,
int high)
{
while (low < high)
{
char t = str[low];
str[low] = str[high];
str[high] = t;
++low;
--high;
}
}
// Cycle leader algorithm to
// move all even positioned
// elements at the end.
static void cycleLeader(int shift,
int len)
{
int j;
char item;
for(int i = 1;
i < len; i *= 3)
{
j = i;
item = str[j + shift];
do
{
// odd index
if (j % 2 == 1)
j = len / 2 + j / 2;
// even index
else
j /= 2;
// Keep the back-up of
// element at new position
char t = str[j + shift];
str[j + shift] = item;
item = t;
}
while (j != i);
}
}
// The main function to transform
// a String. This function mainly
// uses cycleLeader() to transform
static void moveNumberToSecondHalf()
{
int k, lenFirst;
int lenRemaining = str.Length;
int shift = 0;
while (lenRemaining > 0)
{
k = 0;
// Step 1: Find the largest prefix
// subarray of the form 3^k + 1
while (Math.Pow(3, k) +
1 <= lenRemaining)
k++;
lenFirst = (int)Math.Pow(3,
k - 1) + 1;
lenRemaining -= lenFirst;
// Step 2: Apply cycle leader
// algorithm for the largest
// subarrau
cycleLeader(shift, lenFirst);
// Step 4.1: Reverse the second
// half of first subarray
reverse(shift / 2,
shift - 1);
// Step 4.2: Reverse the
// first half of second
// sub-String.
reverse(shift, shift +
lenFirst / 2 - 1);
// Step 4.3 Reverse the second
// half of first sub-String and
// first half of second sub-String
// together
reverse(shift / 2, shift +
lenFirst / 2 - 1);
// Increase the length of
// first subarray
shift += lenFirst;
}
}
// Driver code
public static void Main(String[] args)
{
String st = "a1b2c3d4e5f6g7";
str = st.ToCharArray();
moveNumberToSecondHalf();
Console.Write(str);
}
}
// This code is contributed by 29AjayKumar
Javascript
输出:
abcdefg1234567
单击此处查看各种测试用例。
笔记:
1.如果数组大小已经是 3^k + 1 的形式,我们可以直接应用循环领导者迭代算法。没有必要加入。
2. Cycle Leader 迭代算法只适用于大小为 3^k + 1 的数组。
时间复杂度 O(n) 怎么样?
一个循环中的每个项目最多移动一次。因此,循环领导算法的时间复杂度为 O(n)。反向操作的时间复杂度为 O(n)。我们将很快更新算法时间复杂度的数学证明。
锻炼:
给定“abcdefg1234567”形式的字符串,将其就地转换为“a1b2c3d4e5f6g7”,时间复杂度为 O(n)。