📜  字符串转换的就地算法

📅  最后修改于: 2022-05-13 01:57:07.677000             🧑  作者: Mango

字符串转换的就地算法

给定一个字符串,将所有偶数定位的元素移动到字符串的末尾。在移动元素时,保持所有偶数定位和奇数定位元素的相对顺序相同。例如,如果给定的字符串是“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)。