给定一系列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
。
很明显,我应该尝试做得比序列中可用的最大数量更好。但我无法弄清楚一个好的算法。
答案 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实现。 提供的评论有望使其可读。
"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;
正如您所看到的,程序会生成包含最大值的简化数组。通常,可以有许多具有此最大值的派生数组;只有一个。
答案 2 :(得分:1)
可以使用O(n*m)
时间和空间算法,根据您声明的限制,n <= 500
和m <= 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]
或其配对后代合并的序列来计算最佳值,然后设置一个行中下一个适用单元格的下限(意味着不能通过此单元格与早期序列合并)。这有效的原因是:
s[i + 1][0] > s[i][0]
,那么s[i + 1]
只能与s[i]
的新拆分部分配对;和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,以方便显示矩阵):
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;
答案 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