Как найти подмассив, который имеет сумму, ближайшую к нулю или определенное значение t в O (nlogn)
На самом деле это проблема № 10 главы 8 "Программирование жемчуга" 2-е издание. Он задал два вопроса: заданный массив A [] целых чисел (положительный и неположительный), как вы можете найти непрерывный подмассиво A [], чья сумма ближе всего к 0? Или ближе всего к определенному значению t?
Я могу придумать способ решения проблемы, ближайшей к 0. Вычислить префикс sum array S [], где S [i] = A [0] + A [1] +... + A [i], Затем сортируйте этот S в соответствии со значением элемента вместе с его исходной индексной информацией, чтобы найти субарейную сумму, ближайшую к 0, просто переместите массив S и выполните разность двух соседних значений и обновите минимальный абсолютный diff.
Вопрос в том, что лучший способ решить вторую проблему? Ближе к определенному значению t? Может ли кто-нибудь дать код или хотя бы алгоритм? (Если у кого-то есть лучшее решение для самой близкой к нулевой проблеме, ответы также приветствуются)
Ответы
Ответ 1
Чтобы решить эту проблему, вы можете построить дерево интервалов самостоятельно,
или сбалансированное дерево двоичного поиска, или даже полезно с карты STL, в O (nlogn).
Ниже приведена карта STL с lower_bound().
#include <map>
#include <iostream>
#include <algorithm>
using namespace std;
int A[] = {10,20,30,30,20,10,10,20};
// return (i, j) s.t. A[i] + ... + A[j] is nearest to value c
pair<int, int> nearest_to_c(int c, int n, int A[]) {
map<int, int> bst;
bst[0] = -1;
// barriers
bst[-int(1e9)] = -2;
bst[int(1e9)] = n;
int sum = 0, start, end, ret = c;
for (int i=0; i<n; ++i) {
sum += A[i];
// it->first >= sum-c, and with the minimal value in bst
map<int, int>::iterator it = bst.lower_bound(sum - c);
int tmp = -(sum - c - it->first);
if (tmp < ret) {
ret = tmp;
start = it->second + 1;
end = i;
}
--it;
// it->first < sum-c, and with the maximal value in bst
tmp = sum - c - it->first;
if (tmp < ret) {
ret = tmp;
start = it->second + 1;
end = i;
}
bst[sum] = i;
}
return make_pair(start, end);
}
// demo
int main() {
int c;
cin >> c;
pair<int, int> ans = nearest_to_c(c, 8, A);
cout << ans.first << ' ' << ans.second << endl;
return 0;
}
Ответ 2
Вы можете адаптировать свой метод. Предполагая, что у вас есть массив S
префиксов sum, как вы писали, и уже отсортированы в порядке возрастания суммы суммы. Ключевой концепцией является не только просмотр последовательных префиксных сумм, но вместо этого использование двух указателей для обозначения двух позиций в массиве S
. Написано в (слегка pythonic) псевдокоде:
left = 0 # Initialize window of length 0 ...
right = 0 # ... at the beginning of the array
best = ∞ # Keep track of best solution so far
while right < length(S): # Iterate until window reaches the end of the array
diff = S[right] - S[left]
if diff < t: # Window is getting too small
if t - diff < best: # We have a new best subarray
best = t - diff
# remember left and right as well
right = right + 1 # Make window bigger
else: # Window getting too big
if diff - t < best # We have a new best subarray
best = diff - t
# remember left and right as well
left = left + 1 # Make window smaller
Сложность связана сортировкой. Вышеуказанный поиск займет не более 2 n= O (n) итераций цикла, каждый из которых вычисляет время, ограниченное константой. Обратите внимание, что приведенный выше код был задуман для положительного t
.
Код был задуман для положительных элементов в S
и положителен t
. Если возникнут какие-либо отрицательные целые числа, вы можете столкнуться с ситуацией, когда исходный индекс right
меньше, чем исходный индекс left
. Таким образом, вы получите сумму подстроки -t
. Вы можете проверить это условие в тегах if … < best
, но если вы только подавите такие случаи там, я считаю, что вам могут быть недостающие некоторые соответствующие случаи. Итог: возьмите эту идею, подумайте об этом, но вам придется адаптировать ее для отрицательных чисел.
Примечание. Я думаю, что это та самая общая идея, которую Борис Странджев хотел выразить в своем решении. Тем не менее, я нашел, что решение несколько сложно читать и усложнять, поэтому я предлагаю свою формулировку этого.
Ответ 3
Ваше решение для дела 0 кажется мне удобным. Вот мое решение для второго случая:
- Вы снова вычисляете суммы префикса и сортируете.
- Вы инициализируете индексы
start
до 0 (первый индекс в отсортированном префиксном массиве) end
до last
(последний индекс префиксного массива)
- вы начинаете итерацию по
start
0... last
, и для каждого вы найдете соответствующий end
- последний индекс, в котором сумма префикса такова, что prefix[start]
+ prefix[end]
> t
, Когда вы обнаружите, что end
лучшим решением для start
является либо prefix[start]
+ prefix[end]
, либо prefix[start]
+ prefix[end - 1]
(последнее принимается, только если end
> 0)
- Самое главное, что вы не выполняете поиск
end
для каждого start
с нуля - prefix[start]
увеличивает значение при повторении всех возможных значений для start
, что означает, что на каждой итерации вы интересуется только значениями <= предыдущим значением end
.
- вы можете остановить итерацию, когда
start
> end
- вы берете лучшее из всех значений, полученных для всех позиций
start
.
Нетрудно убедиться, что это даст вам сложность O(n logn)
для всего алгоритма.
Ответ 4
Я нашел этот вопрос случайно. Хотя это было какое-то время, я просто публикую его. O (nlogn), O (n) пространственный алгоритм. Это код Java. Надеюсь, что это поможет людям.
import java.util.*;
public class FindSubarrayClosestToZero {
void findSubarrayClosestToZero(int[] A) {
int curSum = 0;
List<Pair> list = new ArrayList<Pair>();
// 1. create prefix array: curSum array
for(int i = 0; i < A.length; i++) {
curSum += A[i];
Pair pair = new Pair(curSum, i);
list.add(pair);
}
// 2. sort the prefix array by value
Collections.sort(list, valueComparator);
// printPairList(list);
System.out.println();
// 3. compute pair-wise value diff: Triple< diff, i, i+1>
List<Triple> tList = new ArrayList<Triple>();
for(int i=0; i < A.length-1; i++) {
Pair p1 = list.get(i);
Pair p2 = list.get(i+1);
int valueDiff = p2.value - p1.value;
Triple Triple = new Triple(valueDiff, p1.index, p2.index);
tList.add(Triple);
}
// printTripleList(tList);
System.out.println();
// 4. Sort by min diff
Collections.sort(tList, valueDiffComparator);
// printTripleList(tList);
Triple res = tList.get(0);
int startIndex = Math.min(res.index1 + 1, res.index2);
int endIndex = Math.max(res.index1 + 1, res.index2);
System.out.println("\n\nThe subarray whose sum is closest to 0 is: ");
for(int i= startIndex; i<=endIndex; i++) {
System.out.print(" " + A[i]);
}
}
class Pair {
int value;
int index;
public Pair(int value, int index) {
this.value = value;
this.index = index;
}
}
class Triple {
int valueDiff;
int index1;
int index2;
public Triple(int valueDiff, int index1, int index2) {
this.valueDiff = valueDiff;
this.index1 = index1;
this.index2 = index2;
}
}
public static Comparator<Pair> valueComparator = new Comparator<Pair>() {
public int compare(Pair p1, Pair p2) {
return p1.value - p2.value;
}
};
public static Comparator<Triple> valueDiffComparator = new Comparator<Triple>() {
public int compare(Triple t1, Triple t2) {
return t1.valueDiff - t2.valueDiff;
}
};
void printPairList(List<Pair> list) {
for(Pair pair : list) {
System.out.println("<" + pair.value + " : " + pair.index + ">");
}
}
void printTripleList(List<Triple> list) {
for(Triple t : list) {
System.out.println("<" + t.valueDiff + " : " + t.index1 + " , " + t.index2 + ">");
}
}
public static void main(String[] args) {
int A1[] = {8, -3, 2, 1, -4, 10, -5}; // -3, 2, 1
int A2[] = {-3, 2, 4, -6, -8, 10, 11}; // 2, 4, 6
int A3[] = {10, -2, -7}; // 10, -2, -7
FindSubarrayClosestToZero f = new FindSubarrayClosestToZero();
f.findSubarrayClosestToZero(A1);
f.findSubarrayClosestToZero(A2);
f.findSubarrayClosestToZero(A3);
}
}
Ответ 5
Сложность решения: O(NlogN)
Сложность пространства решения: O(N)
[Обратите внимание, что эта проблема не может быть решена в O (N), как утверждают некоторые из них]
Алгоритм: - Страница
- Вычислить совокупный массив (здесь
cum[]
) заданного массива [Строка 10]
- Сортировка совокупного массива [Строка 11]
- Ответ минимален среди
C[i]-C[i+1]
, $\ forall $i∈ [1, n-1] (индекс на основе 1) [Строка 12]
Код С++: -
#include<bits/stdc++.h>
#define M 1000010
#define REP(i,n) for (int i=1;i<=n;i++)
using namespace std;
typedef long long ll;
ll a[M],n,cum[M],ans=numeric_limits<ll>::max(); //cum->cumulative array
int main() {
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
cin>>n; REP(i,n) cin>>a[i],cum[i]=cum[i-1]+a[i];
sort(cum+1,cum+n+1);
REP(i,n-1) ans=min(ans,cum[i+1]-cum[i]);
cout<<ans; //min +ve difference from 0 we can get
}
Ответ 6
Подумав об этой проблеме, я обнаружил, что решение @frankyym - правильное решение. Я сделал некоторые уточнения в оригинальном решении, вот мой код:
#include <map>
#include <stdio.h>
#include <algorithm>
#include <limits.h>
using namespace std;
#define IDX_LOW_BOUND -2
// Return [i..j] range of A
pair<int, int> nearest_to_c(int A[], int n, int t) {
map<int, int> bst;
int presum, subsum, closest, i, j, start, end;
bool unset;
map<int, int>::iterator it;
bst[0] = -1;
// Barriers. Assume that no prefix sum is equal to INT_MAX or INT_MIN.
bst[INT_MIN] = IDX_LOW_BOUND;
bst[INT_MAX] = n;
unset = true;
// This initial value is always overwritten afterwards.
closest = 0;
presum = 0;
for (i = 0; i < n; ++i) {
presum += A[i];
for (it = bst.lower_bound(presum - t), j = 0; j < 2; --it, j++) {
if (it->first == INT_MAX || it->first == INT_MIN)
continue;
subsum = presum - it->first;
if (unset || abs(closest - t) > abs(subsum - t)) {
closest = subsum;
start = it->second + 1;
end = i;
if (closest - t == 0)
goto ret;
unset = false;
}
}
bst[presum] = i;
}
ret:
return make_pair(start, end);
}
int main() {
int A[] = {10, 20, 30, 30, 20, 10, 10, 20};
int t;
scanf("%d", &t);
pair<int, int> ans = nearest_to_c(A, 8, t);
printf("[%d:%d]\n", ans.first, ans.second);
return 0;
}
Ответ 7
В качестве побочного примечания: я согласен с алгоритмами, предоставленными другими потоками здесь. В последнее время есть еще один алгоритм.
Составьте еще одну копию A [], которая является B []. Внутри B [] каждый элемент A [i] -t/n, что означает B [0] = A [0] -t/n, B [1] = A [1] -t/n... B [п-1] = A [N-1] -t/п. Тогда вторая проблема фактически преобразуется в первую задачу, как только найден наименьший подмассив B [], ближайший к 0, подматрица A [], ближайшая к t, найдена в одно и то же время. (Любопытно сложно, если t не делится на n, тем не менее, точность должна быть выбрана соответствующей. Также время выполнения O (n))
Ответ 8
Я думаю, что есть небольшая ошибка относительно ближайшего к решению 0. На последнем шаге мы должны не только проверять разницу между соседними элементами, но и элементы, не близкие друг к другу, если одна из них больше 0, а другая меньше 0.
- Извините, я думал, что должен получить ответы на все вопросы. Не видел, что это требует только одного.
Ответ 9
Не будем использовать динамическое программирование для решения этого вопроса, аналогичного алгоритму kadane. Вот мое решение этой проблемы. Пожалуйста, прокомментируйте, если этот подход неверен.
#include <bits/stdc++.h>
using namespace std;
int main() {
//code
int test;
cin>>test;
while(test--){
int n;
cin>>n;
vector<int> A(n);
for(int i=0;i<n;i++)
cin>>A[i];
int closest_so_far=A[0];
int closest_end_here=A[0];
int start=0;
int end=0;
int lstart=0;
int lend=0;
for(int i=1;i<n;i++){
if(abs(A[i]-0)<abs(A[i]+closest_end_here-0)){
closest_end_here=A[i]-0;
lstart=i;
lend=i;
}
else{
closest_end_here=A[i]+closest_end_here-0;
lend=i;
}
if(abs(closest_end_here-0)<abs(closest_so_far-0)){
closest_so_far=closest_end_here;
start=lstart;
end=lend;
}
}
for(int i=start;i<=end;i++)
cout<<A[i]<<" ";
cout<<endl;
cout<<closest_so_far<<endl;
}
return 0;
}
Ответ 10
Вот реализация кода java:
public class Solution {
/**
* @param nums: A list of integers
* @return: A list of integers includes the index of the first number
* and the index of the last number
*/
public ArrayList<Integer> subarraySumClosest(int[] nums) {
// write your code here
int len = nums.length;
ArrayList<Integer> result = new ArrayList<Integer>();
int[] sum = new int[len];
HashMap<Integer,Integer> mapHelper = new HashMap<Integer,Integer>();
int min = Integer.MAX_VALUE;
int curr1 = 0;
int curr2 = 0;
sum[0] = nums[0];
if(nums == null || len < 2){
result.add(0);
result.add(0);
return result;
}
for(int i = 1;i < len;i++){
sum[i] = sum[i-1] + nums[i];
}
for(int i = 0;i < len;i++){
if(mapHelper.containsKey(sum[i])){
result.add(mapHelper.get(sum[i])+1);
result.add(i);
return result;
}
else{
mapHelper.put(sum[i],i);
}
}
Arrays.sort(sum);
for(int i = 0;i < len-1;i++){
if(Math.abs(sum[i] - sum[i+1]) < min){
min = Math.abs(sum[i] - sum[i+1]);
curr1 = sum[i];
curr2 = sum[i+1];
}
}
if(mapHelper.get(curr1) < mapHelper.get(curr2)){
result.add(mapHelper.get(curr1)+1);
result.add(mapHelper.get(curr2));
}
else{
result.add(mapHelper.get(curr2)+1);
result.add(mapHelper.get(curr1));
}
return result;
}
}