查找集合中所有连续子串的总和的算法

时间:2017-05-16 10:15:34

标签: algorithm math numbers mathematical-optimization discrete-mathematics

我试着编写一个时间有效的算法,可以找到数组中所有可能的连续子串的总和(保留顺序和组合可以是任意长度)

例如:

[1,2,3,4] -> 1 + 2 + 3 + 4 + 12 + 23 + 34 + 123 + 234 + 1234 = 1670

同样重要的是要注意阵列可以重复多次

到目前为止,我最好的尝试可能就是:( n是数字集)

k = 3 // number of times the array repeats
length = len(n)
total = 0

for i in range(0, length*k):
    for exp in range(0, length*k-i): 
    //iterate though all of the possible powers of ten a certain number could be in
    // ie. all the different places that number could be in for all combinations 

        total += ((n[i % length] * 10**exp) * (i + 1))
        // ^ turns number from standard from into int. The i + 1 account for
        // the fact the number could be in the same position in more than one combination

return total

但是,这个算法必须运行一个包含超过10 ^ 20个数字的数组,所以我正在寻找更快的算法。

请注意,所有数字都是单个数字,数字可以重复

3 个答案:

答案 0 :(得分:2)

我们可以通过记录包含该元素的子数组的可能开始(左)和结束(右)位置的数量来计算任何给定幂10的任何给定元素的出现次数。

起始位置的数量只是左边的元素数量(+1),结束位置的数量只是右边元素的数量(+1)。例如,6中包含[4,5,6,7]的子数组将包含3个起始位置和2个结束位置:

s s s e e
↓ ↓ ↓ ↓ ↓
[4,5,6,7]

起始位置不会影响元素的显示效果 - 456566显示包含元素6的子数组的3个不同起始位置,但是对于他们所有人6都是10 0 。可能的起始位置的数量将是元素在某个位置出现的频率的直接乘数(3个起始位置 - >可以在每个位置出现3次)。

结尾位置会影响元素出现在10的幂,但不会影响其出现的次数:556567显示包含该元素的子数组的3个结束位置元素55出现在10 0 ,10 1 和10 2 ,每次只出现一次。我们可以使用5*111对此进行总结。

将这两件事放在一起,对任何元素的总和的影响是:

element * start positions * 111...(end positions times)...11

如上所述,start positions是1 +当前索引(基于0的数组)。当我们从阵列的左侧移动到右侧时,end positions减少1(或者当我们从右向左移动时,1减少1),因此,对于上面最右侧的术语,我们可以从右边int[] array = {1,2,3,4}; int sum = 0; int endMultiplier = 0; for (int i = array.length-1; i >= 0; i--) { endMultiplier = 10*endMultiplier + 1; sum += array[i] * (i+1) * endMultiplier; } System.out.println(sum); // prints 1670 ,然后乘以10并重复加1。

这导致了一些相当简单的(Java)代码:

endMultiplier

Live demo.

如果元素可以是多个数字,则可以推广上述方法,而不是将int endMultiplier = 1; int sum = 0; for (int i = array.length-1; i >= 0; i--) { sum += array[i] * (i+1) * endMultiplier; // Direct method of calculating k (with floating points): // int k = array[i] == 0 ? 10 : (int)Math.pow(10, 1+(int)Math.log10(array[i])); int k = 10; for (; k < array[i]; k *= 10) {} endMultiplier = k*endMultiplier + 1; } 乘以10,我们会根据当前元素的长度将其相乘(10长度1,100长度2,等等。)

select column2
from tbl1
where column1 in ('A','B','C','D')
order by column2 offset 4 rows fetch next 4 rows only

Live demo

答案 1 :(得分:1)

请注意,您可以:
1)每次迭代查找n[i] * (i + 1)次 2)找到1+10 + 100 + ...10^(length-i-1)之和作为算术级数之和

  s = 10^(length-i) - 1 / 9
  9999..99/9=1111..11

所以你可以获得O(n)复杂性。

3)更多优化 - 使1111...乘数每次迭代单次运算(整数除以初始值10或反转为m*10+1

答案 2 :(得分:1)

我终于找到了O(n)中的generalized version,它适用于任何长度的正数:

document.body.innerHTML = solve([1,2,3,4]) + " = 1670<br>";
document.body.innerHTML += solve([22,101,3]) + " = " + (22+101+3+22101+1013+221013);

function solve(arr) {
    let n = arr.length, total = 0, sum = 0;
    for (let i = n - 1; i >= 0; i--) {
        total += arr[i] * (i + 1) * (sum + 1);
        let f = Math.pow(10, len(arr[i]));
        sum = f + sum * f;
    }
    return total;
}

function len(x) {
    if (x < 10)
        return 1;
    return Math.floor(Math.log10(x)) + 1;
}

我通过将n-1的等式写出来n-3来提出公式:

n-1: a[n-1] * n
n-2: a[n-2] * (n-1) + a[n-2] * (n-1) * 10^len(a[n-1])
n-3: a[n-3] * (n-2) + a[n-3] * (n-2) * (10^len(a[n-2]) + 10^(len(a[n-1]) + len(a[n-2])))

第一个术语表示该数字在最后一个位置的次数。我们将10^...部分的总和表示为sum并将因子a[i] * (i+1)表示为+1来自*(sum+1)的地方。下一次迭代的sum10^len(a[i])+10^len(a[i])*sum。这是因为10^a + 10^a * (10^b + 10^(b+c) + ...) = 10^a + 10^(a+b) + 10^(a+b+c) + ...