【问题标题】:Folding paper strip and generate numbers while unfolding折叠纸条并在展开时生成数字
【发布时间】:2016-08-19 14:41:06
【问题描述】:

我有一个作业问题,我正在努力解决,需要一些指导来解决。 假设我有一条纸,我从中心折叠它,使左半部分在右半部分后面。然后我按顺序对折叠的部分进行编号,当我展开时得到编号,如下所示。 1:2

如果我折叠两次,展开时得到的数字如下 1:4:3:2

如果我折叠三次,我得到如下 1 8 5 4 3 6 7 2

我想在折叠 n 次时生成数字数组。因此,如果我将它折叠 25 次,我将得到 2^25 个类似序列的数字。

这些是我的观察结果

  • 第一个和最后一个数字始终是 1 和 2。

  • 中间的两个数字总是4和3

  • 索引 1 处的数字是最大数字,倒数第二个位置的数字是第二大数字。

  • 它看起来像二叉搜索树的前序遍历,但我不知道这有什么帮助。

  • 我尝试从预序构造二叉树,然后将其转换为中序,假设我可以反转这个过程以获得相同的序列,但我错了。

EDIT :为了在这个生成的数组中搜索一个元素,我可以进行顺序搜索,这将是 O(n) 高效的。但我意识到必须有一种更快的方法来搜索这个系列中的数字。

我无法进行二分搜索,因为它没有排序,并且当完成 25 次以上的折叠时有超过十亿个数字。

我可以使用什么样的搜索策略来查找数字及其索引?

这也是我想将其转换为具有 log(n) 搜索效率的二叉搜索树的原因之一。

编辑 2: 我按照其中一个答案的建议尝试了表格折叠算法,但内存效率不高。我无法在内存中存储超过十亿个数字,因此必须有一种方法可以在不实际创建数字数组的情况下找到数字索引。

【问题讨论】:

  • 为什么下面的答案是-1
  • @MichaelMarkidis 对于 SO 是什么,用户之间存在广泛的不同意见:人们互相帮助解决编程问题的社区,或者可能对未来用户有用的高质量问答存档。人们在这个问题上的立场通常可以解释他们的投票。
  • 这个序列看起来很眼熟,而且它确实与你得到的位反转有关,所以那里可能有你可以适应的算法。
  • @m69 哦!我还没有想到位反转。我必须探索这些选择。谢谢

标签: java algorithm recursion data-structures


【解决方案1】:

您可以通过使用位反转(反转数字的二进制表示,例如 0001 变为 1000)来计算折叠的数量,而无需计算整个序列。

这些是通过位反转得到的序列:

1 bit:    0  1
2 bits:   0  2  1  3
3 bits:   0  4  2  6  1  5  3  7
4 bits:   0  8  4 12  2 10  6 14  1  9  5 13  3 11  7 15

这些是折纸序列(从0开始计数):

1 fold:   0  1
2 folds:  0  3  2  1
3 folds:  0  7  4  3  2  5  6  1
4 folds:  0 15  8  7  4 11 12  3  2 13 10  5  6  9 14  1

如果你把折纸序列分成偶数和奇数,你会得到:

          0
             1
          0     2
             3     1
          0     4     2     6
             7     3     5     1
          0     8     4    12     2    10     6    14
            15     7    11     3    13     5     9     1

您会看到折纸序列与位反转序列相同,但前半部分(偶数)与后半部分的反转(奇数)交错。

您还会注意到,每对相邻的偶数/奇数加起来为 2n-1(其中 n 是折叠数),这意味着它们是彼此的倒数,并且您可以使用按位 NOT 计算另一个。

所以,要得到一条折叠n次的条带的折叠次数x(从0开始计数):

将 x 除以 2,如果 x 是奇数,则执行按位 NOT,然后位反转(使用 n 位)

示例(折叠 4 次):

fold   x/2    binary   inverted    bit-reversed    from 1

 0      0      0000                0000       0       1
 1      0      0000      1111      1111      15      16
 2      1      0001                1000       8       9
 3      1      0001      1110      0111       7       8
 4      2      0010                0100       4       5
 5      2      0010      1101      1011      11      12
 6      3      0011                1100      12      13
 7      3      0011      1100      0011       3       4
 8      4      0100                0010       2       3
 9      4      0100      1011      1101      13      14
10      5      0101                1010      10      11
11      5      0101      1010      0101       5       6
12      6      0110                0110       6       7
13      6      0110      1001      1001       9      10
14      7      0111                1110      14      15
15      7      0111      1000      0001       1       2

例子:十亿倍:(折叠30次)

fold:            1,000,000,000
counting from 0:   999,999,999 (x is odd)
x/2:               499,999,999
binary:          011101110011010110010011111111 (30 digits)
bitwise NOT:     100010001100101001101100000000 (because x was odd)
bit-reversed:    000000001101100101001100010001
decimal:             3,560,209
counting from 1:     3,560,210

我不会说 Java,但这样的事情应该可以解决问题:

public static long foldIndex(int n, long x) { // counting from zero
    return Long.reverse((x & 1) == 0 ? x >>> 1 : ~(x >>> 1)) >>> (Long.SIZE - n);
}

【讨论】:

  • 优秀的解决方案。只是想知道为什么可以使用位反转来解决这个问题。 (在您的解决方案之前,我不知道位反转)。
  • @hk6279 这是我第一次研究位反转的答案;它描述了序列是如何构建的,您可以看到对称性如何使其类似于折纸:stackoverflow.com/questions/31844446/…
  • 感谢 @MichaelMarkidis 指出了一些阻止我的 Java 代码编译的 JavaScript 主义。
【解决方案2】:

我不懂 Java,但这应该很容易移植并且适用于任意数量的折叠。 Idea和m69差不多,逻辑我就不解释了。

#include <iostream>

size_t reverse(size_t n, int bits)
{
    size_t result = 0;
    size_t msb_value = 1 << (bits - 1);
    while (n)
    {
        if (n & 1) result |= msb_value;
        msb_value >>= 1;
        n >>= 1;
    }
    return result;
}

struct Fold_Sequence
{
    Fold_Sequence(size_t folds) : folds_(folds), max_(1 << folds) { }

    size_t operator[](size_t i) const
    {
        size_t x = reverse((i / 2) % max_, folds_);
        return i & 1 ? (max_ - x - 1) : x;
    }

    size_t folds_, max_, i = 0;
};

int main()
{
    const size_t folds = 4;
    const unsigned num_parts = 1 << folds;
    Fold_Sequence seq{folds};
    for (unsigned j = 0; j < num_parts; ++j)
        std::cout << seq[j] + 1 << '\n';
}

我也喜欢 hk6279 解决方案的优雅,所以我实现了它(也在 C++ 中,我懒得使用多维数组/vector&lt;vector&lt;&gt;&gt; 并且必须一直仔细调整大小,所以使用它实现效率低下map 键入 x,y 坐标):

#include <iostream>
#include <map>

#define DBG(X) do { std::cout << X << '\n'; } while (false)

typedef std::pair<size_t, size_t> Coord;

struct matrix : std::map<Coord, size_t>
{
    matrix(size_t n)
      : y_size_(n)
    {
        for (size_t i = 0; i < n; ++i)
            (*this)[{0, i}] = i;  // bottom left is 0,0; 0,1 is above
    }

    void fold()
    {
        size_t x_size_ = x_size();
        for (size_t y = y_size_ / 2; y < y_size_; ++y)
            for (size_t x = 0; x < x_size_; ++x)
                move(x, y, x_size_ * 2 - x - 1, y_size_ - y - 1);
        y_size_ /= 2;
    }

    void move(size_t from_x, size_t from_y, size_t to_x, size_t to_y)
    {
        DBG("move(" << from_x << ',' << from_y << " -> " << to_x << ',' << to_y
            << ") value " << ((*this)[{from_x, from_y}]));
        (*this)[{to_x, to_y}] = (*this)[{from_x, from_y}];
        erase({from_x, from_y});
    }

    size_t operator()(size_t x, size_t y) const
    {
        auto it = find({x, y});
        if (it != end()) return it->second;
        std::cerr << "m(" << x << ',' << y << ") doesn't exist\n";
        exit(1);
    }

    size_t x_size() const { return size() / y_size_; }
    size_t y_size() const { return y_size_; }

    size_t y_size_;
};

std::ostream& operator<<(std::ostream& os, const matrix& m)
{
    for (size_t y = m.y_size_ - 1; y <= m.y_size_; --y)
    {
        for (size_t x = 0; x < m.x_size(); ++x)
            os << m(x, y) << ' ';
        os << '\n';
    }
    return os;
}

int main()
{
    const size_t n = 4;
    matrix m(1 << n);
    for (int i = 0; i < n; ++i)
    {
        m.fold();
        std::cout << i+1 << " folds ==> " << m.x_size() << 'x' << m.y_size()
            << " matrix:\n" << m << '\n';
    }
}

【讨论】:

    【解决方案3】:

    这是一种算法,用于查找数字之后的索引 展开。

    它会根据折叠跟踪您的搜索号码移动到的坐标。例如,如果您对 3 个折叠感兴趣(n=3,numFolds)并且想知道数字 7 将在哪里(searchNumber),则算法运行如下:

    初始状态: 8 7 6 5 4 3 2 1 7 在 [1,7] - 第 1 列,第 7 行 现在,当我们将上半部分向下折叠时: 4 5 3 6 2 7 1 8 7 在 [2, 1] - 第 2 列第 2 行 当我们进行下一次折叠时,7 不会移动(因此是 if (row > half) 逻辑) 2 7 6 3 1 8 5 4 最后一折: 1 8 5 4 3 6 7 2 7 位于 [7, 1] - 第 7 列第 1 行,代码将返回 7。
    public static long getIndexOfAfterFold (long numFolds, long searchNumber)
    {
        long total = (long) Math.pow(2, numFolds);
    
        long [] coordsOfSearchNumber = new long [] {1, searchNumber};
    
        int iterations = 0;
    
        while (iterations < numFolds)
        {
            long half = total / 2;
    
            long row = coordsOfSearchNumber[1];
    
            // we are folding down
            if (row > half)
            {
                long newRow = (total - row) + 1;
    
                long col = coordsOfSearchNumber[0];
    
                long newFoldThickness = (long) Math.pow(2, iterations + 1);
                long newCol =  newFoldThickness - (col - 1);
    
                coordsOfSearchNumber[0] = newCol;
                coordsOfSearchNumber[1] = newRow;
            }
    
            total = total / 2;
    
            iterations++;
        }       
        return coordsOfSearchNumber[0];
    }
    

    编辑:将上述代码转换为在int 上使用long

    注意事项

    • 它在 O(n) 时间内运行,其中 n 是折叠数。
    • 用法:System.out.println(getIndexOfAfterFold(4, 13));

    此代码将给出折叠后所有数字的列表

    注意:这是基于@hk6279(表格折叠算法)提供的答案

    public static void unFold (int numFolds)
    {
        int total = (int) Math.pow(2, numFolds);
    
        List<ArrayList<Integer>> table = new ArrayList<ArrayList<Integer>> (total);
    
        // populate the single column table
        for (int i = 0; i < total; i++)
        {
            ArrayList<Integer> list = new ArrayList<Integer>();
            list.add(i + 1);
            table.add(list);
        }
    
        int iterations = 0;
    
        while (iterations < numFolds)
        {
            int half = table.size() / 2;
    
            // place the fold back on itself
            for (int i = 0; i < half; i++)
            {
                ArrayList<Integer> list = table.get(i);
    
                ArrayList<Integer> foldList = table.get(table.size() - (i + 1));
    
                // reverse the fold
                Collections.reverse(foldList);
    
                // add the fold to front
                list.addAll(foldList);
            }
    
            // remove the part we folded
            table.subList(half, table.size()).clear();
    
            iterations++;
        }
        System.out.println(table);
    }
    

    这就是 n=5 的样子:

    1、32、17、16、9、24、25、8、5、28、21、12、13、20、29、4、3、30、19、14、11、22、27、6 , 7, 26, 23, 10, 15, 18, 31, 2

    【讨论】:

    • 这是一个很好的工作解决方案。尽管当折叠次数达到 30+ 时,我遇到了将所有数字存储在数组列表中的问题。我的内存用完了。有没有办法在不实际创建完整数组的情况下找到项目。?
    • 这是一个有趣的提议。我将不得不考虑那个。就搜索复杂性而言 - 它实际上有点类似于二进制搜索。我相信它是 O(Log2(2^n)) - 以 2 的 n 次方为底数。这是我的想法:如果你想在 4 折的情况下(n=4)找到 8 的位置,那么你可以在 4 步中找到 8,因为你需要 4 步才能到达展开分布。我可以在我的回答中详细说明。
    • 是的,我意识到我可以以某种方式“预测” 8 将在哪里结束。这是一种经过修改的二分搜索。
    【解决方案4】:

    第一折:1 2

    第二折:1 4 3 2

    第三折:1 8 5 4 3 6 7 2

    第四折:1 16 9 8 5 12 13 4 3 14 11 6 7 10 15 2


    生成表格(以第 4 折为例)

    假设您有第 n 折纸,然后将其展开。

    1. 生成一个大小为 (column = 1, row = 2^n) 的表,并按升序从下到上填充列

      16
      15
      14
      13
      12
      11
      10
      9
      8
      7
      6
      5
      4
      3
      2
      1
      
    2. 通过将顶部 x 行从后到前粘贴到底部 x 行,递归地调整表格大小(列 = org.column*2,row = org.row / 2)

      8  9
      7 10
      6 11
      5 12
      4 13
      3 14
      2 15
      1 16  
      
      4 13 12  5
      3 14 11  6
      2 15 10  7
      1 16  9  8   
      
      2 15 10  7  6 11 14  3
      1 16  9  8  5 12 13  4
      
      1 16  9  8  5 12 13  4  3 14 11  6  7 10 15  2
      
    3. 从头到尾读取最后的 1 行表作为结果

      1 16 9 8 5 12 13 4 3 14 11 6 7 10 15 2


    剩下的工作就是证明这个工作,然后编码(我只测试到 n=4,因为我很懒)

    【讨论】:

    • 你的算法有效。我根据它发布了一个答案。这是一个聪明的方法。
    • 这真是个聪明的想法!我将尝试编写此算法的代码并将其添加为编辑
    • 解决方案的第一步需要将所有数字 2^n 存储在一个数组中。如果我有 30 次折叠,我就有超过 10 亿个数字,我无法在内存中存储那么多数字。
    猜你喜欢
    • 2018-06-28
    • 2013-07-18
    • 1970-01-01
    • 2011-07-20
    • 1970-01-01
    • 2017-11-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多