배열에서 항목을 제거하여 정렬하고 요소 합계를 최대화하십시오.


13

이 과제는 입학 시험부터 비공개 사이버 보안 코스까지입니다. 어쨌든 사이버 보안과 관련이 없으며 학생들에게 논리적이고 코딩 기술을 테스트하는 것입니다.

직무

나머지 값이 엄격하게 감소하는 순서로 정렬되고 가능한 모든 감소하는 시퀀스 중에서 합계가 최대화되도록 배열에서 항목을 제거하는 프로그램을 작성하십시오.

입력과 출력

입력은 정수 값의 배열 될 것이다 엄격히 더 이상 0모두 서로 다르다 . 파일, 명령 행 또는 stdin에서 입력을 읽을 지 여부를 자유롭게 선택할 수 있습니다.

출력 은 입력 항목 의 내림차순으로 정렬 된 하위 배열이되며 그 합은 다른 가능한 내림차순으로 정렬 된 하위 배열보다 큽니다.

참고 : [5, 4, 3, 2] 의 부분 배열 인 [5, 4, 1, 3, 2]경우에도, 4그리고이 3인접하지 않은. 그냥 터 졌기 때문에 1.

Bruteforce 솔루션

물론 가장 간단한 해결책은 주어진 배열의 모든 가능한 조합 사이를 이동을하고에, 될 가장 큰 합과 정렬 된 하나를 검색 할 파이썬 :

import itertools

def best_sum_desc_subarray(ary):
    best_sum_so_far = 0
    best_subarray_so_far = []
    for k in range(1, len(ary)):
        for comb in itertools.combinations(ary, k):
            if sum(comb) > best_sum_so_far and all(comb[j] > comb[j+1] for j in range(len(comb)-1)):
                best_subarray_so_far = list(comb)
                best_sum_so_far = sum(comb)
    return best_subarray_so_far

불행하게도, 배열이 정렬되어 있는지 확인하고 요소의 합을 계산하는 것은 이 작업이 에서 ~ 까지의 시간에 수행 되므로 점근 적 시간 복잡도는

도전

당신의 목표는 위의 bruteforce보다 더 나은 시간 복잡성을 달성하는 것입니다. 가장 작은 점근 적 시간 복잡도를 가진 솔루션이 도전의 승자입니다. 두 솔루션이 동일한 점근 시간 복잡도를 갖는 경우 가장 작은 점근 공간 복잡도가 가장 큰 솔루션이 승자가됩니다.

참고 : 다수의 원자 에서도 읽기, 쓰기 및 비교를 고려할 수 있습니다 .

참고 : 둘 이상의 솔루션이있는 경우 둘 중 하나를 반환하십시오.

테스트 사례

Input:  [200, 100, 400]
Output: [400]

Input:  [4, 3, 2, 1, 5]
Output: [4, 3, 2, 1]

Input:  [50, 40, 30, 20, 10]
Output: [50, 40, 30, 20, 10]

Input:  [389, 207, 155, 300, 299, 170, 158, 65]
Output: [389, 300, 299, 170, 158, 65]

Input:  [19, 20, 2, 18, 13, 14, 8, 9, 4, 6, 16, 1, 15, 12, 3, 7, 17, 5, 10, 11]
Output: [20, 18, 16, 15, 12, 7, 5]

Input:  [14, 12, 24, 21, 6, 10, 19, 1, 5, 8, 17, 7, 9, 15, 23, 20, 25, 11, 13, 4, 3, 22, 18, 2, 16]
Output: [24, 21, 19, 17, 15, 13, 4, 3, 2]

Input:  [25, 15, 3, 6, 24, 30, 23, 7, 1, 10, 16, 29, 12, 13, 22, 8, 17, 14, 20, 11, 9, 18, 28, 21, 26, 27, 4, 2, 19, 5]
Output: [25, 24, 23, 22, 17, 14, 11, 9, 4, 2]

관련. (현재 두 알고리즘이 동일한 지 여부를 확인할 수는 없지만 두 알고리즘이 동일하다고 생각합니다.)
Martin Ender

의견은 긴 토론을위한 것이 아닙니다. 이 대화는 채팅 으로 이동 되었습니다 .
Martin Ender 2013

답변:


3

시간은 O (n ^ 2), 공간은 O (n)이어야합니다.

한 줄에 공백으로 구분 된 숫자를 STDIN에 제공

#!/usr/bin/perl -a
use strict;
use warnings;

# use Data::Dumper;
use constant {
    INFINITY => 9**9**9,
    DEBUG    => 0,
};

# Recover sequence from the 'how' linked list
sub how {
    my @z;
    for (my $h = shift->{how}; $h; $h = $h->[1]) {
        push @z, $h->[0];
    }
    pop @z;
    return join " ", reverse @z;
}

use constant MINIMUM => {
    how  => [-INFINITY, [INFINITY]],
    sum  => -INFINITY,
    next => undef,
};

# Candidates is a linked list of subsequences under consideration
# A given final element will only appear once in the list of candidates
# in combination with the best sum that can be achieved with that final element
# The list of candidates is reverse sorted by final element
my $candidates = {
    # 'how' will represent the sequence that adds up to the given sum as a
    # reversed lisp style list.
    # so e.g. "1, 5, 8" will be represented as [8, [5, [1, INFINITY]]]
    # So the final element will be at the front of 'how'
    how  => [INFINITY],
    # The highest sum that can be reached with any subsequence with the same
    # final element
    sum  => 0,
    # 'next' points to the next candidate
    next => MINIMUM,   # Dummy terminator to simplify program logic
};

for my $num (@F) {
    # Among the candidates on which an extension with $num is valid
    # find the highest sum
    my $max_sum = MINIMUM;
    my $c = \$candidates;
    while ($num < $$c->{how}[0]) {
        if ($$c->{sum} > $max_sum->{sum}) {
            $max_sum = $$c;
            $c = \$$c->{next};
        } else {
            # Remove pointless candidate
            $$c = $$c->{next};
        }
    }

    my $new_sum = $max_sum->{sum} + $num;
    if ($$c->{how}[0] != $num) {
        # Insert a new candidate with a never before seen end element
        # Due to the unique element rule this branch will always be taken
        $$c = { next => $$c };
    } elsif ($new_sum <= $$c->{sum}) {
        # An already known end element but the sum is no improvement
        next;
    }
    $$c->{sum} = $new_sum;
    $$c->{how} = [$num, $max_sum->{how}];
    # print(Dumper($candidates));
    if (DEBUG) {
        print "Adding $num\n";
        for (my $c = $candidates; $c; $c = $c->{next}) {
            printf "sum(%s) = %s\n", how($c), $c->{sum};
        }
        print "------\n";
    }
}

# Find the sequence with the highest sum among the candidates
my $max_sum = MINIMUM;
for (my $c = $candidates; $c; $c = $c->{next}) {
    $max_sum = $c if $c->{sum} > $max_sum->{sum};
}

# And finally print the result
print how($max_sum), "\n";

3

O(nlogn)O(n)

{-# LANGUAGE MultiParamTypeClasses #-}

import qualified Data.FingerTree as F

data S = S
  { sSum :: Int
  , sArr :: [Int]
  } deriving (Show)

instance Monoid S where
  mempty = S 0 []
  mappend _ s = s

instance F.Measured S S where
  measure = id

bestSubarrays :: [Int] -> F.FingerTree S S
bestSubarrays [] = F.empty
bestSubarrays (x:xs) = left F.>< sNew F.<| right'
  where
    (left, right) = F.split (\s -> sArr s > [x]) (bestSubarrays xs)
    sLeft = F.measure left
    sNew = S (x + sSum sLeft) (x : sArr sLeft)
    right' = F.dropUntil (\s -> sSum s > sSum sNew) right

bestSubarray :: [Int] -> [Int]
bestSubarray = sArr . F.measure . bestSubarrays

작동 원리

bestSubarrays xsxs합계를 늘리고 첫 번째 요소를 증가시켜 왼쪽에서 오른쪽으로 정렬 된 {가장 큰 합계, 가장 작은 첫 번째 요소}의 효율적인 경계에있는 하위 배열의 시퀀스입니다 .

에서 이동하기 bestSubarrays xsbestSubarrays (x:xs), 우리

  1. 이하 제 요소와 좌측으로 순서를 분할 x이상 첫번째 요소 및 우측면 x,
  2. x왼쪽에서 가장 오른쪽에있는 하위 배열 앞에 추가하여 새 하위 배열을 찾습니다.
  3. 오른쪽에서 하위 배열의 접두사를 새 하위 배열보다 작은 합계로 삭제합니다.
  4. 왼쪽, 새 하위 배열 및 나머지 오른쪽을 연결합니다.

O(logn)


1

이 답변은 Ton Hospel의 답변으로 확장됩니다.

재귀를 사용하는 동적 프로그래밍으로 문제를 해결할 수 있습니다.

T(i)=ai+max[{0}{T(j)|0j<iaiaj}]

여기서 는 입력 시퀀스이고 는 인덱스 끝나는 감소하는 하위 시퀀스의 최대 달성 가능한 합계입니다 . 그런 다음 다음 녹 코드에서와 같이 사용하여 실제 솔루션을 다시 추적 할 수 있습니다 .T ( i ) i T(ai)T(i)iT

fn solve(arr: &[usize]) -> Vec<usize> {
    let mut tbl = Vec::new();
    // Compute table with maximum sums of any valid sequence ending
    // with a given index i.
    for i in 0..arr.len() {
        let max = (0..i)
            .filter(|&j| arr[j] >= arr[i])
            .map(|j| tbl[j])
            .max()
            .unwrap_or(0);
        tbl.push(max + arr[i]);
    }
    // Reconstruct an optimal sequence.
    let mut sum = tbl.iter().max().unwrap_or(&0).clone();
    let mut limit = 0;
    let mut result = Vec::new();

    for i in (0..arr.len()).rev() {
        if tbl[i] == sum && arr[i] >= limit {
            limit = arr[i];
            sum -= arr[i];
            result.push(arr[i]);
        }
    }
    assert_eq!(sum, 0);
    result.reverse();
    result
}

fn read_input() -> Vec<usize> {
    use std::io::{Read, stdin};
    let mut s = String::new();
    stdin().read_to_string(&mut s).unwrap();
    s.split(|c: char| !c.is_numeric())
        .filter(|&s| !s.is_empty())
        .map(|s| s.parse().unwrap())
        .collect()
}

fn main() {
    println!("{:?}", solve(&read_input()));
}

온라인으로 사용해보십시오!

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.