通过将两个相邻的x转换为一个(x + 1)可实现的最大数量

时间:2016-04-02 13:53:33

标签: algorithm brute-force

给定一系列N个整数1 <= N <= 500且数字介于1 and 50之间。在一个步骤中,任何两个相邻的相等数字x x可以替换为单个x + 1。这些步骤可达到的最大数量是多少。

例如,如果给定2 3 1 1 2 2,则可能的最大值为4

2 3 1 1 2 2 ---> 2 3 2 2 2 ---> 2 3 3 2 ---> 2 4 2

很明显,我应该尝试做得比序列中可用的最大数量更好。但我无法弄清楚一个好的算法。

4 个答案:

答案 0 :(得分:2)

输入的每个子字符串最多可以产生一个单个数字(不变量:两个总和中的两个与每个条目的幂的对数)。对于每个x,我们可以找到可以生成x的子字符串集。对于每个x,这是(1)每次出现x(2)可以使x - 1的两个连续子串的并集。得到的算法是O(N ^ 2) - 时间。

答案 1 :(得分:1)

算法可以这样工作:

将输入转换为每个元素都具有频率属性的数组,将输入中重复的连续值折叠到一个节点中。例如,此输入:

1 2 2 4 3 3 3 3

将表示如下:

{val: 1, freq: 1}  {val: 2, freq: 2}  {val: 4, freq: 1}  {val: 3, freq: 4}  

然后找到局部最小节点,如(3 3 3 3)中的节点1 (2 2) 4 (3 3 3 3) 4,即其邻居都具有较高值的​​节点。对于那些频率均匀的局部最小值,&#34; lift&#34;那些通过应用步骤。重复此操作,直到不再存在这样的局部最小值(偶数频率)。

启动算法的递归部分:

在阵列的两端,向内工作,然后升级&#34;只要内部邻居具有更高的值,就可以使用这些值。根据此规则,以下内容:

1 2 2 3 5 4 3 3 3 1 1

将彻底解决。首先从左侧向内:

1 4 5 4 3 3 3 1 1

然后从右侧:

1 4 6 3 2

请注意,当存在奇数频率时(如上面的3),会有一个&#34;余数&#34;不能增加。此规则中的余数应始终留在外侧,以最大化朝向阵列内部的潜力。

此时剩余的局部最小值具有奇数频率。将步骤应用于这样的节点将始终留下&#34;余数&#34; (如上所述)具有原始价值。这个剩余的节点可以出现在任何地方,但只考虑这个剩余部分位于电梯左侧或右侧(不在中间)的解决方案。例如:

4 1 1 1 1 1 2 3 4

可以解析其中一个:

4  2   2  1 2 3 4

或者:

4 1  2  2   2 3 4

1位于第二或第四位置,是上面提到的&#34;余数&#34;。显然,在这个例子中,第二种解决方法更有希望。一般来说,当一方存在一个太高而无法合并的值时,选择就很明显,就像最左边的4太高而无法获得五个1值。 4就像一堵墙。

当局部最小值的频率为1时,我们无能为力。它实际上将左侧和右侧的阵列分开,彼此不会相互影响。对于上面讨论的余数元素也是如此:它将数组分成两个不相互影响的部分。

因此,算法的下一步是找到这样的最小值(选择是显而易见的),应用这种步骤并将问题分成两个不同的问题,这些问题应该递归地解决(从顶部开始)。所以在最后一个例子中,以下两个问题将分别解决:

4
2 2 3 4

然后两种解决方案中最好的将作为整体解决方案。在这种情况下,5

算法中最具挑战性的部分是处理那些局部最小值,其中剩余部分的选择并不明显。例如;

3 3 1 1 1 1 1 2 3

这可以去:

3 3  2   2  1 2 3
3 3 1  2   2  2 3

在这个例子中,两个选项的最终结果是相同的,但是在更大的数组中,它会越来越不明显。因此,这两个选项都必须进行调查。一般来说,你可以拥有其中许多,例如本例中的2:

3 1 1 1 2 3 1 1 1 1 1 3

这两个最小值中的每一个都有两个选项。这似乎爆炸成更大阵列的太多可能性。但它并没有那么糟糕。该算法可以在相邻的最小值中采取相反的选择,并且通过整个阵列这样交替。这样,交替的部分是有利的,并且获得最大可能的值,而其他部分被剥夺了价值。现在算法转换表格,并切换所有选择,以便以前优先考虑的部分现在被剥夺,反之亦然。这两种替代方案的解决方案是通过递归地解析每个部分,然后比较两个&#34; grand&#34;选择最好的解决方案。

以下是上述算法的实时JavaScript实现。 提供的评论有望使其可读。

&#13;
&#13;
"use strict";

function Node(val, freq) {
    // Immutable plain object
    return Object.freeze({
        val: val,
        freq: freq || 1, // Default frequency is 1.
        // Max attainable value when merged:
        reduced: val + (freq || 1).toString(2).length - 1
    });
}

function compress(a) {
    // Put repeated elements in a single node
    var result = [], i, j;
    for (i = 0; i < a.length; i = j) {
        for (j = i + 1; j < a.length && a[j] == a[i]; j++);
        result.push(Node(a[i], j - i));
    }
    return result;
}

function decompress(a) {
    // Expand nodes into separate, repeated elements
    var result = [], i, j;
    for (i = 0; i < a.length; i++) {
        for (j = 0; j < a[i].freq; j++) {
            result.push(a[i].val);
        }
    }
    return result;
}

function str(a) {
    return decompress(a).join(' ');
}

function unstr(s) {
    s = s.replace(/\D+/g, ' ').trim();
    return s.length ? compress(s.split(/\s+/).map(Number)) : [];
}

/*
 The function merge modifies an array in-place, performing a "step" on 
 the indicated element.
 The array will get an element with an incremented value
 and decreased frequency, unless a join occurs with neighboring 
 elements with the same value: then the frequencies are accumulated
 into one element. When the original frequency was odd there will 
 be a "remainder" element in the modified array as well.
*/
function merge(a, i, leftWards, stats) {
    var val = a[i].val+1,
        odd = a[i].freq % 2,
        newFreq = a[i].freq >> 1,
        last = i;
    // Merge with neighbouring nodes of same value:
    if ((!odd || !leftWards) && a[i+1] && a[i+1].val === val) {
        newFreq += a[++last].freq;
    }
    if ((!odd || leftWards) && i && a[i-1].val === val) {
        newFreq += a[--i].freq;
    }   
    // Replace nodes
    a.splice(i, last-i+1, Node(val, newFreq));
    if (odd) a.splice(i+leftWards, 0, Node(val-1));
    
    // Update statistics and trace: this is not essential to the algorithm
    if (stats) {
        stats.total_applied_merges++;
        if (stats.trace) stats.trace.push(str(a));
    }
    return i;
}

/*  Function Solve
    Parameters:
        a:  The compressed array to be reduced via merges. It is changed in-place
            and should not be relied on after the call.
        stats:  Optional plain object that will be populated with execution statistics.
    Return value:
        The array after the best merges were applied to achieve the highest
        value, which is stored in the maxValue custom property of the array.
*/
function solve(a, stats) {
    var maxValue, i, j, traceOrig, skipLeft, skipRight, sections, goLeft,
        b, choice, alternate;
    
    if (!a.length) return a;

    if (stats && stats.trace) { 
        traceOrig = stats.trace;
        traceOrig.push(stats.trace = [str(a)]);
    }

    // Look for valleys of even size, and "lift" them
    for (i = 1; i < a.length - 1; i++) {
        if (a[i-1].val > a[i].val && a[i].val < a[i+1].val && (a[i].freq % 2) < 1) {
            // Found an even valley
            i = merge(a, i, false, stats);
            if (i) i--;
        }
    }
    // Check left-side elements with always increasing values
    for (i = 0; i < a.length-1 && a[i].val < a[i+1].val; i++) {
        if (a[i].freq > 1) i = merge(a, i, false, stats) - 1;
    };
    // Check right-side elements with always increasing values, right-to-left
    for (j = a.length-1; j > 0 && a[j-1].val > a[j].val; j--) {
        if (a[j].freq > 1) j = merge(a, j, true, stats) + 1;
    };
    // All resolved?
    if (i == j) {
        while (a[i].freq > 1) merge(a, i, true, stats);
        a.maxValue = a[i].val;
    } else {
        skipLeft = i;
        skipRight = a.length - 1 - j;
        // Look for other valleys (odd sized): they will lead to a split into sections
        sections = [];
        for (i = a.length - 2 - skipRight; i > skipLeft; i--) {
            if (a[i-1].val > a[i].val && a[i].val < a[i+1].val) {
                // Odd number of elements: if more than one, there
                // are two ways to merge them, but maybe 
                // one of both possibilities can be excluded.
                goLeft = a[i+1].val > a[i].reduced;
                if (a[i-1].val > a[i].reduced || goLeft) {
                    if (a[i].freq > 1) i = merge(a, i, goLeft, stats) + goLeft;
                    // i is the index of the element which has become a 1-sized valley
                    // Split off the right part of the array, and store the solution
                    sections.push(solve(a.splice(i--), stats));
                }
            }
        }
        if (sections.length) {
            // Solve last remaining section
            sections.push(solve(a, stats));
            sections.reverse();
            // Combine the solutions of all sections into one
            maxValue = sections[0].maxValue;
            for (i = sections.length - 1; i >= 0; i--) {
                maxValue = Math.max(sections[i].maxValue, maxValue);
            }
        } else {
            // There is no more valley that can be resolved without branching into two
            // directions. Look for the remaining valleys.
            sections = [];
            b = a.slice(0); // take copy
            for (choice = 0; choice < 2; choice++) {
                if (choice) a = b; // restore from copy on second iteration
                alternate = choice;
                for (i = a.length - 2 - skipRight; i > skipLeft; i--) {
                    if (a[i-1].val > a[i].val && a[i].val < a[i+1].val) {
                        // Odd number of elements
                        alternate = !alternate
                        i = merge(a, i, alternate, stats) + alternate;
                        sections.push(solve(a.splice(i--), stats));
                    }
                }
                // Solve last remaining section
                sections.push(solve(a, stats));
            }
            sections.reverse(); // put in logical order
            // Find best section:
            maxValue = sections[0].maxValue;
            for (i = sections.length - 1; i >= 0; i--) {
                maxValue = Math.max(sections[i].maxValue, maxValue);
            }
            for (i = sections.length - 1; i >= 0 && sections[i].maxValue < maxValue; i--);
            // Which choice led to the highest value (choice = 0 or 1)?
            choice = (i >= sections.length / 2)
            // Discard the not-chosen version
            sections = sections.slice(choice * sections.length/2);
        }
        // Reconstruct the solution from the sections.
        a = [].concat.apply([], sections);
        a.maxValue = maxValue;
    }
    if (traceOrig) stats.trace = traceOrig;
    return a;
}

function randomValues(len) {
    var a = [];
    for (var i = 0; i < len; i++) {
        // 50% chance for a 1, 25% for a 2, ... etc.
        a.push(Math.min(/\.1*/.exec(Math.random().toString(2))[0].length,5));
    }
    return a;
}

// I/O
var inputEl = document.querySelector('#inp');
var randEl = document.querySelector('#rand');
var lenEl = document.querySelector('#len');
var goEl = document.querySelector('#go');
var outEl = document.querySelector('#out');

goEl.onclick = function() {
    // Get the input and structure it
    var a = unstr(inputEl.value),
        stats = {
            total_applied_merges: 0,
            trace: a.length < 100 ? [] : undefined
        };
    // Apply algorithm
    a = solve(a, stats);
    // Output results
    var output = {
        value: a.maxValue,
        compact: str(a),
        total_applied_merges: stats.total_applied_merges,
        trace: stats.trace || 'no trace produced (input too large)'
    };
    outEl.textContent = JSON.stringify(output, null, 4);
}

randEl.onclick = function() {
    // Get input (count of numbers to generate):
    len = lenEl.value;
    // Generate
    var a = randomValues(len);
    // Output
    inputEl.value = a.join(' ');
    // Simulate click to find the solution immediately.
    goEl.click();
}

// Tests
var tests = [
    ' ', '',
    '1', '1',
    '1 1', '2',
    '2 2 1 2 2', '3 1 3',
    '3 2 1 1 2 2 3', '5',
    '3 2 1 1 2 2 3 1 1 1 1 3 2 2 1 1 2', '6',
    '3 1 1 1 3', '3 2 1 3',
    '2 1 1 1 2 1 1 1 2 1 1 1 1 1 2', '3 1 2 1 4 1 2',
    '3 1 1 2 1 1 1 2 3', '4 2 1 2 3',
    '1 4 2 1 1 1 1 1 1 1', '1 5 1',
];

var res;
for (var i = 0; i < tests.length; i+=2) {
    var res = str(solve(unstr(tests[i])));
    if (res !== tests[i+1]) throw 'Test failed: ' + tests[i] + ' returned ' + res + ' instead of ' + tests[i+1];
}
&#13;
Enter series (space separated):<br> 
<input id="inp" size="60" value="2 3 1 1 2 2"><button id="go">Solve</button>
<br>
<input id="len" size="4" value="30"><button id="rand">Produce random series of this size and solve</button>
<pre id="out"></pre>
&#13;
&#13;
&#13;

正如您所看到的,程序会生成包含最大值的简化数组。通常,可以有许多具有此最大值的派生数组;只有一个。

答案 2 :(得分:1)

可以使用O(n*m)时间和空间算法,根据您声明的限制,n <= 500m <= 58(考虑即使是十亿个元素,m也需要只有大约60,代表the largest element ± log2(n))。 m代表可能的数字50 + floor(log2(500))

考虑浓缩序列s = {[x, number of x's]}

如果M[i][j] = [num_j,start_idx]其中num_j表示在压缩序列的索引j处结束的最大连续i个数; start_idx,序列开始的索引或-1,如果它不能加入更早的序列;那么我们有以下关系:

M[i][j] = [s[i][1] + M[i-1][j][0], M[i-1][j][1]]
  when j equals s[i][0]

j大于s[i][0]但小于或等于s[i][0] + floor(log2(s[i][1])),表示转换对并与之前的序列合并(如果适用),并在新计数是奇数:

M[i][j][0]为奇数时,我们做两件事:首先通过回顾矩阵到可能与M[i][j]或其配对后代合并的序列来计算最佳值,然后设置一个行中下一个适用单元格的下限(意味着不能通过此单元格与早期序列合并)。这有效的原因是:

  1. 如果s[i + 1][0] > s[i][0],那么s[i + 1]只能与s[i]的新拆分部分配对;和
  2. 如果s[i + 1][0] < s[i][0],则s[i + 1]可能会生成较低j,与j中的奇M[i]结合,可能会产生更长的序列。< / LI>

    最后,返回矩阵中最大的条目max(j + floor(log2(num_j))), for all j

    JavaScript代码(欢迎反例;答案的限制设置为7,以方便显示矩阵):

    &#13;
    &#13;
    function f(str){
    
      var arr = str.split(/\s+/).map(Number);
    
      var s = [,[arr[0],0]];
    
      for (var i=0; i<arr.length; i++){
        if (s[s.length - 1][0] == arr[i]){
          s[s.length - 1][1]++;
        } else {
          s.push([arr[i],1]);
        }
      }
    
      var M = [new Array(8).fill([0,0])], 
          best = 0;
    
      for (var i=1; i<s.length; i++){
        M[i] = new Array(8).fill([0,i]);
    
        var temp = s[i][1],
            temp_odd,
            temp_start,
            odd = false;
    
        for (var j=s[i][0]; temp>0; j++){
       
          var start_idx = odd ? temp_start : M[i][j-1][1];
    
          if (start_idx != -1 && M[start_idx - 1][j][0]){
            temp += M[start_idx - 1][j][0];
            start_idx = M[start_idx - 1][j][1];
          }
    
          if (!odd){
            M[i][j] = [temp,start_idx];
            temp_odd = temp;
          } else {  
            M[i][j] = [temp_odd,-1];
            temp_start = start_idx;
          }
    
          if (!odd && temp & 1 && temp > 1){
            odd = true;
            temp_start = start_idx;
          }
    
          best = Math.max(best,j + Math.floor(Math.log2(temp)));
    
          temp >>= 1;
          temp_odd >>= 1;
        }
      }
    
      return [arr, s, best, M];
    }
    
    // I/O
    var button = document.querySelector('button');
    var input = document.querySelector('input');
    var pre = document.querySelector('pre');
    
    button.onclick = function() {
      var val = input.value;
      var result = f(val);
      var text = '';
      for (var i=0; i<3; i++){
        text += JSON.stringify(result[i]) + '\n\n';
      }
      for (var i in result[3]){
        text += JSON.stringify(result[3][i]) + '\n';
      }
      pre.textContent = text;
    }
    &#13;
    <input value ="2 2 3 3 2 2 3 3 5">
    <button>Solve</button>
    <pre></pre>
    &#13;
    &#13;
    &#13;

答案 3 :(得分:0)

这是一个强力解决方案:

function findMax(array A, int currentMax)
    for each pair (i, i+1) of indices for which A[i]==A[i+1] do
        currentMax = max(A[i]+1, currentMax)
        replace A[i],A[i+1] by a single number A[i]+1
        currentMax = max(currentMax, findMax(A, currentMax))
    end for
    return currentMax

Given the array A, let currentMax=max(A[0], ..., A[n])
print findMax(A, currentMax)

算法终止,因为在每次递归调用中,数组都会缩小1

同样清楚的是它是正确的:我们尝试所有可能的替换序列。

当阵列很大并且有很多关于替换的选项时,代码非常慢,但实际上在具有少量可替换对的阵列上工作得非常快。 (我将尝试根据可替换对的数量来量化运行时间。)

Python中一个天真的工作代码:

def findMax(L, currMax):
    for i in range(len(L)-1):
        if L[i] == L[i+1]:
            L[i] += 1
            del L[i+1]
            currMax = max(currMax, L[i])
            currMax = max(currMax, findMax(L, currMax))
            L[i] -= 1
            L.insert(i+1, L[i])
    return currMax

# entry point
if __name__ == '__main__':
    L1 = [2, 3, 1, 1, 2, 2]
    L2 = [2, 3, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2]
    print findMax(L1, max(L1))
    print findMax(L2, max(L2))

第一次通话的结果是4,正如所料。

第二次通话的结果是预期的5;给出结果的序列:2,3,1,1,2,2,2,2,2,2,2,2, - &gt; 2,3,1,1,3,2,2,2,2,2,2 - &gt; 2,3,1,1,3,3,2,2,2,2, - &gt; 2,3,1,1,3,3,3,2,2 - &gt; 2,3,1,1,3,3,3,3 - &gt; 2,3,1,1,4,3, - &gt; 2,3,1,1,4,4 - &gt; 2,3,1,1,5