几天前,在一次采访中有人问我以下问题: br />
我冻结了,并写了一个我写在下面的效率低下的解决方案。基本上,我遍历原始数组,对每个元素进行平方,然后通过冒泡排序对该数组进行排序。 (我知道,我可以做得更好,但这是刚开始想到的)。

有没有办法更有效地执行此操作,也许是\ $ \ mathcal {O}(N)\ $解决方案?

void bubbleSort(int *arr, int length);

int main(int argc, const char * argv[]) {

    int arr[] = {-3,-2,0,4,5,8};

    for(int i = 0; i < 6; i++) {
        arr[i] *= arr[i];
    }

    bubbleSort(arr, 6);

    for(int j = 0; j < 6; j++)
        cout << arr[j] << endl;
}

void bubbleSort(int *arr, int length) {
    int temp;

    for(int i = 0; i < length; i++) {
        for(int j = 0; j < length; j++) {
            if(arr[j] < arr[j+1]) {
                temp = arr[j+1];
                arr[j+1] = arr[j];
                arr[j] = temp;
            }
        }
    }
}


元素可以是负数或正数。

评论

您不是从排序数组开始的吗?

假定所有值均为正,我将简单地遍历数组,将每个元素替换为正方形。

所有要素都是积极的吗?那么排序是一条红鲱鱼。

@Quentin {-3,-2,0,4,5,8};阅读是技术。 (除非另有说明,否则int类型可以为负)

从技术上讲,问题本身并未指定所用数字的类型。除了负数,对于浮点数,还必须注意(-1; +1)范围。

#1 楼

可以在O(n)时间内完成。将数组分为两个逻辑部分。正面及负面。然后将平方应用于两个数组中的每个元素。然后合并数组(可以在O(n)中完成合并),但是以相反的顺序将数组与以前的负整数合并,因为其值将被反转。

评论


\ $ \ begingroup \ $
很好。这是很有意义的,并且很容易实现。谢谢
\ $ \ endgroup \ $
–carbon_ghost
2015年1月6日在2:32

\ $ \ begingroup \ $
@ 2501,是否可以在线性时间内就地合并数组?我可以使用第二个数组来实现此算法,但是我找不到使用少于线性内存的方法。
\ $ \ endgroup \ $
–user2023861
2015年1月9日在22:23

#2 楼

如果对数组进行排序。
那么唯一会发生的重新排序是将负数转换为正数(因为负数的平方会导致正数)。所以负数将需要重新排序为正数。

  [ -10, -6, -4, -2, 1, 3, 5, 8]
                   ^
                   Split


您实际上可以只考虑这两个单独的排序数组(一个按负方向排序)但这仅意味着使用-而不是++对其进行迭代(我们有一个迭代器)。那)。完成后,对值执行平方运算。该算法为O(n)。尽管您要遍历该数组两次。

唯一的优化是在执行合并的同时执行平方(快速自定义迭代器可以解决此问题)。解决方案是O(n),但它将是数组的单个遍历(并不困难)。

void mergeSortedArray(std::vector<int>& data)
{

    // Find the iterator range for the positive values.
    using iter          = std::vector<int>::const_iterator;
    iter  endPositive   = std::end(data);
    iter  loopPositive  = std::find_if(std::begin(data), std::end(data),
                                       [](int val) {return val >=0;});

    // Find the iterator range for the negative values.
    using reve          = std::reverse_iterator<iter>;
    reve  endNegative   = reve(std::begin(data));
    reve  loopNegative  = reve(loopPositive);

    // Create an array to put the results into.
    std::vector<int>  result;
    result.reserve(data.size());

    // Perform a standard merge
    std::merge(loopPositive, endPositive, loopNegative, endNegative,
               SquarePushBackIterator(result),
               [](int val1, int val2){return std::abs(val1) < std::abs(val2);});

    // Use move assignment to put the result in the output vector.
    // Alternatively we could return an array by value.
    data = std::move(result);
}


只需要一个本地客户迭代器。
这将对值进行平方并根据分配将其推入容器。

class SquarePushBackIterator
    : public std::iterator<std::output_iterator_tag,int>
{
    std::vector<int>& dst;
    public:
        SquarePushBackIterator(std::vector<int>& dst) : dst(dst) {}
        // These just return references to the iterator.
        // The only operation that matters is assignment.
        SquarePushBackIterator& operator*()     {return *this;}
        SquarePushBackIterator& operator++()    {return *this;}
        SquarePushBackIterator& operator++(int) {return *this;}

        // The assignment squares and inserts into the destination.
        void operator=(int val)
        {
            dst.push_back(val * val);
        }
};


评论


\ $ \ begingroup \ $
在面试中提供这样的答案,您甚至不必完成测试! ;)
\ $ \ endgroup \ $
–glampert
2015年1月6日14:23

\ $ \ begingroup \ $
您的代码无法编译。 ideone.com/MKmTlF
\ $ \ endgroup \ $
– MORTAL
15年1月7日,下午5:43

\ $ \ begingroup \ $
@MORTAL:与g ++ 4.2.1兼容。现在(添加了:public std :: iterator )也可以在g ++ 4.8中使用。 ideone.com/RWUnvj
\ $ \ endgroup \ $
–马丁·约克
2015年1月7日在6:52



\ $ \ begingroup \ $
非常感谢。但是在VC ++ 2013中,除非我定义如下方运算符,否则它将不会编译:SquarePushBackIterator&operator =(SquarePushBackIterator&lr){return * this; }。它看起来很奇怪,但是可以解决问题
\ $ \ endgroup \ $
– MORTAL
2015年1月7日在7:06

#3 楼

由于已排序,因此您可以遍历数组一次,比较数组中第一项和最后一项的绝对值,然后基于较大的那个,对该项进行平方,然后将值放入新数组的最后一项中。通过增加前索引或减小后索引(取决于哪个值较大)来重复此过程,然后将下一个最大的项目放到新数组的倒数第二个项目中,依此类推。

基本上,您在一个循环中同时对值进行排序和平方。

int arr[] = {-3,-2,0,4,5,8};
int size = 6;
int newArr[size];

int newArrInd = size - 1;
int front = 0;
int back = size - 1;

for(int i = 0; i < size; i++) {
    if (abs(arr[front]) > abs(arr[back])){
        newArr[newArrInd--] = arr[front] * arr[front];
        front++;
    }
    else{
        newArr[newArrInd--] =  arr[back] * arr[back];
        back--;
    }
}

for(int j = 0; j < size; j++)
    cout << newArr[j] << endl;


评论


\ $ \ begingroup \ $
好主意。不过,如果您提供一些文本来配合算法,那将是很好的。 (另外,不需要平方时的abs())
\ $ \ endgroup \ $
–Cornstalks
2015年1月6日,3:30

\ $ \ begingroup \ $
次要点:不需要abs(arr [back]),arr [back]就足够了。
\ $ \ endgroup \ $
– Magoo
2015年1月6日,9:15

\ $ \ begingroup \ $
@Magoo:arr [back]也需要abs。例如,考虑仅负数的输入。
\ $ \ endgroup \ $
– DarkDust
2015年1月7日在17:05

#4 楼

这是一个可能更简单的解决方案(运行时间可能会稍长;尽管仍然是\ $ O(n)\ $)。当然,基本思想是只需要反转负数(在平方之前或之后,实际上并不重要)。它不具有基础容器将在整个过程中保持排序的属性;是否真正需要(或者是否仅是必须遵守该准则的最终结果)尚不是100%明确。

#include <algorithm>
#include <iterator>

template <typename Iterator>
void square_and_sort(Iterator begin, Iterator end)
{
    using value_type = typename std::iterator_traits<Iterator>::value_type;

    auto first_positive = std::find_if(begin, end, 
            [](const value_type& v) { return v >= value_type{}; });
    std::reverse(begin, first_positive);
    std::transform(begin, end, begin,
            [](const value_type& v) { return v * v; });
    std::inplace_merge(begin, first_positive, end);
}


有效地: br />

查找第一个零或正值,
反转所有值直至该第一个零或正值(因为它们必须为负),最后
将平方的负值和平方的正值合并回原位。


评论


\ $ \ begingroup \ $
+1对我来说是完美的答案,但是我有一个问题,如果您不介意的话,我想知道使用std :: find_if是否优于std :: lower_bound
\ $ \ endgroup \ $
– MORTAL
2015年1月6日在21:31

\ $ \ begingroup \ $
@MORTAL我相信使用std :: lower_bound(log(n))有好处,因为find_if为O(n),lower_bound更好,因为我们知道列表已排序,但这不会改变整体复杂。
\ $ \ endgroup \ $
–ryanpattison
2015年1月6日在22:50

\ $ \ begingroup \ $
@MORTAL是的,你是对的,std :: lower_bound在这里会更好-当我写这篇文章时,我略过了。
\ $ \ endgroup \ $
–玉石
2015年1月7日,下午3:15

#5 楼

您的解决方案

如果我想对数组进行平方和排序的显而易见的解决方案,它将看起来像这样:
不使用标准库的要求?它使您免于实现无聊,困难和容易出错的事情(例如排序算法)的痛苦。负数变为正数,使数组不再排序。我的想法是找到第一个非负数的索引mid,然后对整个数组求平方。部分列表[begin, mid[以相反的顺序排序,并且部分列表]mid, end[已经正确排序。可以将两个排序列表有效地排序为一个排序列表。可以使用std::merge来补偿第一个列表的反向顺序。

#include <algorithm>
#include <iostream>

int main(){
    //arguably arr should be an std::array or even auto for an std::initializer_list
    int arr[] = { -3, -2, 0, 4, 5, 8 };
    //square array
    std::transform(std::begin(arr), std::end(arr), std::begin(arr),
        [](int n){ return n * n; }
    );
    //sort array
    std::sort(std::begin(arr), std::end(arr));
    //print result
    for (const auto &i : arr)
        std::cout << i << ' ';
}


此实现将第二个数组用于合并列表。可能可以就地进行,但这需要更多的努力。我的解决方案将复杂度从O(n * log(n))降低到O(n),但是将空间复杂度从O(1)更改为O(n),这并不是严格必要的。我可能会偏爱您的解决方案,因为它很容易,除非经过性能分析证明效率低下是一个重大问题。

评论


\ $ \ begingroup \ $
尽管我喜欢您的解决方案(+1)。我认为您以一种难以理解且难以维护的方式编写了该文档。空格/注释和较短的行都将是有益的。将所有内容塞到一条线上并不总是最好的解决方案。
\ $ \ endgroup \ $
–马丁·约克
2015年1月6日在18:29



\ $ \ begingroup \ $
@LokiAstari我看不到拥挤和难以维护,但我尝试应用了您的建议。
\ $ \ endgroup \ $
– nwp
15年1月6日,19:19

\ $ \ begingroup \ $
当然你看不到;你刚刚写的。但是将其放回原本,并在6个月后再回来看看您是否仍然了解它。现在好多了,现在很容易阅读。对合并进行了全面讨论。
\ $ \ endgroup \ $
–马丁·约克
2015年1月6日在19:21



\ $ \ begingroup \ $
@nwp是的,很遗憾,我不允许使用任何内置库。
\ $ \ endgroup \ $
–carbon_ghost
2015年1月7日在2:59

#6 楼

如果允许在平方后对数组进行未排序,然后对其进行排序,请至少使用std::sort;或者,为了获得更好的时间性能但更多地占用空间,请使用std::merge。如果标准库超出了采访的范围,请运行。 :)

另一方面,如果必须在更改每个元素后进行排序,请利用标准库的其他部分,例如std::lower_boundstd::rotate。请注意执行操作的顺序,以免冒平方两次的风险。

#7 楼

我相信您错过了一项关键要求,即需要保持阵列排序。您的解决方案没有。输入中的负值开始排序,但是当您平方它们时,它们会反转。然后,您可以使用冒泡排序对它进行纠正。

有一种方法可以使所有数据始终保持排序。这不是\ $ O(n)\ $操作。

使用的算法是对数组右侧所有大于abs值的值进行平方运算。最左边的值。

一旦找到较小的值,您就会知道需要在该点插入最左边的值平方。向左移动所有值,插入正方形,然后继续。

您的代码非常类似于C,并且类似C的实现为:

>
基于矢量的基本解决方案将是:使用的算法。请注意,在某些情况下,值是重复的(在移位过程中),但是数组在任何时候都不会乱序。

#8 楼

如果对数组进行了排序,则可以使用二进制搜索实现找到从负到正的位置。

{-3 -2 | 0 4 5 8}
     A   B


然后使用该位置,比较左边的数字在右边。取绝对值较小的那个数字,将其平方添加到新数组的下一个位置,然后将其指针从中间移开,直到A达到-1或B达到长度

算法的迭代方法

array    {-3 -2 | 0 4 5 8}
pointer       A   B
squares  {              }


abs(arr[B]) < ab(arr[A])
B++
array    {-3 -2 | 0 4 5 8}
pointer       A     B
squares  {0              }

abs(arr[B]) > ab(arr[A])
A--
array    {-3 -2 | 0 4 5 8}
pointer    A        B
squares  {0 4            }

abs(arr[B]) > ab(arr[A])
A--
array    {-3 -2 | 0 4 5 8}
pointer  A          B
squares  {0 4 9          }

A == -1 && B != length
B++
array    {-3 -2 | 0 4 5 8}
pointer  A            B
squares  {0 4 9 16        }

etc...


我希望自己能很好地解释一下,它基本上是合并排序。据我所知,这需要O(log(n)(已删除:*)+ n)时间和O(n)空间。

#9 楼

这取决于排序算法的实现。由于数组已经大部分排序或以降序排序等情况,因此对于随机数而言,好的实用排序算法不会得到优化,而是针对实际情况进行优化。如果只对所有数字求平方,然后调用实现提供的排序算法,则很可能会发现您的数组由大量的降序整数组成,然后由大量的升序组成排序,并以最佳方式合并两个序列。

#10 楼

最快的方法是使用两个索引:

int main()
{
    vector<double> k={-5, -3, 1, 2 ,6 };
    deque<double> l;
    int p1=0,p2=k.size()-1;

    double s ,e;
    do {
        s=k[p1]; e=k[p2];
        if(s*s>e*e) { l.push_front(s*s); p1++; }
        else { l.push_front(e*e); p2--; }
    } while(p2>=p1);
}



只插入一次
每个开关仅测试两次
读取值\ $ 2 * n \ $
由于完美的别名向量,最坏的情况\ $ 2 * n \ $测试

只需添加int main()并更改测试以使其适用于多个值(我认为这很明显)。

deque是你的朋友。

评论


\ $ \ begingroup \ $
除非以前没有公开要求使残酷地使源代码简短,否则该答案将真正受益于描述性变量名和空白的注入。
\ $ \ endgroup \ $
–爱德华
2015年1月7日在22:32

\ $ \ begingroup \ $
l最终被反向排序。
\ $ \ endgroup \ $
– 200_success
15年1月8日在11:04

\ $ \ begingroup \ $
do / while将导致在空数组上失败。空数组适合问题空间,这是排序数组的琐碎情况。
\ $ \ endgroup \ $
– chqrlie
15年3月24日在22:53

\ $ \ begingroup \ $
甚至嵌套了while <,while>和while ==循环,仅测试在后者中满足的指针。
\ $ \ endgroup \ $
–灰胡子
16年1月16日在13:22

#11 楼

您可以考虑使用std::set通过插入成员函数对数组进行排序。

它是一个O(N * log(size()+ N)),其中N是要插入的元素数。

根据http:// en .cppreference.com / w / cpp / container / set / insert

#include <iostream>
#include <set>
int main()
{
    using namespace std;
    int arr[] = { -3, -2, 0, 4, 5, 8 };

    set<int> sort_squae;

    // it will sort and square the array for free
    for (auto&& i : arr)
        sort_squae.insert(i * i);

    // print it
    for (const auto& i : sort_squae)
        cout << i << ' ';
}


#12 楼

直接问题


有没有办法更有效地执行此操作,也许是O(N)解决方案?


是的,可以比bubbleSort的O(n ^ 2)更有效。而且不行。一般比较排序算法的极限是O(n log n)。

面试问题


数组的排序算法被烘焙到大多数编程语言中,或者可以在库中找到[带有书籍的种类,如果不是语言标准的]。当然,它们不能直接处理任意对象,但是它们都可以适当地处理整数以达到通常将语言放入的目的。这些是现成的零件。 Knuth中有整整一章供任何需要认真对待的人使用。

在更改数组值的同时保持数组排序意味着并发语义。对于明智的面试官而言,这是一个棘手的问题。即使在O(n log n)中给出可靠的答案是可取的,但在O(n log n)中给出不可靠的答案通常比在O(n ^ 2)中给出可靠的答案要差。 /> C ++是面向对象的。这建议通过类和对象的语义进行封装。
C ++是C的一种形式。指针的操作是惯用法。它完全基于操纵指向记录的指针,而不是移动实际记录。有更糟糕的权威机构试图模仿。

解决这一问题的方案的骨架可能看起来像:
虽然这是一个玩具问题。购买Xtreme CPU,将所有内容存储在RAM中-甚至更好的L1缓存,然后开始飙车。但是最终的性能是调整而不是解决问题。复杂性保持不变。