Cách nhanh nhất để tìm sản phẩm tối thiểu gồm 2 phần tử mảng chứa hơn 200000 phần tử


13

Tôi có một mảng a[n]. Số nđược nhập bởi chúng tôi. Tôi cần tìm sản phẩm tối thiểu của a[i]a[j]nếu:

1) abs(i - j) > k

2) a[i] * a[j]được giảm thiểu

Đây là giải pháp của tôi (rất ngây thơ):

#include <iostream>
using namespace std;
#define ll long long
int main() {
    ll n,k; cin >> n >> k;

    ll a[n]; for(ll i=0;i<n;i++) cin >> a[i];

    ll mn; bool first = true;

    for(ll i=0;i<n;i++) {
        for(ll j=0;j<n;j++) {
            if(i!=j)
            if(abs(i-j) > k) {
                if(first) {
                    mn = a[i]*a[j];
                    first = false;
                } else if(a[i]*a[j] < mn) mn = a[i]*a[j];
            }
        }
    }
    cout << mn << endl;
}

Nhưng tôi muốn biết liệu có cách nào nhanh hơn để tìm một sản phẩm tối thiểu với khoảng cách không?


7
Tại sao tôi không nên #include <bits / stdc ++. H>? và C ++ chỉ cung cấp Mảng độ dài biến đổi bằng phần mở rộng trình biên dịch. Tại sao bạn không sử dụng std::vector? @Scheff - sắp xếp sẽ phá hủy các mối quan hệ "khoảng cách" ban đầu.
David C. Rankin

3
Ít nhất là kiểm tra if (i!=j) if (abs(i - j) > k)có thể được loại bỏ. Chỉ cần bắt đầu vòng lặp bên trong tại i + k + 1 : for (ll j = i + k + 1; j < n; ++j). Việc kiểm tra firstcũng có thể được loại bỏ nếu mnđược khởi tạo trước, ví dụ như với mn = a[0] * a[k + 1];. (Có lẽ, knên được kiểm tra nban đầu để chống đạn này.) Nhưng nó vẫn là O (N²). Điều này phải được thực hiện nhanh hơn ...
Scheff

2
@PaulMcKenzie Vui lòng hiển thị một truy vấn có không dưới hai lần truy cập hữu ích trong số mười lần truy cập đầu tiên cho sản phẩm tối thiểu với khoảng cách chỉ mục (hoặc tối đa).
greybeard

1
@PaulMcKenzie "Có thể có hàng trăm, nếu không phải là hàng ngàn liên kết URL hiển thị câu trả lời cho câu hỏi này." - vui lòng chia sẻ ít nhất ba trong số các URL này.
גלעד ברקן

2
Câu hỏi này đến từ đâu? Nó không giống như một cái gì đó được tạo ra từ không khí mỏng. Tôi sẽ không ngạc nhiên nếu đó là từ một trong những trang web "thẩm phán trực tuyến". Nếu vậy, trên các trang web đó có lẽ là các cuộc thảo luận kéo dài về giải quyết vấn đề, nếu không phải là giải pháp đầy đủ.
PaulMcKenzie

Câu trả lời:


12

Giả sử có ít nhất một cặp yếu tố thỏa mãn điều kiện và không nhân hai yếu tố trong nó, điều này có thể được thực hiện trong Theta(n-k)thời gian và Theta(1)không gian tồi tệ nhất và trường hợp tốt nhất, với điều tương tự như sau:

auto back_max = a[0];
auto back_min = a[0];
auto best = a[0]*a[k+1];

for(std::size_t i=1; i<n-(k+1); ++i) {
    back_max = std::max(back_max, a[i]);
    back_min = std::min(back_min, a[i]);
    best = std::min(best, std::min(a[i+k+1]*back_max, a[i+k+1]*back_min));
}

return best;

Điều này là tối ưu về độ phức tạp trong trường hợp xấu nhất không có triệu chứng cho cả thời gian và không gian bởi vì sản phẩm tối ưu có thể a[0]với bất kỳ n-(k+1)yếu tố nào trong khoảng cách ít nhất k+1, do đó, ít nhất n-(k+1)các số nguyên cần phải được đọc bởi bất kỳ thuật toán nào giải quyết vấn đề.


Ý tưởng đằng sau thuật toán như sau:

Sản phẩm tối ưu sử dụng hai yếu tố a, giả sử đây là a[r]a[s]. Không mất tính tổng quát, chúng tôi có thể cho rằng s > rvì sản phẩm là giao hoán.

Do những hạn chế abs(s-r) > knày ngụ ý rằng s >= k+1. Bây giờ scó thể là mỗi chỉ số thỏa mãn điều kiện này, vì vậy chúng tôi lặp lại các chỉ số này. Đó là sự lặp lại itrong mã được hiển thị, nhưng nó được thay đổi bởi k+1sự thuận tiện (không thực sự quan trọng). Đối với mỗi lần lặp, chúng ta cần tìm sản phẩm tối ưu liên quan đến i+k+1chỉ số lớn nhất và so sánh nó với dự đoán tốt nhất trước đó.

Các chỉ số có thể ghép nối i+k+1với tất cả các chỉ số nhỏ hơn hoặc bằng nhau ido yêu cầu khoảng cách. Chúng tôi cũng cần phải lặp đi lặp lại tất cả những điều này, nhưng điều đó là không cần thiết bởi vì mức tối thiểu a[i+k+1]*a[j]trên jcố định ibằng min(a[i+k+1]*max(a[j]), a[i+k+1]*min(a[j]))với tính đơn điệu của sản phẩm (lấy mức tối thiểu đối với cả hai a[j]tài khoản tối thiểu và tối đa trên hai tài khoản có thể dấu hiệu của a[i+k+1]hoặc tương đương hai hướng đơn điệu có thể có.)

Vì tập hợp các a[j]giá trị mà chúng tôi tối ưu hóa ở đây chỉ là {a[0], ..., a[i]}, nó chỉ tăng trưởng theo một yếu tố ( a[i]) trong mỗi lần lặp i, nên chúng tôi chỉ cần theo dõi max(a[j])min(a[j])với các biến đơn bằng cách cập nhật chúng nếu a[i]lớn hơn hoặc nhỏ hơn các giá trị tối ưu trước đó. Điều này được thực hiện với back_maxback_mintrong ví dụ mã.

Bước đầu tiên của phép lặp ( i=0) được bỏ qua trong vòng lặp và thay vào đó được thực hiện dưới dạng khởi tạo các biến.


3
@greybeard Tôi không cần phải giữ chúng xung quanh, bởi vì những ứng cử viên duy nhất có thể cho một sản phẩm tối ưu a[i+k+1]là tối thiểu và tối đa.
quả óc chó

bạn có thể giải thích tại sao thuật toán hoạt động trong câu trả lời của bạn?
MinaHany

6

Không chắc chắn về nhanh nhất .

Đối với bài toán đơn giản hơn không có i <j - k , sản phẩm tối thiểu nằm trong số các sản phẩm của các cặp từ hai yếu tố nhỏ nhất và lớn nhất.

Vì vậy, (phần sau quá phức tạp, hãy xem câu trả lời của quả óc chó )
(• chùn bước nếu k ≤ n
  • khởi tạo minSản phẩm thành [0] * a [k + 1])

  • giữ hai cấu trúc dữ liệu minmax động lên ToIbeyondIplusK
    bắt đầu bằng {} và {a [ j ] | kj }
  • với mỗi i từ 0 đến n - k - 1
    • thêm [ i ] vào upToI
    • xóa [ i + k ] khỏi beyondIplusK
    • kiểm tra sản phẩm tối thiểu mới trong số
      min ( upToI ) × min ( beyondIplusK ), min ( upToI ) × max ( beyondIplusK ),
      max ( upToI ) × min ( beyondIplusK ) và max ( upToI ) × max ( beyondIplusK) )

Điều này nên là nhanh nhất, ít nhất là phức tạp-khôn ngoan. Đó là O (n) thời gian và lưu trữ.
smttsp

giải pháp ban đầu có độ phức tạp O (N ** 2), làm thế nào để bạn ước tính độ phức tạp của giải pháp của bạn?
lenik

Thời gian O (nlogn), không gian O (n) (để triển khai minmax phù hợp)
greybeard

@greybeard. Tại sao bạn cần n * logn time. Tại sao không chỉ đơn giản là giữ một 4 * n mảng chứa minUpto, maxUpto, minBeyond, maxBeyond(Bạn có thể tạo ra trong hai lặp lại)? Sau đó, trong lần lặp thứ ba, với mỗi chỉ số, hãy tìm phép nhân tối thiểu có thể.
smttsp

(@smttsp Đó sẽ là một bước thay thế theo hướng giải pháp của quả óc chó .)
greybeard

4

Đối với "cường độ tối thiểu"

Tìm 2 phần tử "cường độ nhỏ nhất", sau đó (sau khi bạn đã tìm thấy hai số không hoặc tìm kiếm toàn bộ mảng), nhân chúng.

Đối với "giá trị thấp nhất" không có abs(i - j) > k phần

Có 3 khả năng:

  • hai số âm cao nhất (cường độ nhỏ nhất)

  • hai số không âm thấp nhất (cường độ nhỏ nhất)

  • số âm thấp nhất (cường độ lớn nhất) và số không âm cao nhất (cường độ lớn nhất)

Bạn có thể tìm kiếm tất cả 6 giá trị và tìm ra sản phẩm nào là tốt nhất.

Tuy nhiên; ngay khi bạn thấy số 0 bạn biết bạn không cần biết thêm về 2 khả năng đầu tiên; và ngay khi bạn nhìn thấy một số âm và một số không âm, bạn biết rằng bạn chỉ quan tâm đến khả năng thứ ba.

Điều này dẫn đến một bộ máy trạng thái hữu hạn với 3 trạng thái - "quan tâm đến cả 3 khả năng", "câu trả lời là 0 trừ khi nhìn thấy số âm" và "chỉ quan tâm đến khả năng cuối cùng". Điều này có thể được thực hiện như một bộ 3 vòng lặp, trong đó 2 vòng lặp nhảy vào ( goto) giữa một vòng lặp khác khi trạng thái (của máy trạng thái hữu hạn) thay đổi.

Cụ thể, nó có thể trông giống một cái gì đó mơ hồ (chưa được kiểm tra):

   // It could be any possibility

   for(ll i=0;i<n;i++) {
       if(a[i] >= 0) {
            if(a[i] < lowestNonNegative1) {
                lowestNonNegative2 = lowestNonNegative1;
                lowestNonNegative1 = a[i];
            }
            if(lowestNonNegative2 == 0) {
                goto state2;
            }
       } else {
            if(a[i] > highestNegative1) {
                highestNegative2 = highestNegative1;
                highestNegative1= a[i];
            }
            if(lowestNonNegative1 < LONG_MAX) {
                goto state3;
            }
       }
   }
   if(lowestNonNegative2 * lowestNonNegative1 < highestNegative2 * highestNegative1) {
       cout << lowestNonNegative2 * lowestNonNegative1;
   } else {
       cout << highestNegative2 * highestNegative1;
   }
   return;

   // It will be zero, or a negative and a non-negative

   for(ll i=0;i<n;i++) {
state2:
       if(a[i] < 0) {
           goto state3;
       }
   }
   cout << "0";
   return;

   // It will be a negative and a non-negative

   for(ll i=0;i<n;i++) {
state3:
       if(a[i] < lowestNegative) {
           lowestNegative = a[i];
       } else if(a[i] > highestNonNegative) {
           highestNonNegative = a[i];
       }
    }
    cout << lowestNegative * highestNonNegative;
    return;

Đối với "giá trị thấp nhất" với abs(i - j) > kphần

Trong trường hợp này bạn vẫn có 3 khả năng; và có thể làm cho nó hoạt động với cùng một cách tiếp cận "3 vòng với máy trạng thái hữu hạn" nhưng nó trở nên quá lộn xộn / xấu xí. Trong trường hợp này, một sự thay thế tốt hơn có khả năng quét trước mảng để xác định xem có bất kỳ số không nào không và nếu tất cả chúng đều âm hoặc tất cả đều dương; để sau khi quét trước, bạn có thể biết câu trả lời là 0 hoặc chọn một vòng lặp được thiết kế cho khả năng cụ thể.


1
Trường hợp này tài khoản cho k ràng buộc thấp hơn về chênh lệch chỉ số?
greybeard

1
@greybeard: Nó không (tôi đã bỏ lỡ phần đó) - mã sẽ cần phải được sửa đổi để tính đến điều đó.
Brendan

Tại sao bạn cần hai số không?
TrentP

@TrentP: Argh - bạn nói đúng. Một số không là đủ để biết câu trả lời là 0 hoặc số âm.
Brendan
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.