将数组划分为k个连续的子数组,以使每个子数组之和的按位与最大

时间:2019-04-10 21:00:54

标签: java algorithm

给出大小为n(n <= 50)的数组,其中包含正整数。 您必须以使所有子数组总和的按位k最大化的方式将数组划分为AND个连续的子数组。

例如对于array=[30,15,26,16,21]k=3,请考虑所有分区:

  • (30)和(15)和(26 + 16 + 21)= 14
  • (30)&(15 + 26)&(16 + 21)= 0
  • (30)和(15 + 26 + 16)和(21)= 16
  • (30 + 15)和(26 + 16)和(21)= 0
  • (30 + 15)和(26)&(16 + 21)= 0
  • (30 + 15 + 26)&(16)&(21)= 0

最大为16,因此该数组的答案为16。

除了蛮力之外,我什么都没有。请帮忙。

static void findMaxAND(int[] arr,int k){
        if (k>arr.length){
            System.out.println(0);
            return;
        }
        int n=arr.length;
        int[] parSum=new int[n];
        parSum[0]=arr[0];
        for (int i=1;i<n;i++){
            parSum[i]+=parSum[i-1]+arr[i];
        }

        int upperSum=parSum[n-1]/k;
        int upperBit=(int)Math.floor((Math.log10(upperSum)/Math.log10(2)));

        partitions=new ArrayList<>();
        while (true){
            int min=(int)Math.pow(2,upperBit);

            check(arr,min,-1,new ArrayList<>(),1,k);
            if (!partitions.isEmpty()){
                int maxAND=Integer.MIN_VALUE;
                for (List<Integer> partiton:partitions){
                    partiton.add(n-1);
                    int innerAND=parSum[partiton.get(0)];
                    for (int i=1;i<partiton.size();i++){
                        innerAND&=(parSum[partiton.get(i)]-parSum[partiton.get(i-1)]);
                    }

                    maxAND=Math.max(maxAND,innerAND);
                }
                System.out.println(maxAND);
                break;
            }

            upperBit--;
        }
    }

    private static List<List<Integer>> partitions;

    static void check(int[] arr,int min,int lastIdx,List<Integer> idxs,int currPar,int k){
        int sum=0;
        if (currPar==k){
            if (lastIdx>=arr.length-1){
                return;
            }
            int i=lastIdx+1;
            while (i<arr.length){
                sum+=arr[i];
                i++;
            }
            if ((sum&min)!=0){
                partitions.add(new ArrayList<>(idxs));
            }
        }
        if (currPar>k||lastIdx>=(arr.length-1)){
            return;
        }

        sum=0;
        for (int i=lastIdx+1;i<arr.length;i++){
            sum+=arr[i];
            if ((sum&min)!=0){
                idxs.add(i);
                check(arr,min,i,idxs,currPar+1,k);
                idxs.remove(idxs.size()-1);
            }
        }
    }

它正在工作,但是时间复杂度太差了。

2 个答案:

答案 0 :(得分:2)

以下是非递归动态编程解决方案(在JavaScript中,尽管将其移植到Java应该非常简单)。它的工作方式与user3386109的注释和גלעדברקן的答案所建议的类似,尽管我不确定是否完全相同。 (它比我测试时גלעדברקן的答案要好得多,但这可能是由于实现上的细微差异,而不是有意义的概念差异。)

它的整体复杂度是最坏情况下的 O n 2 kb )时间和 O nk )多余的空间,其中<​​em> b 是要尝试的位数-我只是将其硬编码为下面的31,尽管在实践中效果还不错您可以根据需要通过排除更大的数字来对其进行优化。 (注意,我在这里假设加法和按位与是 O (1)。如果必须支持 really 大数,则实际的最坏情况下的时间复杂度将是 O n 2 kb 2 )。)

有关详细信息,请参见代码注释。

function f(array, numSegments) {
  const n = array.length;
  const maxBit = (1 << 30); // note: can improve if desired

  if (numSegments > n) {
    throw 'Too many segments.';
  }

  /* prefixSums[i] will be the sum of array[0..(i-1)], so that
   * the sum of array[i..j] will be prefixSums[j+1]-prefixSums[i].
   * This is a small optimization and code simplification, but the
   * same asymptotic complexity is possible without it.
   */
  const prefixSums = [];
  prefixSums[0] = 0;
  for (let i = 1; i <= n; ++i) {
    prefixSums.push(prefixSums[i-1] + array[i-1]);
  }

  /* bestKnownBitmask will be the result -- the best bitmask that we
   * could achieve. It will grow by one bit at a time; for example,
   * if the correct answer is binary 1011, then bestKnownBitmask will
   * start out as 0000, then later become 1000, then later 1010, and
   * finally 1011.
   */
  let bestKnownBitmask = 0;

  /* startIndices[seg] will be a list of start-indices where
   * it's possible to divide the range from such a start-index to
   * the end of the array into 'seg' segments whose sums all satisfy
   * a given bitmask condition.
   *
   * In particular, startIndices[0] will always be [n], because the
   * only way to get zero segments is to have zero elements left; and
   * startIndices[numSegments][0] will always be 0, because we only
   * keep a bitmask condition if we successfully found a way to
   * partition the entire array (0..(n-1)) into 'numSegments' segments
   * whose sums all satisfied it.
   */
  let startIndices = [];
  startIndices.push([n]);
  for (let seg = 1; seg <= numSegments; ++seg) {
    startIndices.push([]);
    for (let i = numSegments - seg; i <= n - seg; ++i) {
      startIndices[seg].push(i);
    }
  }

  for (let currBit = maxBit; currBit > 0; currBit >>= 1) {
    const bitmaskToTry = (bestKnownBitmask | currBit);

    const tmpStartIndices = startIndices.map(row => []); // empty copy
    tmpStartIndices[0].push(n);

    for (let seg = 1; seg <= numSegments; ++seg) {
      for (const startIndex of startIndices[seg]) {
        for (const nextIndex of tmpStartIndices[seg-1]) {
          if (nextIndex <= startIndex) {
            continue;
          }
          const segmentSum = prefixSums[nextIndex] - prefixSums[startIndex];
          if ((segmentSum & bitmaskToTry) === bitmaskToTry) {
            tmpStartIndices[seg].push(startIndex);
            break;
          }
        }
      }
    }

    if (tmpStartIndices[numSegments].length > 0
        && tmpStartIndices[numSegments][0] === 0) {
      // success!
      bestKnownBitmask = bitmaskToTry;
      startIndices = tmpStartIndices;
    }
  }

  return bestKnownBitmask;
}

function runFunctionAndLogResult(array, numSegments) {
  let startTime = performance.now();
  let result = f(array, numSegments);
  let endTime = performance.now();
  console.log(
    'array = [' + array.join(', ') + ']\n' +
    'k = ' + numSegments + '\n' +
    'result = ' + result + '\n' +
    'time = ' + (endTime - startTime) + ' ms'
  );
}

runFunctionAndLogResult(
  [ 25, 40, 45, 69, 26, 13, 49, 49, 84, 67, 30, 22, 43, 82, 2, 95, 96, 63, 78, 26, 95, 57, 80, 8, 85, 23, 64, 85, 12, 66, 74, 69, 9, 35, 69, 89, 34, 2, 60, 91, 79, 99, 64, 57, 52, 56, 89, 20, 8, 85 ],
  12
);

答案 1 :(得分:1)

这是在注释中引用user3386109的建议的想法,尽管我们没有将可能的子数组my_var_作为参数,而是将当前的最高设置位设置为

给出具有最高设置位AND的前缀,我们想返回所有b后缀为AND的组合。如果没有,则无法使用此位,因此请尝试较低的位。请注意,从具有最高固定位集的所有可能分区生成的值将必然包括其中最好的总体答案。

下面的递归将b作为参数(和搜索空间),并作为结果的可能值列表。我们将备忘录既应用于单个呼叫,也应用于针对具有固定的左和右索引的呼叫范围的呼叫汇总(似乎像蛮力,不是吗?)。

随后是JavaScript代码(不确定是否能始终如一地工作:)除非我犯了一些错误,否则很难得到退化的数据,这似乎就像在user3386109所建议的那样,修复了高位,从而大大缩小了搜索空间。

left_index, right_index, current_k, bth_bit_set

样本输出

function f(arr, K){
  let str = `Array:\n${ arr.join('\n') }` + 
            `\n\nK: ${ K }\n\n`

  let hash = {
    f: {},
    f_range: {}
  }

  function g(l, r, k, b, A, K){
    // Out of bounds
    if (r > A.length - 1 || k > K || b < 0)
      return []

    if (hash.f.hasOwnProperty([l, r, k, b]))
      return hash.f[[l, r, k, b]]
      
    let s = pfxs[r] - pfxs[l-1]
    
    // This sum does not have
    // the bth bit set
    if (!(s & (1 << b)))
      return hash.f[[l, r, k, b]] = []
      
    if (r == A.length - 1){
      if (k < K)
        return hash.f[[l, r, k, b]] = []
      else
        return hash.f[[l, r, k, b]] = [s]
    }
    
    if (k == K){
      if (r == A.length - 1)
        return hash.f[[l, r, k, b]] = s & (1 << b) ? [s] : []
      else
        return hash.f[[l, r, k, b]] = g(l, r + 1, k, b, A, K)
    }
    
    // Possible suffixes
    let sfxs = []
    // Number of parts outstanding
    let ks = K - k
    // Upper bound for next part
    let ub = A.length - ks + 1
    
    if (hash.f_range.hasOwnProperty([r + 1, ub, k + 1, b])){
      sfxs = hash.f_range[[r + 1, ub, k + 1, b]]

    } else {
      for (let rr=r+1; rr<ub; rr++)
        sfxs = sfxs.concat(
          g(r + 1, rr, k + 1, b, A, K)
        )
      hash.f_range[[r + 1, ub, k + 1, b]] = sfxs
    }
        
    // We have a possible solution
    if (sfxs.length){
      result = []
      
      for (let sfx of sfxs)
        result.push(s & sfx)
      
      return hash.f[[l, r, k, b]] = result
    
    } else {
      return []
    }
  }

  // Array's prefix sums
  let pfxs = [arr[0]]
  for (let i=1; i<arr.length; i++)
    pfxs[i] = arr[i] + pfxs[i - 1]
  pfxs[-1] = 0

  let highBit = -1

  let maxNum = arr.reduce((acc, x) => acc + x, 0)
  while (maxNum){
    highBit++
    maxNum >>= 1
  }

  str += `\nhigh bit: ${ highBit }`

  let best = 0

  for (let b=highBit; b>=0; b--){
    for (let r=0; r<arr.length-K+1; r++){
      let result = g(0, r, 1, b, arr, K)
      //str += `\n${ JSON.stringify(result) }`
      if (result.length)
        best = Math.max(best, Math.max.apply(null, result))
    }
    if (best)
      break
  }
  console.log(str + '\n')
  return best
}

let arr = [30, 15, 26, 16, 21]
let K = 3
console.log(`result: ${ f(arr, K) }\n\n`)

let rand_arr = []
let rand_len = Math.ceil(Math.random() * 49)
for (let i=0; i<rand_len; i++){
  let rand_exp = ~~(Math.random() * 30)
  rand_arr[i] = Math.ceil(Math.random() * (1 << rand_exp))
}
let rand_k = Math.ceil(Math.random() * rand_len)

console.log(`result: ${ f(rand_arr, rand_k) }\n\n`)

const ex = [ 25, 40, 45, 69, 26, 13, 49, 49, 84, 67, 30, 22, 43, 82, 2, 95, 96, 63, 78, 26, 95, 57, 80, 8, 85, 23, 64, 85, 12, 66, 74, 69, 9, 35, 69, 89, 34, 2, 60, 91, 79, 99, 64, 57, 52, 56, 89, 20, 8, 85 ]

console.log(`result: ${ f(ex, 12) }`)

更多示例输出:

Array:
9598
15283236
121703215
80
25601067
761
7071
428732360
238244
2
176
116076
4
3517
491766404
5619908
39459923
330411
8
38

K: 5


high bit: 30

result: 4259840