【问题标题】:recursion: cut array of integers in two parts of equal sum - in a single pass递归:将整数数组切割成相等和的两部分 - 一次通过
【发布时间】:2010-11-11 01:36:33
【问题描述】:

使用递归,找到一个索引,将数组分成两部分,使两部分的和相等。

Cut 意思是像用刀一样切割。结果索引 结果的所有单元格的总和。任何单元格都不能被遗漏或成为双方的一部分。

数组包含任意整数(即正数、负数和零)。

如果没有这样的索引返回-1

不允许分配堆对象。

你必须一次性完成。

您必须使用递归(即不能使用循环结构)。

可以是任何语言或伪代码。

忘记添加了:不能修改数组

【问题讨论】:

  • 如何定义递归中的“单程”?你的意思是每个节点只会被触摸一次?
  • 是的,每个节点只能读取一次(数组是RO,看问题的更新版本)
  • 如果数组的总数是奇数怎么办?还是假设不会?
  • @bill:任意整数(正数/负数/零)
  • @victor 可能很奇怪。例如这个数组:[1, 1, 2] 有奇数个元素并且有一个解

标签: algorithm puzzle


【解决方案1】:

使用递归进行迭代是一种微不足道的转换,因此我们假设您知道如何做到这一点。

如果您使用“一次传递”来构建自己的“总和到该索引”数组,并且可以对该数组进行另一次传递,我可以看到如何做到这一点。只需遍历第二个数组并从 sum[last] 中减去 sum[x]。如果您发现结果 = sum[x] 的情况,则返回 x。如果您不这样做,则返回 -1。

正如 Neil N 提到的,如果您为递归定义“pass”非常宽松,这样整个递归实际上可以多次访问索引,那么您可以省去第二个数组。


在考虑了一下之后,我怀疑这个想法是让您只访问每个数组元素一次(按顺序),并使用递归的内置堆栈属性来摆脱对任何第二个数组的需要。

您所做的是编写递归例程以将其当前索引的数组值保存在本地,将该值添加到传入的“sum_of_array”值,然后在下一个最高索引(如果有的话)上调用自身。如果没有下一个最高索引,它将总和保存到全局中,现在可用于每个堆叠递归调用。每个例程都通过检查其总和与全局总和来完成。如果是一半,则返回其索引。否则返回-1。如果调用自身返回非 -1,则跳过最后一步并返回该值。我将显示在伪 Ada 中

Total_Sum : integer;

function Split (Subject : Integer_Array; After : Integer := 0; Running_Sum : Integer := 0) is
begin
   Running_Sum := Running_Sum + Subject(After);
   if (After < Subject'last) then --'// comment Hack for SO colorizer
      Magic_Index : constant Integer := Split (Subject, After +  1, Running_Sum);
      if (Magic_Index = -1) then
         if (Total_Sum - Running_Sum = Running_Sum) then
            return After;
         else
            return -1;
         end if;
      else
         return Magic_Index;
      end if;
   else
      Total_Sum := Running_Sum;
      return -1;
   end if;
end Split;

此代码应具有以下属性:

  • 仅使用数组调用它会返回所描述的“拆分”索引,如果没有,则返回 -1。
  • 它只从源数组中的任何元素读取一次
  • 它以严格的索引顺序读取源数组元素。
  • 不需要额外的结构化数据存储(数组)。

【讨论】:

    【解决方案2】:

    哈斯克尔:

    split' _ s [] = (-1, s)
    split' idx s (x:xs) | sidx >= 0   = (sidx, s')
                        | s * 2 == s' = (idx - 1, s)
                        | otherwise   = (-1, s')
        where (sidx, s') = split' (idx + 1) (x + s) xs
    
    split = fst . split' 0 0
    

    您的规则有些误导。您要求不要在堆上分配任何对象,但恕我直言,没有解决方案,该算法没有O(n) 的空间要求,即堆栈随着列表的长度线性增长并且尾部调用是不可能的,因为该函数必须检查递归调用的返回值。

    【讨论】:

      【解决方案3】:

      这是 Erlang 的一个实现,因为我正在学习它,这似乎是一个有趣的挑战。想法无耻地抄袭了 Pesto 的解决方案。

      find_split(List) -> {Idx, _Sum} = find_split(List, 1, 0), Idx.
      find_split([Head], _Idx, _Sum) -> {-1, Head};
      find_split([Head|Tail], Idx, Sum) ->
        case find_split(Tail, Idx + 1, Sum + Head) of
          {-1, Tailsum} when Sum + Head == Tailsum -> {Idx, Sum + Head};
          {-1, Tailsum} -> {-1, Head + Tailsum};
          Ret -> Ret
        end.
      

      【讨论】:

        【解决方案4】:

        C/C++/Java 代码:

        function cut(int i, int j, int s1, int s2, int a[])
        {
            if(i==j && s1==s2)
                return i;
            else if(i==j && s1!=s2)
                return -1;
            else if(s1>s2)
                return cut(i, j-1, s1, s2 + a[j-1]);
            else
                return cut(i+1, j, s1 + a[i+1], s2);
        }
        

        使用以下语法调用:

        cut(0, array.length, 0, 0, array);
        

        【讨论】:

        • 对我来说这是返回 -1 的数组,它不应该是......虽然我将它移植到 java 中。我有什么问题吗?我将函数替换为“public static int”,然后将 ,a 添加到递归调用中。
        • 您可以通过将 s1 + a[i+1] 替换为 s1+a[i] 来修复代码,然后它适用于某些情况。但是,也有一些病理性的情况会中断,例如 [3, -3, -3, 3]。该数组在 1 处有一个拆分,但代码将返回 -1。
        【解决方案5】:

        这是一种利用 Ruby 返回多个值的能力的方法。第一个值是拆分的索引(如果存在),第二个是每一半的总和(如果没有找到拆分,则为整个数组的总和):

        def split(arr, index = 0, sum = 0)
          return -1, arr[index] if index == arr.length - 1
          sum = sum + arr[index]
          i, tail = split(arr, index + 1, sum)
        
          if i > -1
            return i, tail
          elsif sum == tail
            return index, sum 
          end
          return -1, arr[index] + tail
        end
        

        这样称呼它:

        p split([1, 1, 2])
        p split([1])
        p split([-1, 2, 1])
        p split([2, 3, 4])
        p split([0, 5, 4, -9])
        

        结果如下:

        [1, 2]
        [-1, 1]
        [1, 1]
        [-1, 9]
        [0, 0]
        

        编辑:


        这是一个稍作修改的版本,用于解决 onebyone.livejournal.com 的 cmets。现在数组中的每个索引只被访问一次:

        def split(arr, index = 0, sum = 0)
          curr = arr[index]
          return -1, curr if index == arr.length - 1
          sum = sum + curr
          i, tail = split(arr, index + 1, sum)
        
          if i > -1
            return i, tail
          elsif sum == tail
            return index, sum 
          end
          return -1, curr + tail
        end
        

        【讨论】:

        • 有效!并为传播 ruby​​ 的福音而感到自豪,我安装它只是为了测试你的答案。 (我会再次使用它吗?)
        • 虽然使用多个返回值非常简单,但我想知道是否以及如何仅使用单个整数作为返回值来完成。 +1
        • @Daniel +1 我问自己同样的问题。我没有看到没有将两个整数“编码”为一个的答案。也许您想在 SO 中问另一个问题?如果是这样,请在这里放一个链接。
        • @onebyone 你说的有些道理。我选择这个答案是因为它在代码中(而不是伪代码)并且可以轻松测试)。
        • 一旦我们在 SO 上遇到以下问题。 stackoverflow.com/questions/731832/interview-question-ffn-n/… 我可以想象类似的技巧会有所帮助。
        【解决方案6】:

        我的版本:

        # Returns either (right sum from the currentIndex, currentIndex, False),
        # or, if the winning cut is found, (sum from the cut, its index, True)
        def tryCut(anArray, currentIndex, currentLeftSum):
           if currentIndex == len(anArray):
              return (0, currentIndex, currentLeftSum==0)
        
           (nextRightSum, anIndex, isItTheWinner) = tryCut(anArray, currentIndex + 1, currentLeftSum + anArray[currentIndex])
        
           if isItTheWinner: return (nextRightSum, anIndex, isItTheWinner)
           rightSum = anArray[currentIndex] + nextRightSum
           return (rightSum, currentIndex, currentLeftSum == rightSum)
        
        def findCut(anArray):
           (dummy, anIndex, isItTheWinner) = tryCut(anArray, 0, 0)
           if isItTheWinner: return anIndex
           return -1
        

        注意:如果返回的索引是 5,我的意思是 sum(anArray[:5]) == sum(anArray[5:])。 “极值”也是有效的(空切片的总和为零),即如果整个数组的总和为零,则 0 和 len(anArray) 也是有效的切分。

        【讨论】:

          【解决方案7】:

          看看以下,仅使用 1 个索引,假设数组的索引是从 1 开始的:

          int recursion(index, rightvalue, leftvalue, array)
          {
          if array=[] then
          {
              if rightvalue=leftvalue then return index
              else return -1
          }
          else
          {
              if rightvalue <= leftvalue
              { recursion(index+1, rightvalue+array[1], leftvalue, array[2..len(array)] }
              else 
              { recursion(index, rightvalue, leftvalue+array[len(array)], array[1..len(array)-1] }
          }
          
          int main_function(array)
          {
              return recursion(1, 0, 0, array)
          }
          

          【讨论】:

            【解决方案8】:
            public static Int32 SplitIndex(Int32[] array, Int32 left, Int32 right, Int32 leftsum, Int32 rightsum)
            {
                if (left == right - 1)
                {
                    return (leftsum == rightsum) ? left : -1;
                }
            
                if (leftsum > rightsum)
                {
                    return SplitIndex(array, left, right - 1, leftsum, rightsum + array[right - 1]);
                }
                else
                {
                    return SplitIndex(array, left + 1, right, leftsum + array[left + 1], rightsum);
                }
            }
            

            方法调用如下。

            Int32[] a = { 1, 2, 3, 1, 6, 1 };
            
            Console.WriteLine(SplitIndex(a, -1, a.Length, 0, 0));
            

            这可以简化为仅使用一个总和并以零为目标。

            public static Int32 SplitIndex(Int32[] array, Int32 left, Int32 right, Int32 sum)
            {
                if (left == right - 1)
                {
                    return (sum == 0) ? left : -1;
                }
            
                if (sum > 0)
                {
                    return SplitIndex(array, left, right - 1, sum - array[right - 1]);
                }
                else
                {
                    return SplitIndex(array, left + 1, right, sum + array[left + 1]);
                }
            }
            

            该方法现在调用如下。

            Int32[] a = { 1, 2, 3, 1, 6, 1 };
            
            Console.WriteLine(SplitIndex(a, -1, a.Length, 0));
            

            【讨论】:

            • 你是对的 - 它在负数上失败。我要再看一遍。
            • 对这个输入 {1,-100,1,1,-100,3} 无效,但是数组不能切入 {1,-100,1,1}和 {-100, 3} 两种情况的总和为 -97
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2015-12-19
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多