从右侧移动到奇数位置,从左侧移动到偶数位置
Posted
技术标签:
【中文标题】从右侧移动到奇数位置,从左侧移动到偶数位置【英文标题】:move from right side to odd positions and from left side to even in-place 【发布时间】:2012-11-09 15:46:46 【问题描述】:给定一个非空的项目数组。您必须将所有项目从右侧移动到奇数位置(从零开始),并从左侧移动到偶数位置,如下所示:
原始数据:0 2 4 6 8 10 12 14 1 3 5 7 9 11 13
结果:0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
对于这个具有 O(n) 时间复杂度的就地算法存在什么?它的实现方式是什么?
逆向问题解决了here(这个算法本质上可以逆向,但是看起来会很难看)。
【问题讨论】:
我想从技术上讲这是一个“算法”,但它似乎只是在 O(n) 中将列表分成两半,然后将两半交错(也是 O(n))就足够了......虽然,我猜这是假设一个连续的数组数据结构 - 一个单链表可能不能在 O(n) 中拆分,尽管交错仍然是线性的...... @twalberg,听起来你已经开始有了答案。你能多谈谈交错过程吗?请记住,它必须就位。 糟糕...错过了就地要求。我想象的需要使用辅助存储,尽管结果肯定可以复制回输入数组......@Kevin - 没有详细说明,因为它闻起来有点像家庭作业/考试类型的问题在一些初始指针的帮助下,最有利于 OP 充分开发解决方案... 我认为你错了。关于这个主题,撰写了严肃的科学著作。 【参考方案1】:这里只是算法本身。有关详细信息、说明和替代方法,请参阅answer for the inverse problem。
-
将指向右侧元素池的指针初始化为 N/2。
获取大小为 3k+1 的最大子数组
加入数组开头的 (3k+1)/2 个元素和右侧元素池中的 (3k+1)/2 个元素交换适当的子阵列。更新池指针。
对该子数组的各个部分应用循环领导算法,从位置 1、3、9、... 3 开始k-1:将元素移动到子数组中的正确位置(子数组左边的元素到偶数位置,从右边到奇数位置),被替换的元素也应该移动到它的正确位置,等等,直到这个过程回到起始位置。
使用步骤 2 .. 4 递归处理数组的其余部分。
这个问题比OP中提到的逆问题更简单,因为这里我们必须从较大的子数组开始重新排序子数组,顺序与循环领导算法相同(逆问题必须单独进行并且以相反的顺序,从较小的开始获得 O(N) 复杂度)。
【讨论】:
WRT第二个子句:子数组是什么?子阵的下半部?如果我们有奇数长度的源数组怎么办? @Orient:我的意思是整个数组最左边的子数组(更准确地说,是它尚未处理的部分)。据我了解,您已经完全像这样实现了它。奇数长度的源数组都取决于您如何定义“右侧”。如果“右侧”从中间元素开始,这个问题没有解决办法。如果“右侧”从中间元素旁边的元素开始,我们可以只向数组添加(隐式)一个元素,然后像往常一样移动元素,然后删除这个额外的元素(它仍然在数组的末尾) .【参考方案2】:我终于找到了正反问题的解:
#include <iterator>
#include <algorithm>
#include <type_traits>
#include <limits>
#include <deque>
#include <utility>
#include <cassert>
template< typename Iterator >
struct perfect_shuffle_permutation
static_assert(std::is_same< typename std::iterator_traits< Iterator >::iterator_category, std::random_access_iterator_tag >::value,
"!");
using difference_type = typename std::iterator_traits< Iterator >::difference_type;
using value_type = typename std::iterator_traits< Iterator >::value_type;
perfect_shuffle_permutation()
for (difference_type power3_ = 1; power3_ < std::numeric_limits< difference_type >::max() / 3; power3_ *= 3)
powers3_.emplace_back(power3_ + 1);
powers3_.emplace_back(std::numeric_limits< difference_type >::max());
void
forward(Iterator _begin, Iterator _end) const
return forward(_begin, std::distance(_begin, _end));
void
backward(Iterator _begin, Iterator _end) const
return backward(_begin, std::distance(_begin, _end));
void
forward(Iterator _begin, difference_type const _size) const
assert(0 < _size);
assert(_size % 2 == 0);
difference_type const left_size_ = *(std::upper_bound(powers3_.cbegin(), powers3_.cend(), _size) - 1);
cycle_leader_forward(_begin, left_size_);
difference_type const rest_ = _size - left_size_;
if (rest_ != 0)
Iterator middle_ = _begin + left_size_;
forward(middle_, rest_);
std::rotate(_begin + left_size_ / 2, middle_, middle_ + rest_ / 2);
void
backward(Iterator _begin, difference_type const _size) const
assert(0 < _size);
assert(_size % 2 == 0);
difference_type const left_size_ = *(std::upper_bound(powers3_.cbegin(), powers3_.cend(), _size) - 1);
std::rotate(_begin + left_size_ / 2, _begin + _size / 2, _begin + (_size + left_size_) / 2);
cycle_leader_backward(_begin, left_size_);
difference_type const rest_ = _size - left_size_;
if (rest_ != 0)
Iterator middle_ = _begin + left_size_;
backward(middle_, rest_);
private :
void
cycle_leader_forward(Iterator _begin, difference_type const _size) const
for (difference_type leader_ = 1; leader_ != _size - 1; leader_ *= 3)
permutation_forward permutation_(leader_, _size);
Iterator current_ = _begin + leader_;
value_type first_ = std::move(*current_);
while (++permutation_)
assert(permutation_ < _size);
Iterator next_ = _begin + permutation_;
*current_ = std::move(*next_);
current_ = next_;
*current_ = std::move(first_);
void
cycle_leader_backward(Iterator _begin, difference_type const _size) const
for (difference_type leader_ = 1; leader_ != _size - 1; leader_ *= 3)
permutation_backward permutation_(leader_, _size);
Iterator current_ = _begin + leader_;
value_type first_ = std::move(*current_);
while (++permutation_)
assert(permutation_ < _size);
Iterator next_ = _begin + permutation_;
*current_ = std::move(*next_);
current_ = next_;
*current_ = std::move(first_);
struct permutation_forward
permutation_forward(difference_type const _leader, difference_type const _size)
: leader_(_leader)
, current_(_leader)
, half_size_(_size / 2)
;
bool
operator ++ ()
if (current_ < half_size_)
current_ += current_;
else
current_ = 1 + (current_ - half_size_) * 2;
return (current_ != leader_);
operator difference_type () const
return current_;
private :
difference_type const leader_;
difference_type current_;
difference_type const half_size_;
;
struct permutation_backward
permutation_backward(difference_type const _leader, difference_type const _size)
: leader_(_leader)
, current_(_leader)
, half_size_(_size / 2)
;
bool
operator ++ ()
if ((current_ % 2) == 0)
current_ /= 2;
else
current_ = (current_ - 1) / 2 + half_size_;
return (current_ != leader_);
operator difference_type () const
return current_;
private :
difference_type const leader_;
difference_type current_;
difference_type const half_size_;
;
std::deque< difference_type > powers3_;
;
【讨论】:
是的,看起来是这样,但使用swap
- 是最常见的方法。
在循环领导算法中,我们向前移动前一个值,替换当前值。因此,我们无法避免每个周期的三个分配。
问题不在于swap
本身,而在于交换两个内存位置。我们无法避免每个循环三个赋值,但是如果我们将领导值保留在局部变量中并仅在循环完成后将其写入内存,我们可以避免每个循环两次内存写入。可能编译器足够聪明,可以为我们做这种优化,但我不确定。
我已经以适当的方式修改了算法。在这个变体中,排列以相反的顺序完成,领导者存储在一个局部变量中,只在最后一步使用。
@EvgenyKluev 我重写了解决方案。现在它的实现更加清晰了。 Repository.以上是关于从右侧移动到奇数位置,从左侧移动到偶数位置的主要内容,如果未能解决你的问题,请参考以下文章
算法 调整数组顺序,使得奇数在前偶数在后,分别保证奇数和偶数之间的相对位置不变
Vue仿制外卖点餐界面的左右侧菜单联动:点击左侧使右侧滚动到对应位置,右侧滚动时选中左侧对应选项
Leetcode练习(Python):滑动窗口:第239题:滑动窗口最大值:给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动
Leetcode练习(Python):滑动窗口:第239题:滑动窗口最大值:给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动