【问题标题】:Convert a given array with all zeros to a target array将全零给定数组转换为目标数组
【发布时间】:2018-10-31 05:00:55
【问题描述】:

最近我遇到了一个面试问题。我尝试解决它,但面试官正在寻找更好的解决方案。问题是:

给定一个包含零的源数组和包含 numbers,返回您可以获得的最小步数 从源头瞄准。您只能进行以下操作: 您可以将源数组元素的值从 在一次操作中索引 L 到索引 R。

我的想法:

Let array be [4,2,3,5] 
cnt=0;
For each sub-array with only non-zero element, subtract minimum of that sub-array from each of the element of that sub-array. Count increases by sum of mins in each subarray
So array becomes :
After first step : [2,0,1,3] cnt+=1*2 (because only one subarray)
After second step : [0,0,0,2] cnt+=2+1 (because two subarray, each requiring an increment operation)
After second step : [0,0,0,0] cnt+=2

有人可以帮助找到更好的算法吗?我也在考虑是否也可以使用段树/二叉索引树但无法提出解决方案。

【问题讨论】:

    标签: arrays algorithm segment-tree


    【解决方案1】:

    有一个简单的 O(n) 运行时,O(1) 内存解决方案。

    target 数组视为具有给定高度的山脉。最少的操作数是您必须放下的非断开连接的 1 层高的层数以构建该山脉。

    但更简单的考虑方法是,如果您从任一方向穿越该山脉,您必须向上攀登的总距离。

    这是一个 C++ 实现。

    int minNumberOperations(const vector<int>& target) {
      int result = 0, prev = 0;
      for (int height : target) {
        result += max(0, height - prev);
        prev = height;
      }
      return result;
    }
    

    【讨论】:

    • 这里的 val 是什么?
    • @AishwatSingh:哎呀,好消息!那应该是height。我修好了。
    【解决方案2】:

    不要增加零数组并将其转换为给定数组,而是以其他方式攻击 - 尝试通过递减将给定数组变为零数组。

    • 在给定数组的顶部构建一个段树。段树应该能够回答这个查询 - min(left, right ) - 范围 leftright 之间的最小项目的索引。

    • range(0, n - 1) 开头,其中n 是数组的大小。在任何时候,对于query(left, right),假设最小元素是x,索引是indx

    • 现在这是诀窍。这个想法实际上是不要减少范围 leftright 之间的元素,因为这会很困难。递归调用range(left, indx - 1)range(indx + 1, right)。现在您知道,对于左侧和右侧部分,您已经从每个元素中减少了 dx。所以现在任何元素都是X,你必须像X - dx一样处理它。

    希望您能理解。我将为此提供 C++ 实现。

    编辑

    请查看代码并使用笔和纸。你会希望得到这个想法。我已经对棘手的部分添加了评论。

    class Solution {
    public:
        vector<int> segmentTree;
        vector<int> arr;
        int n;
        void init() {
            segmentTree.clear();
            const int SIZE  = pow(2, ceil(log((double) n) / log(2.0)) + 1) - 1;
            segmentTree.resize(SIZE, 0);
            build(1, 0, n - 1);
        }
    
        // O(n)
        int build(int node, int left, int right) {
            if(left == right) {
                return segmentTree[node] = left;
            }
            int leftNode = node << 1;
            int rightNode = leftNode | 1;
            int mid = left + (right - left) / 2;
            int leftIdx = build(leftNode, left, mid);
            int rightIdx = build(rightNode, mid + 1, right);
            return segmentTree[node] = (arr[leftIdx] <= arr[rightIdx]) ? leftIdx : rightIdx;
        }
    
        int query(int node, int left, int right, int x, int y) {
            if(x > right or y < left) return -1;
            if(left >= x and right <= y) return segmentTree[node];
            int leftNode = node << 1;
            int rightNode = leftNode | 1;
            int mid = left + (right - left) / 2;
            int leftIdx = query(leftNode, left, mid, x, y);
            int rightIdx = query(rightNode, mid + 1, right, x, y);
            if(leftIdx == -1) return rightIdx;
            if(rightIdx == -1) return leftIdx;
            return (arr[leftIdx] <= arr[rightIdx]) ? leftIdx : rightIdx;
        }
    
        int query(int x, int y) {
            return query(1, 0, n - 1, x, y);
        }
    
        int convertUtil(int left, int right, int dx) {
            if(left > right) return 0;
            int mid = query(left, right);
            int minElement = arr[mid];
    
            int cnt = 0; // the number of operation
    
            // dx is the amount that has been already decremented from this range
            // So you have to treat every element X as (X - dx)
            cnt += (minElement - dx);
    
            cnt += convertUtil(left, mid - 1, minElement) + convertUtil(mid + 1, right, minElement);
    
            return cnt;
        }
    
        int convert(vector<int>& arr) {
            this->arr = arr;
            this->n = arr.size();
            init();
            return convertUtil(0, n - 1, 0);
        }
    };
    
    // vector<int> arr {4,2,3,5};
    // cout << Solution().convert(arr); // OUTPUT: 7
    

    【讨论】:

    • 您能否分享一下如何有效地完成更新功能。我的意思是将每个元素减一会导致段树发生变化(所有元素),我认为这将是昂贵的操作
    • @crystal 请检查我刚刚添加的代码。实际上,您不需要 update 方法,这会使其成本高昂。请检查代码,如果您需要任何解释,请告诉我。我也会更新我的答案
    • 非常感谢您提供如此详细的解释:D
    【解决方案3】:

    使用堆栈。运行时间为 O(moves + length)。

    值每增加 1,将 (position, end_val) 添加到堆栈中。每次值减 1 时,移除堆栈的顶部元素并将其位置与当前位置左侧的一个配对。

    例如,[5, 3, 2, 5]

    process 5: an increase. Stack is now (0,1), (0,2), (0,3), (0,4), (0,5)
    process 3: a decrease at index 1. Remove the top two elements from the stack and record these moves (0,0) (0,0). The stack is now (0,1), (0,2), (0,3)
    process 2: a decrease at index 2. Remove the top element from the stack and record (0,1). The stack is now (0,1), (0,2)
    process 5: an increase at index 3. The stack is now (0,1), (0,2), (3,3), (3,4), (3,5)
    process 0 (implied): a decrease at index 4. record (3,3), (3,3), (3,3), (0,3), (0,3)
    

    最终移动:(0,0) (0,0), (0,1), (3,3), (3,3), (3,3), (0,3), (0, 3).

    单独记录多级位置变化可以在空间上做得更好(例如第一次移动的(0,5)),但是时间效率是一样的,因为每次移动只能改变1的值。

    【讨论】:

      【解决方案4】:
      public int minNumberOperations(int[] target) {
          int ans = target[0];
          for(int i=1; i < target.length; i++){
              if(target[i]>target[i-1]){
                  ans += target[i]-target[i-1];
              }
          }
          return ans;
       }
      

      【讨论】:

        猜你喜欢
        • 2020-07-31
        • 1970-01-01
        • 2020-02-25
        • 2023-03-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-01-08
        相关资源
        最近更新 更多