在之前的一篇文章(百度2012年校招笔试题——pszStringRotate)中,我介绍了一种时间复杂度O(n),空间复杂度O(1)的字符串循环位移算法,其核心思想是交换和冲突处理。
其实这只是线性时间复杂度字符串变换算法的一个特例——变换函数为f(x) = x + offset (offset为偏移,取全体整数)。倘若存在一个更一般的变换函数f(x),f(x)不一定是线性函数,甚至不可导或者不连续,此算法的复杂度是否还能维持?答案是肯定的,可以证明其时间复杂度为O(cn),其中c为变换函数的图像不可导点数+1,即此分段函数的段数。
为什么这么说呢?先来看看pszStringRotate的改写版,使用了变换函数:
#include <cstdio>
#include <cstring>
using namespace std;
char str[1010];
int len;
int shift;
inline void swap(char *c1, char *c2)
{
char tmp = *c1;
*c1 = *c2;
*c2 = tmp;
}
inline int transform(int x)
{
return x - shift;
}
void stringTransform(char *pszString)
{
int cnt = 0; // Current operate position on pszString
int target = transform(cnt);
if (target < 0)
target += (-target / len + 1) * len;
target %= len;
for (int i = 1; i <= len - 1; i++) // Operation times upper bound : len - 1
{
while (cnt >= target && i <= len - 1) // Collision found
{
cnt++; // Update current operate position
cnt %= len;
target = transform(cnt);
if (target < 0)
target += (-target / len + 1) * len;
target %= len;
i++; // Reduce loop step
}
swap(&pszString[cnt], &pszString[target]); // Do operate
target = transform(target);
if (target < 0)
target += (-target / len + 1) * len;
target %= len;
}
}
int main ()
{
while (~scanf("%s%d", str, &shift))
{
len = strlen(str);
stringTransform(str);
printf("%s\n", str);
memset(str, 0, sizeof(str));
}
return 0;
}
stringTransform的时空复杂度均和pszStringRotate相同,这归功于transform函数的复杂度,为O(1),stringTransform中有三次transform调用。特殊地,如果变换规则十分不明朗(变换函数段数接近n,几近一点为一段),则算法时间复杂度为O(n^2)。
再来看一个更复杂的变换函数:f(x) = x / 2 (x为偶数) len - ((x + 1) / 2) (x为奇数),其含义是将一个递增序列变为合唱队形(如12345678 -> 13578642 或 12345 -> 13542)。
#include <cstdio>
#include <cstring>
using namespace std;
char str[1010];
int len;
inline void swap(char *c1, char *c2)
{
char tmp = *c1;
*c1 = *c2;
*c2 = tmp;
}
inline int transform(int x)
{
if (x % 2)
return len - ((x + 1) >> 1);
return x >> 1;
}
void stringTransform(char *pszString)
{
int cnt = 0; // Current operate position on pszString
int target = transform(cnt);
if (target < 0)
target += (-target / len + 1) * len;
target %= len;
for (int i = 1; i <= len && cnt <= len - 1; i++) // Operation times upper bound : len - 1
{
int flag = 0;
while (cnt >= target && i <= len && cnt <= len - 1) // Collision found
{
cnt++; // Update current operate position
cnt %= len;
target = transform(cnt);
if (target < 0)
target += (-target / len + 1) * len;
target %= len;
i++; // Reduce loop step
flag = 1;
}
if (flag && i >= len)
break;
flag = 0;
swap(&pszString[cnt], &pszString[target]); // Do operate
target = transform(target);
if (target < 0)
target += (-target / len + 1) * len;
target %= len;
}
}
int main ()
{
while (~scanf("%s", str))
{
len = strlen(str);
stringTransform(str);
printf("%s\n", str);
memset(str, 0, sizeof(str));
}
return 0;
}
百度某年的面试题如下:给定一个数组,其中元素各不相同,将其排成合唱队形(同上述规则)。
有了上述的算法,本题就迎刃而解了,首先将数组排成递增,需要时间O(nlogn),再利用上述线性时间复杂度字符串变换算法,总的时间复杂度仍为O(nlogn)。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int num[1010];
int len;
inline int cmp(int a, int b)
{
return a < b;
}
inline void swap(int *c1, int *c2)
{
int tmp = *c1;
*c1 = *c2;
*c2 = tmp;
}
inline int transform(int x)
{
if (x % 2)
return len - ((x + 1) >> 1);
return x >> 1;
}
void stringTransform(int *pszString)
{
int cnt = 0; // Current operate position on pszString
int target = transform(cnt);
if (target < 0)
target += (-target / len + 1) * len;
target %= len;
for (int i = 1; i <= len && cnt <= len - 1; i++) // Operation times upper bound : len - 1
{
int flag = 0;
while (cnt >= target && i <= len && cnt <= len - 1) // Collision found
{
cnt++; // Update current operate position
cnt %= len;
target = transform(cnt);
if (target < 0)
target += (-target / len + 1) * len;
target %= len;
i++; // Reduce loop step
flag = 1;
}
if (flag && i >= len)
break;
flag = 0;
swap(&pszString[cnt], &pszString[target]); // Do operate
target = transform(target);
if (target < 0)
target += (-target / len + 1) * len;
target %= len;
}
}
int main ()
{
while (~scanf("%d", &len))
{
for (int i = 0; i < len; i++)
scanf("%d", &num[i]);
sort(num, num + len, cmp);
stringTransform(num);
for (int i = 0; i < len; i++)
printf("%d ", num[i]);
printf("\n");
}
return 0;
}
测试结果: