วิธีที่เร็วที่สุดในการค้นหาผลิตภัณฑ์ขั้นต่ำขององค์ประกอบ 2 แถวที่มีองค์ประกอบมากกว่า 200,000 รายการ


13

ฉันมีอาเรa[n]ย์ หมายเลขnถูกป้อนโดยเรา ฉันต้องการค้นหาผลิตภัณฑ์ขั้นต่ำa[i]และa[j]ถ้า:

1) abs(i - j) > k

2) a[i] * a[j]ย่อเล็กสุด

นี่คือทางออกของฉัน (ไร้เดียงสามาก):

#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;
}

แต่ฉันต้องการทราบว่ามีวิธีใดที่เร็วกว่าในการหาผลิตภัณฑ์ที่มีระยะทางน้อยที่สุด?


7
เหตุใดฉันจึงไม่ #include <bits / stdc ++. h> และ C ++ จัดเตรียมVariable Length Arrayโดยส่วนขยายคอมไพเลอร์เท่านั้น ทำไมคุณไม่ใช้std::vector? @Seff - การเรียงลำดับจะทำลายความสัมพันธ์ "ระยะทาง" ดั้งเดิม
David C. Rankin

3
อย่างน้อยif (i!=j) if (abs(i - j) > k)สามารถยกเลิกการตรวจสอบได้ เพียงแค่เริ่มต้นภายในวงที่ i + k + for (ll j = i + k + 1; j < n; ++j)1: การตรวจสอบที่มีfirstสามารถตัดออกเช่นกันถ้าจะเริ่มต้นได้เช่นก่อนด้วยmn mn = a[0] * a[k + 1];(อาจkจะต้องมีการตรวจสอบnในขั้นต้นเพื่อให้หลักฐานแสดงหัวข้อนี้) แต่ก็ยังคงเป็น O (N²) ต้องทำได้เร็วกว่านี้ ...
Scheff

2
@PaulMcKenzie กรุณาแสดงหนึ่งแบบสอบถามที่มีไม่น้อยกว่าสองประโยชน์ฮิตหนึ่งในสิบครั้งแรกสำหรับสินค้าที่มีน้อยที่สุดด้วยระยะทางดัชนี (หรือสูงสุด)
greybeard

1
@PaulMcKenzie "อาจมีลิงค์เป็นร้อยเป็นร้อยถ้าไม่ใช่ลิงค์ URL ที่แสดงคำตอบสำหรับคำถามนี้" - โปรดแบ่งปัน URL เหล่านี้อย่างน้อยสามรายการ
גלעדברקן

2
คำถามนี้มาจากไหน มันไม่ได้เสียงเหมือนบางสิ่งบางอย่างที่สร้างขึ้นจากอากาศบาง ฉันจะไม่แปลกใจถ้ามันมาจากหนึ่งในเว็บไซต์ "ผู้ตัดสินออนไลน์" ถ้าเป็นเช่นนั้นในเว็บไซต์เหล่านั้นอาจมีการพูดคุยกันเป็นเวลานานในการแก้ปัญหาหากไม่สามารถแก้ปัญหาได้อย่างสมบูรณ์
PaulMcKenzie

คำตอบ:


12

สมมติว่ามีอย่างน้อยหนึ่งคู่ขององค์ประกอบที่ตรงตามเงื่อนไขและไม่มีการคูณของสององค์ประกอบในนั้นล้นซึ่งสามารถทำได้ในTheta(n-k)เวลาและTheta(1)พื้นที่ที่เลวร้ายที่สุด - และกรณีที่ดีที่สุดด้วยสิ่งนี้:

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;

นี่เป็นวิธีที่ดีที่สุดในแง่ของความซับซ้อนกรณีที่แย่ที่สุดสำหรับซีมโทติคทั้งเวลาและพื้นที่เพราะผลิตภัณฑ์ที่ดีที่สุดอาจอยู่a[0]กับn-(k+1)องค์ประกอบใด ๆในระยะทางอย่างน้อยk+1ดังนั้นn-(k+1)จำนวนเต็มอย่างน้อยต้องอ่านโดยอัลกอริทึมใด ๆ


แนวคิดเบื้องหลังอัลกอริทึมมีดังนี้:

ผลิตภัณฑ์ที่ดีที่สุดใช้สององค์ประกอบของaสมมติเหล่านี้เป็นและa[r] a[s]เราสามารถสันนิษฐานได้ว่าs > rเนื่องจากผลิตภัณฑ์ไม่ได้เป็นแบบทั่วไป

เนื่องจากข้อ จำกัดนี้ก็หมายความว่าabs(s-r) > k s >= k+1ตอนนี้sอาจเป็นแต่ละดัชนีที่สอดคล้องกับเงื่อนไขนี้ดังนั้นเราจึงทำซ้ำดัชนีเหล่านี้ นั่นคือการวนซ้ำiในรหัสที่แสดง แต่ถูกเลื่อนโดยk+1เพื่อความสะดวก (ไม่สำคัญจริงๆ) สำหรับการวนซ้ำแต่ละครั้งเราจำเป็นต้องค้นหาผลิตภัณฑ์ที่ดีที่สุดที่เกี่ยวข้องกับi+k+1ดัชนีที่ใหญ่ที่สุดและเปรียบเทียบกับการคาดเดาที่ดีที่สุดก่อนหน้านี้

ดัชนีที่เป็นไปได้ในการจับคู่i+k+1กับดัชนีทั้งหมดมีขนาดเล็กลงหรือเท่ากันiเนื่องจากข้อกำหนดระยะทาง เราจะต้องย้ำกว่าสิ่งเหล่านี้เป็นอย่างดี แต่ที่ไม่จำเป็นเพราะขั้นต่ำของa[i+k+1]*a[j]มากกว่าjที่คงที่iเท่ากับmin(a[i+k+1]*max(a[j]), a[i+k+1]*min(a[j]))เนื่องจาก monotonicity ของผลิตภัณฑ์ (การต่ำสุดที่เกี่ยวกับทั้งต่ำสุดและสูงสุดกว่าa[j]บัญชีสำหรับสองที่เป็นไปได้ สัญญาณของa[i+k+1]หรือเท่ากับสองทิศทางที่เป็นไปได้ของ monotonicity.)

เนื่องจากชุดของa[j]ค่าที่เราปรับให้เหมาะสมที่นี่เป็นเพียงแค่{a[0], ..., a[i]}ซึ่งการเติบโตโดยหนึ่งองค์ประกอบ ( a[i]) ในแต่ละการวนซ้ำของiเราจึงสามารถติดตามmax(a[j])และmin(a[j])มีตัวแปรเดียวโดยการปรับปรุงพวกเขาหากa[i]มีขนาดใหญ่กว่าหรือเล็กกว่าค่าที่เหมาะสมก่อนหน้า สิ่งนี้ทำได้ด้วยback_maxและback_minในตัวอย่างโค้ด

ขั้นตอนแรกของการทำซ้ำ ( i=0) ถูกข้ามในลูปและดำเนินการแทนเป็นการเริ่มต้นของตัวแปร


3
@greybeard ฉันไม่จำเป็นต้องเก็บไว้เพราะผู้สมัครที่เป็นไปได้เพียงอย่างเดียวสำหรับผลิตภัณฑ์ที่ดีที่สุดด้วยa[i+k+1]คือขั้นต่ำและสูงสุด
วอลนัท

คุณอธิบายได้ไหมว่าทำไมอัลกอริธึมทำงานในคำตอบของคุณ?
MinaHany

6

ไม่แน่ใจเรื่องการเร็วที่สุด

สำหรับปัญหาที่ง่ายขึ้นโดยไม่ต้องi <j - kผลิตภัณฑ์ขั้นต่ำสุดอยู่ในผลิตภัณฑ์ของคู่จากองค์ประกอบที่เล็กที่สุดและใหญ่ที่สุดสองรายการ

ดังนั้น (ต่อไปนี้ซับซ้อนเกินไปดูคำตอบของวอลนัต )
(•หยุดชะงักถ้า k ≤ n
  •ค่าเริ่มต้น minProduct เป็น [0] * a [k + 1])

  • รักษาโครงสร้างข้อมูล minmax แบบไดนามิก สองรายการไว้ในToIและBeyondIplusK โดย
    เริ่มจาก {} และ {a [ j ] | kj }
  • สำหรับแต่ละฉันจาก 0 ถึงn - k - 1
    • เพิ่ม [ i ] เพื่อupToI
    • ลบ [ i + k ] ออกจากBeyondIplusK
    • ตรวจสอบผลิตภัณฑ์ขั้นต่ำใหม่ระหว่าง
      min ( upToI ) × min ( BeyondIplusK ), min ( upToI ) × max ( BeyondIplusK ),
      สูงสุด ( upToI ) × min ( BeyondIplusK ) และสูงสุด ( upToI ) × max ( BeyondIplusK )

นี่ควรเป็นวิธีที่เร็วที่สุดและมีความซับซ้อนน้อยที่สุด มันเป็นเวลา O (n) และการเก็บรักษา
smttsp

โซลูชันดั้งเดิมมีความซับซ้อน O (N ** 2) คุณประเมินความซับซ้อนของโซลูชันอย่างไร
lenik

เวลา O (nlogn), พื้นที่ O (n) (สำหรับการใช้งาน minmax ที่เหมาะสม)
greybeard

@greybeard ทำไมคุณต้องใช้เวลา n * logn ทำไมไม่เพียงทำให้อาร์เรย์ 4 n * ที่มีminUpto, maxUpto, minBeyond, maxBeyond(คุณสามารถสร้างได้ในสองซ้ำ)? จากนั้นในการทำซ้ำครั้งที่สามสำหรับแต่ละดัชนีให้ค้นหาการคูณขั้นต่ำที่เป็นไปได้
smttsp

(@smttsp ที่จะเป็นขั้นตอนทางเลือกในทิศทางของวอลนัทของการแก้ปัญหา .)
greybeard

4

สำหรับ "ขนาดขั้นต่ำ"

ค้นหาองค์ประกอบ 2 "ขนาดเล็กที่สุด" จากนั้น (หลังจากคุณพบศูนย์สองศูนย์หรือค้นหาทั้งอาร์เรย์) แล้วคูณองค์ประกอบเหล่านั้น

สำหรับ "ค่าต่ำสุด" โดยไม่มีabs(i - j) > kส่วน

มี 3 ความเป็นไปได้:

  • ตัวเลขติดลบ (ขนาดเล็กที่สุด) สองค่าสูงสุด

  • ตัวเลขที่ไม่เป็นลบต่ำสุด (ขนาดเล็กที่สุด) สองค่า

  • ตัวเลขติดลบต่ำสุด (ขนาดใหญ่ที่สุด) และจำนวนไม่ติดลบสูงสุด (ขนาดใหญ่ที่สุด)

คุณสามารถค้นหาค่าทั้งหมด 6 ค่าและค้นหาผลิตภัณฑ์และสิ่งที่ดีที่สุดในตอนท้าย

อย่างไรก็ตาม; ทันทีที่คุณเห็นศูนย์คุณรู้ว่าคุณไม่จำเป็นต้องรู้เพิ่มเติมเกี่ยวกับความเป็นไปได้ 2 ประการแรก และทันทีที่คุณเห็นตัวเลขลบหนึ่งตัวและตัวเลขที่ไม่ใช่ลบคุณรู้ว่าคุณสนใจเฉพาะความเป็นไปได้ที่สาม

สิ่งนี้นำไปสู่เครื่องจักรสถานะ จำกัด ที่มี 3 สถานะ - "ใส่ใจเกี่ยวกับความเป็นไปได้ทั้ง 3 ข้อ", "คำตอบคือศูนย์เว้นแต่จะมีตัวเลขติดลบปรากฏ" และ "สนใจเฉพาะความเป็นไปได้สุดท้าย" สิ่งนี้สามารถนำมาใช้เป็นชุดของ 3 ลูปที่ 2 ของลูปกระโดดเข้าสู่ ( goto) ตรงกลางของลูปอื่นเมื่อการเปลี่ยนแปลงสถานะ (ของเครื่องสถานะ จำกัด )

โดยเฉพาะมันอาจดูเหมือนสิ่งที่คลุมเครือ (ยังไม่ทดลอง):

   // 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;

สำหรับ "ค่าต่ำสุด" พร้อมชิ้นabs(i - j) > kส่วน

ในกรณีนี้คุณยังมี 3 ความเป็นไปได้ และสามารถทำให้มันทำงานได้ด้วยวิธีการ "3 ลูปที่มีกลไก จำกัด แน่นอน" แต่มันก็ยุ่งเหยิง / น่าเกลียดเกินไป สำหรับกรณีนี้ทางเลือกที่ดีกว่ามีแนวโน้มที่จะสแกนอาร์เรย์ล่วงหน้าเพื่อตรวจสอบว่ามีศูนย์หรือไม่และถ้าทั้งหมดเป็นลบหรือเป็นบวกทั้งหมด เพื่อให้หลังจากการสแกนล่วงหน้าคุณสามารถรู้คำตอบว่าเป็นศูนย์หรือเลือกการวนซ้ำที่ออกแบบมาเพื่อความเป็นไปได้เฉพาะอย่างเดียว


1
บัญชีนี้อยู่ที่ระดับkล่างในความแตกต่างของดัชนีที่ไหน
greybeard

1
@greybeard: มันไม่ได้ (ฉันพลาดส่วนนั้นไป) - รหัสจะต้องได้รับการแก้ไขเพื่อนำมาพิจารณา
เบรนแดน

ทำไมคุณต้องมีสองศูนย์?
TrentP

@TrentP: Argh - ถูกต้อง ศูนย์หนึ่งก็เพียงพอที่จะรู้ว่าคำตอบคือ 0 หรือจำนวนลบ
เบรนแดน
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.