M个位置的圆移阵列最快的算法是什么?
例如,[3 4 5 2 3 1 4]
移位 M = 2 个位置应该是[1 4 3 4 5 2 3]
。
非常感谢。
M个位置的圆移阵列最快的算法是什么?
例如,[3 4 5 2 3 1 4]
移位 M = 2 个位置应该是[1 4 3 4 5 2 3]
。
非常感谢。
如果您想要 O(n) 时间并且不需要额外的内存使用(因为指定了数组),请使用 Jon Bentley 的书“Programming Pearls 2nd Edition”中的算法。它交换所有元素两次。不如使用链表快,但使用更少的内存并且概念上很简单。
shiftArray( theArray, M ):
size = len( theArray )
assert( size > M )
reverseArray( theArray, 0, size - 1 )
reverseArray( theArray, 0, M - 1 )
reverseArray( theArray, M, size - 1 )
reverseArray( anArray, startIndex, endIndex ) 将元素的顺序从 startIndex 反转到 endIndex,包括端点。
要求最快的问题。反转 3 次是最简单的,但每个元素恰好移动两次,需要 O(N) 时间和 O(1) 空间。也可以在 O(N) 时间和 O(1) 空间中对一个数组进行循环移位,将每个元素仅移动一次。
N=9
我们可以用一个循环循环移位一个长度数组M=1
:
tmp = arr[0]; arr[0] = arr[1]; ... arr[7] = arr[8]; arr[8] = tmp;
如果N=9
,M=3
我们可以用三个循环循环移位:
tmp = arr[0]; arr[0] = arr[3]; arr[3] = tmp;
tmp = arr[1]; arr[1] = arr[4]; arr[4] = tmp;
tmp = arr[2]; arr[2] = arr[5]; arr[5] = tmp;
请注意,每个元素被读取一次并写入一次。
N=9, M=3
第一个循环以黑色显示,数字表示操作顺序。第二个和第三个循环以灰色显示。
所需的周期数是 和 的最大公约数( GCD) 。如果 GCD 为 3,我们在每个 开始一个循环。使用二进制 GCD 算法计算 GCD 速度很快。N
M
{0,1,2}
示例代码:
// n is length(arr)
// shift is how many place to cycle shift left
void cycle_shift_left(int arr[], int n, int shift) {
int i, j, k, tmp;
if(n <= 1 || shift == 0) return;
shift = shift % n; // make sure shift isn't >n
int gcd = calc_GCD(n, shift);
for(i = 0; i < gcd; i++) {
// start cycle at i
tmp = arr[i];
for(j = i; 1; j = k) {
k = j+shift;
if(k >= n) k -= n; // wrap around if we go outside array
if(k == i) break; // end of cycle
arr[j] = arr[k];
}
arr[j] = tmp;
}
}
// circle shift an array left (towards index zero)
// - ptr array to shift
// - n number of elements
// - es size of elements in bytes
// - shift number of places to shift left
void array_cycle_left(void *_ptr, size_t n, size_t es, size_t shift)
{
char *ptr = (char*)_ptr;
if(n <= 1 || !shift) return; // cannot mod by zero
shift = shift % n; // shift cannot be greater than n
// Using GCD
size_t i, j, k, gcd = calc_GCD(n, shift);
char tmp[es];
// i is initial starting position
// Copy from k -> j, stop if k == i, since arr[i] already overwritten
for(i = 0; i < gcd; i++) {
memcpy(tmp, ptr+es*i, es); // tmp = arr[i]
for(j = i; 1; j = k) {
k = j+shift;
if(k >= n) k -= n;
if(k == i) break;
memcpy(ptr+es*j, ptr+es*k, es); // arr[j] = arr[k];
}
memcpy(ptr+es*j, tmp, es); // arr[j] = tmp;
}
}
// cycle right shifts away from zero
void array_cycle_right(void *_ptr, size_t n, size_t es, size_t shift)
{
if(!n || !shift) return; // cannot mod by zero
shift = shift % n; // shift cannot be greater than n
// cycle right by `s` is equivalent to cycle left by `n - s`
array_cycle_left(_ptr, n, es, n - shift);
}
// Get Greatest Common Divisor using binary GCD algorithm
// http://en.wikipedia.org/wiki/Binary_GCD_algorithm
unsigned int calc_GCD(unsigned int a, unsigned int b)
{
unsigned int shift, tmp;
if(a == 0) return b;
if(b == 0) return a;
// Find power of two divisor
for(shift = 0; ((a | b) & 1) == 0; shift++) { a >>= 1; b >>= 1; }
// Remove remaining factors of two from a - they are not common
while((a & 1) == 0) a >>= 1;
do
{
// Remove remaining factors of two from b - they are not common
while((b & 1) == 0) b >>= 1;
if(a > b) { tmp = a; a = b; b = tmp; } // swap a,b
b = b - a;
}
while(b != 0);
return a << shift;
}
编辑:由于缓存局部性,该算法与数组反转(当N
大和小)相比也可能具有更好的性能,因为我们以小步骤循环数组。M
最后一点:如果你的数组很小,三重反转很简单。如果您有一个大数组,那么计算 GCD 以将移动次数减少 2 倍是值得的。参考:http ://www.geeksforgeeks.org/array-rotation/
这只是一个代表问题。将当前索引保留为整数变量,并在遍历数组时使用模运算符来知道何时回绕。然后,移位仅更改当前索引的值,将其包裹在数组的大小周围。这当然是 O(1)。
例如:
int index = 0;
Array a = new Array[SIZE];
get_next_element() {
index = (index + 1) % SIZE;
return a[index];
}
shift(int how_many) {
index = (index+how_many) % SIZE;
}
用指针设置它,几乎不需要时间。每个元素都指向下一个,而“最后一个”(没有最后一个;毕竟,您说它是循环的)指向第一个。一个指向“开始”(第一个元素)的指针,也许还有一个长度,你就有了你的数组。现在,要进行轮班,您只需沿着圆圈移动起始指针即可。
要求一个好的算法,你就会得到明智的想法。要求最快,你会得到奇怪的想法!
该算法在 O(n) 时间和 O(1) 空间中运行。这个想法是跟踪移位中的每个循环组(按nextGroup
变量编号)。
var shiftLeft = function(list, m) {
var from = 0;
var val = list[from];
var nextGroup = 1;
for(var i = 0; i < list.length; i++) {
var to = ((from - m) + list.length) % list.length;
if(to == from)
break;
var temp = list[to];
list[to] = val;
from = to;
val = temp;
if(from < nextGroup) {
from = nextGroup++;
val = list[from];
}
}
return list;
}
def shift(nelements, k):
result = []
length = len(nelements)
start = (length - k) % length
for i in range(length):
result.append(nelements[(start + i) % length])
return result
此代码即使在负移位 k 上也能正常工作
C 数组ShiftRight 函数。如果 shift 为负,则函数将数组左移。它针对更少的内存使用进行了优化。运行时间为 O(n)。
void arrayShiftRight(int array[], int size, int shift) {
int len;
//cut extra shift
shift %= size;
//if shift is less then 0 - redirect shifting left
if ( shift < 0 ) {
shift += size;
}
len = size - shift;
//choosing the algorithm which needs less memory
if ( shift < len ) {
//creating temporary array
int tmpArray[shift];
//filling tmp array
for ( int i = 0, j = len; i < shift; i++, j++ ) {
tmpArray[i] = array[j];
}
//shifting array
for ( int i = size - 1, j = i - shift; j >= 0; i--, j-- ) {
array[i] = array[j];
}
//inserting lost values from tmp array
for ( int i = 0; i < shift; i++ ) {
array[i] = tmpArray[i];
}
} else {
//creating temporary array
int tmpArray[len];
//filling tmp array
for ( int i = 0; i < len; i++ ) {
tmpArray[i] = array[i];
}
//shifting array
for ( int i = 0, j = len; j < size; i++, j++ ) {
array[i] = array[j];
}
//inserting lost values from tmp array
for ( int i = shift, j = 0; i < size; i++, j++ ) {
array[i] = tmpArray[j];
}
}
}
一个非常简单的解决方案。这是一种非常快速的方法,这里我使用一个大小相同或原始的临时数组,并在最后附加到原始变量。该方法使用 O(n) 时间复杂度和 O(n) 空间复杂度,实现起来非常简单。
int[] a = {1,2,3,4,5,6};
int k = 2;
int[] queries = {2,3};
int[] temp = new int[a.length];
for (int i = 0; i<a.length; i++)
temp[(i+k)%a.length] = a[i];
a = temp;
这应该可以循环移动一个数组: Input : { 1, 2, 3, 5, 6, 7, 8 }; 输出值出现在 forloops 之后的数组中:{8,7,1,2,3,5,6,8,7}
class Program
{
static void Main(string[] args)
{
int[] array = { 1, 2, 3, 5, 6, 7, 8 };
int index = 2;
int[] tempArray = new int[array.Length];
array.CopyTo(tempArray, 0);
for (int i = 0; i < array.Length - index; i++)
{
array[index + i] = tempArray[i];
}
for (int i = 0; i < index; i++)
{
array[i] = tempArray[array.Length -1 - i];
}
}
}
这里是一个简单高效的通用C++就地旋转函数,不到10行。
摘自我对另一个问题的回答。如何旋转数组?
#include <iostream>
#include <vector>
// same logic with STL implementation, but simpler, since no return value needed.
template <typename Iterator>
void rotate_by_gcd_like_swap(Iterator first, Iterator mid, Iterator last) {
if (first == mid) return;
Iterator old = mid;
for (; mid != last;) {
std::iter_swap(first, mid);
++first, ++mid;
if (first == old) old = mid; // left half exhausted
else if (mid == last) mid = old;
}
}
int main() {
using std::cout;
std::vector<int> v {0,1,2,3,4,5,6,7,8,9};
cout << "before rotate: ";
for (auto x: v) cout << x << ' '; cout << '\n';
int k = 7;
rotate_by_gcd_like_swap(v.begin(), v.begin() + k, v.end());
cout << " after rotate: ";
for (auto x: v) cout << x << ' '; cout << '\n';
cout << "sz = " << v.size() << ", k = " << k << '\n';
}
根据您使用的数据结构,您可以在 O(1) 中完成。我认为最快的方法是以链表的形式保存数组,并有一个哈希表可以在数组中的“索引”到条目的“指针”之间转换。这样你可以在 O(1) 中找到相关的头和尾,并在 O(1) 中进行重新连接(并在 O(1) 中切换后更新哈希表)。这当然是一个非常“混乱”的解决方案,但如果你感兴趣的只是移位的速度,那会做(以在数组中更长的插入和查找为代价,但它仍然会保持 O( 1))
如果你有一个纯数组中的数据,我认为你不能避免 O(n)。
编码方面,这取决于您使用的语言。
例如,在 Python 中,您可以“切片”它(假设 n 是移位大小):
result = original[-n:]+original[:-n]
(我知道哈希查找在理论上不是 O(1) 但我们在这里是实用的而不是理论上的,至少我希望如此......)
circleArray
有一些错误,并非在所有情况下都有效!
循环必须继续while i1 < i2
NOT i1 < last - 1
。
void Shift(int* _array, int _size, int _moves)
{
_moves = _size - _moves;
int i2 = _moves;
int i1 = -1;
while(++i1 < i2)
{
int tmp = _array[i2];
_array[i2] = _array[i1];
_array[i1] = tmp;
if(++i2 == _size) i2 = _moves;
}
}
static int [] shift(int arr[], int index, int k, int rem)
{
if(k <= 0 || arr == null || arr.length == 0 || rem == 0 || index >= arr.length)
{
return arr;
}
int temp = arr[index];
arr = shift(arr, (index+k) % arr.length, k, rem - 1);
arr[(index+k) % arr.length] = temp;
return arr;
}
如果您对 Java 实现感兴趣,请参阅此内容:
红宝石示例:
def move_cyclic2 array, move_cnt
move_cnt = array.length - move_cnt % array.length
if !(move_cnt == 0 || move_cnt == array.length)
array.replace( array[move_cnt..-1] + array[0...move_cnt] )
end
end
理论上,最快的是这样的循环:
if (begin != middle && middle != end)
{
for (i = middle; ; )
{
swap(arr[begin++], arr[i++]);
if (begin == middle && i == end) { break; }
if (begin == middle) { middle = i; }
else if (i == end) { i = middle; }
}
}
在实践中,您应该对其进行分析并查看。
这是另一个(C++):
void shift_vec(vector<int>& v, size_t a)
{
size_t max_s = v.size() / a;
for( size_t s = 1; s < max_s; ++s )
for( size_t i = 0; i < a; ++i )
swap( v[i], v[s*a+i] );
for( size_t i = 0; i < a; ++i )
swap( v[i], v[(max_s*a+i) % v.size()] );
}
当然,它不如著名的反向三倍解决方案那么优雅,但根据机器的不同,它可以同样快速。
我的一个朋友在开玩笑时问我如何移动数组,我想出了这个解决方案(见 ideone 链接),现在我看到了你的,有人似乎有点深奥。
看看这里。
#include <iostream>
#include <assert.h>
#include <cstring>
using namespace std;
struct VeryElaboratedDataType
{
int a;
int b;
};
namespace amsoft
{
namespace inutils
{
enum EShiftDirection
{
Left,
Right
};
template
<typename T,size_t len>
void infernalShift(T infernalArray[],int positions,EShiftDirection direction = EShiftDirection::Right)
{
//assert the dudes
assert(len > 0 && "what dude?");
assert(positions >= 0 && "what dude?");
if(positions > 0)
{
++positions;
//let's make it fit the range
positions %= len;
//if y want to live as a forcio, i'l get y change direction by force
if(!direction)
{
positions = len - positions;
}
// here I prepare a fine block of raw memory... allocate once per thread
static unsigned char WORK_BUFFER[len * sizeof(T)];
// std::memset (WORK_BUFFER,0,len * sizeof(T));
// clean or not clean?, well
// Hamlet is a prince, a prince does not clean
//copy the first chunk of data to the 0 position
std::memcpy(WORK_BUFFER,reinterpret_cast<unsigned char *>(infernalArray) + (positions)*sizeof(T),(len - positions)*sizeof(T));
//copy the second chunk of data to the len - positions position
std::memcpy(WORK_BUFFER+(len - positions)*sizeof(T),reinterpret_cast<unsigned char *>(infernalArray),positions * sizeof(T));
//now bulk copy back to original one
std::memcpy(reinterpret_cast<unsigned char *>(infernalArray),WORK_BUFFER,len * sizeof(T));
}
}
template
<typename T>
void printArray(T infernalArrayPrintable[],int len)
{
for(int i=0;i<len;i++)
{
std::cout << infernalArrayPrintable[i] << " ";
}
std::cout << std::endl;
}
template
<>
void printArray(VeryElaboratedDataType infernalArrayPrintable[],int len)
{
for(int i=0;i<len;i++)
{
std::cout << infernalArrayPrintable[i].a << "," << infernalArrayPrintable[i].b << " ";
}
std::cout << std::endl;
}
}
}
int main() {
// your code goes here
int myInfernalArray[] = {1,2,3,4,5,6,7,8,9};
VeryElaboratedDataType myInfernalArrayV[] = {{1,1},{2,2},{3,3},{4,4},{5,5},{6,6},{7,7},{8,8},{9,9}};
amsoft::inutils::printArray(myInfernalArray,sizeof(myInfernalArray)/sizeof(int));
amsoft::inutils::infernalShift<int,sizeof(myInfernalArray)/sizeof(int)>(myInfernalArray,4);
amsoft::inutils::printArray(myInfernalArray,sizeof(myInfernalArray)/sizeof(int));
amsoft::inutils::infernalShift<int,sizeof(myInfernalArray)/sizeof(int)>(myInfernalArray,4,amsoft::inutils::EShiftDirection::Left);
amsoft::inutils::printArray(myInfernalArray,sizeof(myInfernalArray)/sizeof(int));
amsoft::inutils::infernalShift<int,sizeof(myInfernalArray)/sizeof(int)>(myInfernalArray,10);
amsoft::inutils::printArray(myInfernalArray,sizeof(myInfernalArray)/sizeof(int));
amsoft::inutils::printArray(myInfernalArrayV,sizeof(myInfernalArrayV)/sizeof(VeryElaboratedDataType));
amsoft::inutils::infernalShift<VeryElaboratedDataType,sizeof(myInfernalArrayV)/sizeof(VeryElaboratedDataType)>(myInfernalArrayV,4);
amsoft::inutils::printArray(myInfernalArrayV,sizeof(myInfernalArrayV)/sizeof(VeryElaboratedDataType));
amsoft::inutils::infernalShift<VeryElaboratedDataType,sizeof(myInfernalArrayV)/sizeof(VeryElaboratedDataType)>(myInfernalArrayV,4,amsoft::inutils::EShiftDirection::Left);
amsoft::inutils::printArray(myInfernalArrayV,sizeof(myInfernalArrayV)/sizeof(VeryElaboratedDataType));
amsoft::inutils::infernalShift<VeryElaboratedDataType,sizeof(myInfernalArrayV)/sizeof(VeryElaboratedDataType)>(myInfernalArrayV,10);
amsoft::inutils::printArray(myInfernalArrayV,sizeof(myInfernalArrayV)/sizeof(VeryElaboratedDataType));
return 0;
}
这种方法将完成这项工作:
public static int[] solution1(int[] A, int K) {
int temp[] = new int[A.length];
int count = 0;
int orignalItration = (K < A.length) ? K :(K%A.length);
for (int i = orignalItration; i < A.length; i++) {
temp[i] = A[count++];
}
for (int i = 0; i < orignalItration; i++) {
temp[i] = A[count++];
}
return temp;
}
与@IsaacTurner 类似,由于不必要的复制而不那么优雅,但实现很短。
这个想法 - 将索引 0 上的元素 A 与位于 A 目的地的元素 B 交换。现在 B 是第一个。将它与位于 B 的目的地的元素 C 交换。继续直到目的地不为 0。
如果最大公约数不是 1,那么您还没有完成 - 您需要继续交换,但现在在起点和终点使用索引 1。
继续,直到您的起始位置不是 gcd。
int gcd(int a, int b) => b == 0 ? a : gcd(b, a % b);
public int[] solution(int[] A, int K)
{
for (var i = 0; i < gcd(A.Length, K); i++)
{
for (var j = i; j < A.Length - 1; j++)
{
var destIndex = ((j-i) * K + K + i) % A.Length;
if (destIndex == i) break;
var destValue = A[destIndex];
A[destIndex] = A[i];
A[i] = destValue;
}
}
return A;
}
这是我在 Java 中的解决方案,它让我在 Codility 获得了 100% 的任务分数和 100% 的正确性:
class Solution {
public int[] solution(int[] A, int K) {
// write your code in Java SE 8
if (A.length > 0)
{
int[] arr = new int[A.length];
if (K > A.length)
K = K % A.length;
for (int i=0; i<A.length-K; i++)
arr[i+K] = A[i];
for (int j=A.length-K; j<A.length; j++)
arr[j-(A.length-K)] = A[j];
return arr;
}
else
return new int[0];
}
}
请注意,尽管看到了两个for
循环,但整个数组的迭代只进行了一次。
用于左移数组的 Swift 4 版本。
func rotLeft(a: [Int], d: Int) -> [Int] {
var result = a
func reverse(start: Int, end: Int) {
var start = start
var end = end
while start < end {
result.swapAt(start, end)
start += 1
end -= 1
}
}
let lenght = a.count
reverse(start: 0, end: lenght - 1)
reverse(start: lenght - d, end: lenght - 1)
reverse(start: 0, end: lenght - d - 1)
return result
}
例如,如果输入数组是a = [1, 2, 3, 4, 5]
,并且左移偏移量是d = 4
,那么结果将是[5, 1, 2, 3, 4]
@IsaacTurner 的答案 (C) https://stackoverflow.com/a/32698823/4386969
和@SomeStrangeUser 的答案(Java): https ://stackoverflow.com/a/18154984/4386969
提供一个简单的 O(N) 时间、O(1) 空间算法来回答这个问题并且需要准确地分配 N 个元素。我相信(如果我错了,有人纠正我)计算 N 和 M 之间的 gcd 是不必要的;简单地计算我们放置在正确位置的元素数量就足够了。这是因为一旦我们将一个元素放在正确的位置,我们就可以保证在当前循环和后续循环中都不必再次访问它。
这是一个带有额外简化的 Python 3 实现:
# circle shift an array to the left by M
def arrayCircleLeftShift(a, M):
N = len(a)
numAccessed = 0
cycleIdx = 0
while numAccessed != N:
idx = cycleIdx
swapIdx = (idx + M) % N
tmp = a[idx]
while swapIdx != cycleIdx:
a[idx] = a[swapIdx]
numAccessed += 1
idx = swapIdx
swapIdx = (idx + M) % N
a[idx] = tmp
numAccessed += 1
cycleIdx += 1
我知道这是一篇旧文章,但是这是 O(n) 中的最佳解决方案:每个元素只移动一次,不需要额外的空间。它与 Isaac Turner 提出的解决方案非常相似,但不需要 gcd 计算。
public static void shiftArray(int[] A, int k) {
if (A.length == 0) {
return;
}
k = k % A.length;
k = (k + A.length) % A.length; // ensure k is positive
if (k == 0) {
return;
}
int i = 0, i0 = 0;
int x = A[0];
for (int u = 0; u < A.length; u++) { // count number of shifted elements
int j = (i - k + A.length) % A.length; // ensure modulo is positive
if (j == i0) { // end of a (sub-)cycle, advance to next one
A[i] = x;
x = A[i = ++i0];
} else {
A[i] = A[j];
i = j;
}
}
}
为数组保留两个索引,一个索引从数组的开头到数组的结尾。另一个索引从 last 的第 M 个位置开始,并循环遍历最后的 M 个元素任意次数。始终采用 O(n)。不需要额外的空间。
circleArray(Elements,M){
int size=size-of(Elements);
//first index
int i1=0;
assert(size>M)
//second index starting from mth position from the last
int i2=size-M;
//until first index reaches the end
while(i1<size-1){
//swap the elements of the array pointed by both indexes
swap(i1,i2,Elements);
//increment first pointer by 1
i1++;
//increment second pointer. if it goes out of array, come back to
//mth position from the last
if(++i2==size) i2=size-M;
}
}