ให้สตริงเป็นตัวเลขหนึ่งล้านส่งคืนตัวเลข 3 หลักที่ซ้ำกันทั้งหมด


137

ฉันได้สัมภาษณ์กับ บริษัท กองทุนป้องกันความเสี่ยงในนิวยอร์กเมื่อไม่กี่เดือนที่ผ่านมาและโชคไม่ดีที่ฉันไม่ได้รับการฝึกงานในฐานะวิศวกรข้อมูล / ซอฟต์แวร์ (พวกเขายังขอให้วิธีการแก้ปัญหาอยู่ในหลาม)

ฉันเมามากขึ้นกับปัญหาการสัมภาษณ์ครั้งแรก ...

คำถาม: รับสตริงเป็นล้านตัวเลข (เช่น Pi) เขียนฟังก์ชัน / โปรแกรมที่ส่งกลับตัวเลข 3 หลักซ้ำและจำนวนการทำซ้ำที่มากกว่า 1

ตัวอย่างเช่น: ถ้าสตริงเป็น: 123412345123456ดังนั้นฟังก์ชัน / โปรแกรมจะส่งคืน:

123 - 3 times
234 - 3 times
345 - 2 times

พวกเขาไม่ได้ให้คำตอบกับฉันหลังจากที่ฉันสัมภาษณ์ล้มเหลว แต่พวกเขาบอกฉันว่าเวลาในการแก้ปัญหานั้นมีค่าคงที่ 1,000 เนื่องจากผลลัพธ์ที่เป็นไปได้ทั้งหมดอยู่ระหว่าง:

000 -> 999

ตอนนี้ฉันกำลังคิดเกี่ยวกับมันฉันไม่คิดว่ามันเป็นไปได้ที่จะเกิดอัลกอริธึมเวลาคงที่ ใช่ไหม?


68
หากพวกเขาคิดว่าวิธีแก้ปัญหานั้นมีค่าคงที่ 1,000 นั่นก็ทำให้ฉันคิดว่าพวกเขาจะสร้างตัวเลขสามหลักทั้งหมดแล้วจึงค้นหา regex เป็นเรื่องธรรมดามากที่ผู้คนจะคิดว่าการดำเนินการที่พวกเขาไม่ได้เขียน / ดูเป็น "ฟรี" ฉันค่อนข้างแน่ใจว่านี่จะเป็นเส้นตรงกับความยาวของสตริง
mypetlion

54
ถ้าขนาดอินพุตเป็นค่าคงที่ทุกอัลกอริทึมจะเป็นเวลาคงที่ ;-)
Pa --lo Ebermann

34
คง 1000 สิ่งที่ ? (เพิ่มเติม? ช้าง?)
ilkkachu

31
ถ้าความยาวสตริงคงที่ (1M) และความยาวซับสตริง / หมายเลขคงที่ (3) ดังนั้นในทางเทคนิคทุกวิธีจะเป็นค่าคงที่…
Kevin

8
They did not give me the solution after I failed the interview, but they did tell me that the time complexity for the solution was constant of 1000 since all the possible outcomes are between: 000 --> 999 นี่น่าจะเป็นการทดสอบจริง เพื่อดูว่าคุณสามารถพิสูจน์ให้พวกเขาเห็นว่าทำไมมันจึงเป็นไปไม่ได้และแสดงให้พวกเขาเห็นถึงความซับซ้อนของเวลาขั้นต่ำที่ถูกต้อง
James

คำตอบ:


168

คุณปิดตัวลงเบา ๆ คุณอาจไม่ต้องการทำงานให้กองทุนเฮดจ์ฟันด์ที่นักวิจัยไม่เข้าใจอัลกอริธึมพื้นฐาน :-)

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

ถึงแม้ว่าจะเป็นกันเล็กน้อยO(n)ขั้นตอนวิธีการจะเป็นO(1)ขนาดใส่คงเป็นเช่นนั้นในทางเทคนิคที่พวกเขาอาจจะเป็นที่ถูกต้องที่นี่ อย่างไรก็ตามนั่นไม่ใช่วิธีที่ผู้คนใช้การวิเคราะห์ความซับซ้อน

สำหรับฉันแล้วคุณสามารถสร้างความประทับใจให้พวกเขาได้หลายวิธี

ก่อนอื่นโดยแจ้งให้พวกเขาทราบว่าไม่สามารถทำได้O(1)เว้นแต่คุณจะใช้เหตุผล "ผู้ต้องสงสัย" ที่ระบุไว้ด้านบน

ประการที่สองโดยแสดงทักษะชั้นยอดของคุณโดยการให้รหัส Pythonic เช่น:

inpStr = '123412345123456'

# O(1) array creation.
freq = [0] * 1000

# O(n) string processing.
for val in [int(inpStr[pos:pos+3]) for pos in range(len(inpStr) - 2)]:
    freq[val] += 1

# O(1) output of relevant array values.
print ([(num, freq[num]) for num in range(1000) if freq[num] > 1])

ผลลัพธ์นี้:

[(123, 3), (234, 3), (345, 2)]

แน่นอนว่าคุณสามารถปรับเปลี่ยนรูปแบบผลลัพธ์เป็นอะไรก็ได้ที่คุณต้องการ

และในที่สุดการบอกพวกเขาว่าแทบจะไม่มีปัญหากับการO(n)แก้ปัญหาเนื่องจากโค้ดด้านบนให้ผลลัพธ์สำหรับสตริงหนึ่งล้านหลักภายในไม่ถึงครึ่งวินาที ดูเหมือนว่าจะขยายได้ค่อนข้างเป็นเส้นตรงเช่นกันเนื่องจากสตริง 10,000,000 อักขระใช้เวลา 3.5 วินาทีและ 100,000,000 ตัวอักษรใช้เวลา 36 วินาที

และหากพวกเขาต้องการสิ่งที่ดีกว่านั้นก็มีวิธีการเรียงลำดับของสิ่งต่าง ๆ เหล่านี้ที่สามารถเร่งความเร็วได้อย่างมาก

ไม่ได้อยู่ในที่เดียวล่ามหลามแน่นอนเนื่องจาก GIL แต่คุณสามารถแยกสตริงเป็นสิ่งที่ต้องการ (ระบุทับซ้อนกันโดยvvจะต้องอนุญาตให้มีการประมวลผลที่เหมาะสมของพื้นที่เขตแดน):

    vv
123412  vv
    123451
        5123456

คุณสามารถทำฟาร์มเหล่านี้เพื่อแยกคนงานออกจากกันและรวมผลลัพธ์หลังจากนั้น

การแยกอินพุตและการรวมเอาท์พุทมีแนวโน้มที่จะล้นการประหยัดด้วยสตริงเล็ก ๆ (และอาจเป็นสตริงล้านหลัก) แต่สำหรับชุดข้อมูลที่มีขนาดใหญ่กว่ามากมันอาจสร้างความแตกต่างได้ มนต์ปกติของฉันของ"วัดไม่ต้องเดา"ใช้ที่นี่แน่นอน


มนต์นี้ยังนำไปใช้กับความเป็นไปได้อื่น ๆเช่นการเลี่ยงผ่าน Python โดยสิ้นเชิงและใช้ภาษาอื่นซึ่งอาจเร็วกว่า

ตัวอย่างเช่นรหัส C ต่อไปนี้ซึ่งทำงานบนฮาร์ดแวร์เดียวกันกับรหัส Python ก่อนหน้านี้จัดการตัวเลขหนึ่งร้อยล้านหลักใน 0.6 วินาทีโดยประมาณในเวลาเดียวกับรหัส Python ที่ประมวลผลหนึ่งล้าน กล่าวอีกนัยหนึ่งเร็วกว่ามาก :

#include <stdio.h>
#include <string.h>

int main(void) {
    static char inpStr[100000000+1];
    static int freq[1000];

    // Set up test data.

    memset(inpStr, '1', sizeof(inpStr));
    inpStr[sizeof(inpStr)-1] = '\0';

    // Need at least three digits to do anything useful.

    if (strlen(inpStr) <= 2) return 0;

    // Get initial feed from first two digits, process others.

    int val = (inpStr[0] - '0') * 10 + inpStr[1] - '0';
    char *inpPtr = &(inpStr[2]);
    while (*inpPtr != '\0') {
        // Remove hundreds, add next digit as units, adjust table.

        val = (val % 100) * 10 + *inpPtr++ - '0';
        freq[val]++;
    }

    // Output (relevant part of) table.

    for (int i = 0; i < 1000; ++i)
        if (freq[i] > 1)
            printf("%3d -> %d\n", i, freq[i]);

    return 0;
}

19
"ขนาดอินพุตคงที่" นี้ดูเหมือนจะเป็นเรื่องตลกที่ไม่ดีทั้งที่ผู้สัมภาษณ์หรือผู้ให้สัมภาษณ์ไม่ได้รับ ขั้นตอนวิธีการทุกคนจะกลายO(1)เป็นnได้รับการแก้ไขหรือกระโดด
Eric Duminil

5
หากพวกเขาต้องการดีกว่านั้นบางทีพวกเขาไม่ควรใช้ Python อย่างน้อยก็สำหรับอัลกอริทึมเฉพาะ
Sebastian Redl

3
@ezzzCash เพราะอาจมีการทับซ้อนที่จุดที่สตริงจะถูก "เสีย" เมื่อพยายามใช้วิธีการแบบขนาน เนื่องจากคุณกำลังมองหากลุ่ม 3 หลัก -2 จะช่วยให้การตรวจสอบการจัดกลุ่มคู่ขนานทั้งสองไม่ควรพลาดการจับคู่ที่ถูกต้อง
code_dredd

5
@ezzzCash มันไม่ได้ขาดความรู้การเขียนโปรแกรมแบบขนาน Nพิจารณาสตริงของความยาว ถ้าคุณทำลายมันออกเป็นสองส่วนในตำแหน่งที่N/2คุณยังคงต้องไปยังบัญชีสำหรับความจริงที่คุณอาจจะพลาดการแข่งขัน 3 หลักที่ถูกต้องที่ "ชายแดน" ในตอนท้ายของและจุดเริ่มต้นของstring1 string2ดังนั้นคุณต้องตรวจสอบการจับคู่ระหว่างstring1[N/2-2]และstring2[2](โดยใช้ดัชนี zero-based) ฯลฯ นั่นคือความคิด
code_dredd

1
ด้วยลำดับตัวเลขที่ยาวขึ้นจะมีบางสิ่งที่จะได้รับจากการปรับการแปลงให้เป็นจำนวนเต็มด้วยหน้าต่างแบบเลื่อนที่ช่วยให้คุณวางตัวเลขสูงสุดและเพิ่มตัวเลขใหม่ (Python เหนือหัวอาจจะฆ่าสิ่งนี้ดังนั้นมันจะใช้กับ C หรือการใช้งานระดับต่ำอื่น ๆ เท่านั้น) val -= 100 * (d[i]-'0');เพื่อวางตัวเลขนำหน้า val = 10*val + d[i+2]-'0'เพื่อสะสมตัวเลขที่มีนัยสำคัญน้อยที่สุดใหม่ (การแยกสตริงแบบปกติ -> จำนวนเต็ม) val % 100อาจไม่ใช่เรื่องที่น่ากลัว แต่ถ้า100เป็นค่าคงที่เวลารวบรวมดังนั้นจึงไม่ใช้การหาร HW จริง
Peter Cordes

78

ไม่สามารถใช้เวลาคงที่ได้ ต้องดูอย่างน้อย 1 ล้านหลักทั้งหมดอย่างน้อยหนึ่งครั้งดังนั้นความซับซ้อนของเวลาของ O (n) โดยที่ n = 1 ล้านในกรณีนี้

สำหรับโซลูชัน O (n) แบบง่าย ๆ ให้สร้างอาร์เรย์ขนาด 1000 ที่แสดงจำนวนการเกิดขึ้นของแต่ละตัวเลข 3 หลักที่เป็นไปได้ เลื่อนครั้งละ 1 หลักดัชนีแรก == 0 ดัชนีล่าสุด == 999997 และอาร์เรย์ที่เพิ่มขึ้น [หมายเลข 3 หลัก] เพื่อสร้างฮิสโตแกรม (นับจำนวนที่เกิดขึ้นสำหรับแต่ละตัวเลข 3 หลักที่เป็นไปได้) จากนั้นเอาท์พุทเนื้อหาของอาร์เรย์ด้วยจำนวน> 1


26
@ezzzCash - ใช่พจนานุกรมจะทำงานได้ แต่ก็ไม่จำเป็น ทุกคนรู้จัก "คีย์" ที่เป็นไปได้ล่วงหน้า จำกัด อยู่ในช่วง 0 ถึง 999 ความแตกต่างของค่าใช้จ่ายจะเป็นเวลาที่ต้องใช้การเข้าถึงคีย์โดยใช้สตริงอักขระ 3 ตัวเป็นคีย์เทียบกับเวลาที่ใช้ในการแปลง 3 สตริงหลักไปยังดัชนีจากนั้นใช้ดัชนีเพื่อเข้าถึงอาร์เรย์
rcgldr

4
หากคุณต้องการลูกเล่นตัวเลขคุณสามารถตัดสินใจที่จะไป BCD และเก็บตัวเลขสามหลักใน 12 บิต และถอดรหัสตัวเลข ASCII โดยปิดบัง 4 บิตต่ำ แต่x-'0'รูปแบบนั้นไม่ถูกต้องใน Python มันเป็น C-ism (โดยที่ตัวอักษรเป็นจำนวนเต็ม)
Yann Vernier

5
@ LorenPechtel: การค้นหาพจนานุกรมใน Python นั้นรวดเร็วมาก ได้รับการเข้าถึงอาเรย์นั้นเร็วขึ้นดังนั้นถ้าเราจัดการกับจำนวนเต็มตั้งแต่ต้นคุณก็จะถูกต้อง อย่างไรก็ตามในกรณีนี้เรามีความยาว 3 สายซึ่งเราต้องแปลงเป็นจำนวนเต็มก่อนหากเราต้องการใช้กับอาร์เรย์ ปรากฎว่าตรงกันข้ามกับสิ่งแรกที่คาดหวังการค้นหาพจนานุกรมเร็วกว่าการแปลงจำนวนเต็ม + การเข้าถึงอาร์เรย์ ในความเป็นจริงแล้ววิธีแก้ปัญหาอาเรย์นั้นช้ากว่า 50%
Aleksi Torhamo

2
ผมคิดว่าใครจะเถียงว่าถ้าจำนวนการป้อนข้อมูลที่มีอยู่เสมอว่า 1 ล้านคนตัวเลขกว่าอัลกอริทึมที่เป็น O (1) กับปัจจัยคง 1 ล้าน
tobias_k

2
@AleksiTorhamo - หากเป้าหมายคือการเปรียบเทียบความเร็วสัมพัทธ์ของการใช้งานสำหรับอัลกอริทึมฉันต้องการภาษาดั้งเดิมเช่น C หรือ C ++ เนื่องจาก Python ช้ากว่าอย่างเห็นได้ชัดและดูเหมือนว่าจะมีค่าใช้จ่ายเฉพาะของ Python เทียบกับภาษาอื่น
rcgldr

14

ล้านมีขนาดเล็กสำหรับคำตอบที่ฉันให้ด้านล่าง คาดหวังเพียงว่าคุณจะต้องสามารถเรียกใช้โซลูชันในการสัมภาษณ์โดยไม่หยุดชั่วคราวจากนั้นการทำงานต่อไปนี้ในเวลาน้อยกว่าสองวินาทีและให้ผลลัพธ์ที่ต้องการ:

from collections import Counter

def triple_counter(s):
    c = Counter(s[n-3: n] for n in range(3, len(s)))
    for tri, n in c.most_common():
        if n > 1:
            print('%s - %i times.' % (tri, n))
        else:
            break

if __name__ == '__main__':
    import random

    s = ''.join(random.choice('0123456789') for _ in range(1_000_000))
    triple_counter(s)

หวังว่าผู้สัมภาษณ์จะมองหาการใช้งานคอลเลกชันห้องสมุดมาตรฐานชั้นเรียน

เวอร์ชันการทำงานแบบขนาน

ฉันเขียนบทความในบล็อกนี้พร้อมคำอธิบายเพิ่มเติม


มันใช้งานได้ดีและดูเหมือนจะเป็นวิธีแก้ปัญหาที่เร็วที่สุดและไม่ใช่ numpy
Eric Duminil

3
@EricDuminil ฉันไม่คิดว่าคุณควรกังวลเกี่ยวกับการกำหนดเวลา fastet ที่นี่เมื่อโซลูชันส่วนใหญ่ที่ให้มาจะไม่ล่าช้าคุณมากนัก ดีกว่าที่จะแสดงให้เห็นว่าคุณมีความเข้าใจที่ดีของห้องสมุด Python มาตรฐานและสามารถเขียนรหัสที่สามารถบำรุงรักษาได้ในสถานการณ์สัมภาษณ์ที่ฉันคิดว่า (เว้นแต่ผู้สัมภาษณ์จะเน้นย้ำถึงความสำคัญของเวลาคุณควรถามเวลาจริงก่อนประเมินว่าอะไรจะเกิดขึ้นต่อไป)
Paddy3118

1
เราเห็นด้วย 100% แม้ว่าผมไม่แน่ใจว่าคำตอบใด ๆ O(1)ที่มีความเกี่ยวข้องที่ทุกคนถ้าสัมภาษณ์จริงๆคิดว่ามันเป็นไปได้ที่จะทำใน
Eric Duminil

1
หากผู้สัมภาษณ์ย้ำว่ามันเป็นช่วงเวลาที่สำคัญหลังจากนั้นหลังจากการทำโปรไฟล์เพื่อยืนยันว่านี่เป็นขีด จำกัด อาจถึงเวลาที่ต้องเขียนโมดูล C เพื่อแก้ไขปัญหาคอขวดนี้ ฉันมีสคริปต์ที่เห็นการปรับปรุง 84x เหนือรหัสหลามหลังจากเราเปลี่ยนไปใช้โมดูล ac
TemporalWolf

สวัสดี @TemporalWolf ฉันอ่านสิ่งที่คุณพูดแล้วคิดว่าโซลูชันอื่นที่เร็วกว่าและปรับขนาดได้อาจเปลี่ยนเป็นอัลกอริธึมแบบขนานเพื่อให้สามารถทำงานกับกระบวนการหลายอย่างในฟาร์มคำนวณ / คลาวด์ คุณต้องแบ่งสตริงออกเป็นส่วนต่างๆ ซ้อนทับ 3 ตัวอักษรสุดท้ายของแต่ละส่วนด้วยส่วนถัดไป จากนั้นแต่ละส่วนสามารถสแกนหาสิ่งที่เป็นอิสระได้สามข้อสรุปที่รวมกันและสามตัวอักษรสามตัวที่ส่วนท้ายของทั้งหมด แต่ส่วนสุดท้ายจะถูกลบออกเนื่องจากจะนับเป็นสองเท่า ฉันมีรหัสและอาจเปลี่ยนเป็นโพสต์บล็อก ...
Paddy3118

13

วิธีแก้ปัญหา O (n) อย่างง่ายจะนับจำนวน 3 หลักแต่ละตัว:

for nr in range(1000):
    cnt = text.count('%03d' % nr)
    if cnt > 1:
        print '%03d is found %d times' % (nr, cnt)

นี่จะค้นหาทั้งหมด 1 ล้านหลัก 1,000 ครั้ง

การข้ามตัวเลขเพียงครั้งเดียว:

counts = [0] * 1000
for idx in range(len(text)-2):
    counts[int(text[idx:idx+3])] += 1

for nr, cnt in enumerate(counts):
    if cnt > 1:
        print '%03d is found %d times' % (nr, cnt)

countแสดงให้เห็นว่าระยะเวลาการทำซ้ำเพียงครั้งเดียวในช่วงดัชนีรวดเร็วเป็นสองเท่าโดยใช้


37
มีส่วนลดวันศุกร์สีดำtext.count()หรือไม่?
Eric Duminil

3
@EricDuminil คุณมีจุดดี แต่เนื่องจากtext.countทำในภาษาที่รวบรวมด้วยความเร็วสูง (เช่น C) ซึ่งตรงข้ามกับการแปลลูประดับช้าของงูหลามใช่มีส่วนลด
John1024

มันไม่มีประสิทธิภาพมากที่จะนับแต่ละหมายเลขแยกกัน แต่มันเป็นเวลาที่แน่นอนดังนั้นยังคงเป็น O (n)
Loren Pechtel

11
ตัวเลือกที่คุณเสนอที่ใช้countไม่ถูกต้องเนื่องจากจะไม่นับรูปแบบที่ทับซ้อนกัน โปรดทราบว่าเมื่อเราจะคาดหวังว่ามันจะ'111'.count('11') == 1 2
Cireo

2
นอกจากนี้ " O(n)วิธีแก้ปัญหาอย่างง่าย" ของคุณนั้นแท้จริงแล้วO(10**d * n)ด้วยdจำนวนตัวเลขที่ค้นหาและnความยาวทั้งหมดของสตริง ประการที่สองคือO(n)เวลาและO(10**d + n)พื้นที่
Eric Duminil

10

นี่คือการดำเนินการ NumPy ของอัลกอริทึม "ฉันทามติ" O (n): เดินผ่านทริปเปิลและถังขยะตามที่คุณไป การทำ binning ทำได้โดยเมื่อพบว่า "385" โดยเพิ่มหนึ่งไปยัง bin [3, 8, 5] ซึ่งเป็นการดำเนินการ O (1) ช่องเก็บของถูกจัดเรียงเป็น10x10x10ลูกบาศก์ เนื่องจากการ binning นั้นถูกปรับเวกเตอร์อย่างสมบูรณ์จึงไม่มีการวนซ้ำในโค้ด

def setup_data(n):
    import random
    digits = "0123456789"
    return dict(text = ''.join(random.choice(digits) for i in range(n)))

def f_np(text):
    # Get the data into NumPy
    import numpy as np
    a = np.frombuffer(bytes(text, 'utf8'), dtype=np.uint8) - ord('0')
    # Rolling triplets
    a3 = np.lib.stride_tricks.as_strided(a, (3, a.size-2), 2*a.strides)

    bins = np.zeros((10, 10, 10), dtype=int)
    # Next line performs O(n) binning
    np.add.at(bins, tuple(a3), 1)
    # Filtering is left as an exercise
    return bins.ravel()

def f_py(text):
    counts = [0] * 1000
    for idx in range(len(text)-2):
        counts[int(text[idx:idx+3])] += 1
    return counts

import numpy as np
import types
from timeit import timeit
for n in (10, 1000, 1000000):
    data = setup_data(n)
    ref = f_np(**data)
    print(f'n = {n}')
    for name, func in list(globals().items()):
        if not name.startswith('f_') or not isinstance(func, types.FunctionType):
            continue
        try:
            assert np.all(ref == func(**data))
            print("{:16s}{:16.8f} ms".format(name[2:], timeit(
                'f(**data)', globals={'f':func, 'data':data}, number=10)*100))
        except:
            print("{:16s} apparently crashed".format(name[2:]))

น่าแปลกใจที่ NumPy นั้นเร็วกว่าโซลูชัน Python ของ @ Daniel ในชุดข้อมูลขนาดใหญ่ ตัวอย่างผลลัพธ์:

# n = 10
# np                    0.03481400 ms
# py                    0.00669330 ms
# n = 1000
# np                    0.11215360 ms
# py                    0.34836530 ms
# n = 1000000
# np                   82.46765980 ms
# py                  360.51235450 ms

น่าจะเร็วกว่าอย่างมากในการทำให้แบนของสตริงหลักแทนที่จะมีถังขยะซ้อนกันเว้นแต่ว่า NumPy จะใช้มันเป็นเมทริกซ์ 3 มิติที่มีการจัดทำดัชนีที่มีประสิทธิภาพ @ Daniel's เวอร์ชันไหนที่คุณต่อต้าน อันไหนที่เรียกใช้การค้นหาสตริงสำหรับจำนวนเต็มแต่ละอันหรืออันที่มีฮิสโตแกรม?
Peter Cordes

2
@ PeterCordes ฉันสงสัยมัน ndarrays, ประเภท numpy หลักคือทั้งหมดที่เกี่ยวกับการจัดเก็บที่มีประสิทธิภาพการจัดการและการจัดทำดัชนีของอาร์เรย์หลายมิติของตัวเลข บางครั้งคุณสามารถโกนหนวดสักสองสาม% ด้วยการทำให้แบน แต่ในกรณีนี้การทำ 100 x [0] + 10 x [1] + x [2] ด้วยมือจะไม่ทำให้คุณได้รับประโยชน์มากนัก ฉันใช้ @Daniel บอกว่าเร็วขึ้นคุณสามารถตรวจสอบรหัสมาตรฐานได้ด้วยตัวเอง
Paul Panzer

ฉันไม่รู้จัก NumPy จริงๆ (หรือ Python โดยทั่วไปส่วนใหญ่ฉันทำ C และการปรับประสิทธิภาพแอสเซมบลีสำหรับ x86) แต่ฉันคิดว่าคุณมีอาร์เรย์ 3D เดียวใช่ไหม ฉันกำลังคิดจากข้อความภาษาอังกฤษของคุณ (ซึ่งเห็นได้ชัดว่าฉันไม่ได้อ่านอย่างละเอียด) ว่าคุณมีวัตถุหลามที่ซ้อนกันจริงและได้จัดทำดัชนีแยกต่างหาก แต่นั่นไม่ใช่กรณีนี้ดังนั้น nvm ความคิดเห็นแรกของฉัน
Peter Cordes

ฉันคิดว่า Python เวอร์ชั่นบริสุทธิ์ที่คุณใช้นั้นเป็นการนำฮิสโตแกรมมาใช้ในแบบเดียวกันกับที่ใช้กับคำตอบที่ได้รับการโหวตมากขึ้น แต่ถ้าหากวิธีการเขียนแบบต่างๆใน Python มีผลต่อความเร็วมาก
Peter Cordes

3

ฉันจะแก้ปัญหาดังต่อไปนี้:

def find_numbers(str_num):
    final_dict = {}
    buffer = {}
    for idx in range(len(str_num) - 3):
        num = int(str_num[idx:idx + 3])
        if num not in buffer:
            buffer[num] = 0
        buffer[num] += 1
        if buffer[num] > 1:
            final_dict[num] = buffer[num]
    return final_dict

นำไปใช้กับสตริงตัวอย่างของคุณอัตราผลตอบแทนนี้:

>>> find_numbers("123412345123456")
{345: 2, 234: 3, 123: 3}

วิธีการแก้ปัญหานี้ทำงานใน O (n) สำหรับ n เป็นความยาวของสตริงที่ให้และฉันเดาที่ดีที่สุดที่คุณจะได้รับ


Counterคุณก็สามารถใช้ คุณไม่ต้องการfinal_dictและคุณไม่จำเป็นต้องอัปเดตในแต่ละรอบซ้ำ
Eric Duminil

2

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

รหัสจะมีลักษณะดังนี้:

def calc_repeating_digits(number):

    hash = {}

    for i in range(len(str(number))-2):

        current_three_digits = number[i:i+3]
        if current_three_digits in hash.keys():
            hash[current_three_digits] += 1

        else:
            hash[current_three_digits] = 1

    return hash

คุณสามารถกรองคีย์ที่มีมูลค่ารายการมากกว่า 1 ได้


2

ดังกล่าวในคำตอบอื่นคุณไม่สามารถทำอัลกอริทึมนี้ในเวลาคงที่เนื่องจากคุณต้องดูอย่างน้อย n หลัก เวลาเชิงเส้นเป็นวิธีที่เร็วที่สุดที่คุณจะได้รับ

อย่างไรก็ตามขั้นตอนวิธีที่สามารถทำได้ใน O (1) พื้นที่ คุณจะต้องเก็บจำนวนของตัวเลข 3 ตัวเท่านั้นดังนั้นคุณต้องมีอาร์เรย์ 1,000 รายการ จากนั้นคุณสามารถสตรีมตัวเลขใน

ฉันเดาว่าทั้งผู้สัมภาษณ์สะกดผิดเมื่อพวกเขาให้ทางออกหรือคุณคิดว่า "เวลาคงที่" เมื่อพวกเขาพูดว่า "พื้นที่คงที่"


ตามที่คนอื่น ๆ ได้ชี้ให้เห็นวิธีการฮิสโตแกรมเป็นO(10**d)พื้นที่เพิ่มเติมซึ่งdเป็นจำนวนทศนิยมที่คุณต้องการ
Peter Cordes

1
วิธีการแบบพจนานุกรมจะเป็น O (ขั้นต่ำ (10 ^ d, n)) สำหรับ n หลัก ตัวอย่างเช่นถ้าคุณมี n = 10 ^ 9 ตัวและต้องการค้นหาลำดับ 15 หลักที่หายากที่เกิดขึ้นมากกว่าหนึ่งครั้ง
gnasher729

1

นี่คือคำตอบของฉัน:

from timeit import timeit
from collections import Counter
import types
import random

def setup_data(n):
    digits = "0123456789"
    return dict(text = ''.join(random.choice(digits) for i in range(n)))


def f_counter(text):
    c = Counter()
    for i in range(len(text)-2):
        ss = text[i:i+3]
        c.update([ss])
    return (i for i in c.items() if i[1] > 1)

def f_dict(text):
    d = {}
    for i in range(len(text)-2):
        ss = text[i:i+3]
        if ss not in d:
            d[ss] = 0
        d[ss] += 1
    return ((i, d[i]) for i in d if d[i] > 1)

def f_array(text):
    a = [[[0 for _ in range(10)] for _ in range(10)] for _ in range(10)]
    for n in range(len(text)-2):
        i, j, k = (int(ss) for ss in text[n:n+3])
        a[i][j][k] += 1
    for i, b in enumerate(a):
        for j, c in enumerate(b):
            for k, d in enumerate(c):
                if d > 1: yield (f'{i}{j}{k}', d)


for n in (1E1, 1E3, 1E6):
    n = int(n)
    data = setup_data(n)
    print(f'n = {n}')
    results = {}
    for name, func in list(globals().items()):
        if not name.startswith('f_') or not isinstance(func, types.FunctionType):
            continue
        print("{:16s}{:16.8f} ms".format(name[2:], timeit(
            'results[name] = f(**data)', globals={'f':func, 'data':data, 'results':results, 'name':name}, number=10)*100))
    for r in results:
        print('{:10}: {}'.format(r, sorted(list(results[r]))[:5]))

วิธีการค้นหาแบบอาร์เรย์นั้นรวดเร็วมาก (เร็วกว่าวิธีการแบบเลขฐาน @ paul-panzer!) แน่นอนมันโกงเพราะมันยังไม่เสร็จสิ้นหลังจาก technicailly เพราะมันกลับมากำเนิด นอกจากนี้ยังไม่ต้องตรวจสอบซ้ำทุกครั้งหากมีค่าอยู่แล้วซึ่งน่าจะช่วยได้มาก

n = 10
counter               0.10595780 ms
dict                  0.01070654 ms
array                 0.00135370 ms
f_counter : []
f_dict    : []
f_array   : []
n = 1000
counter               2.89462101 ms
dict                  0.40434612 ms
array                 0.00073838 ms
f_counter : [('008', 2), ('009', 3), ('010', 2), ('016', 2), ('017', 2)]
f_dict    : [('008', 2), ('009', 3), ('010', 2), ('016', 2), ('017', 2)]
f_array   : [('008', 2), ('009', 3), ('010', 2), ('016', 2), ('017', 2)]
n = 1000000
counter            2849.00500992 ms
dict                438.44007806 ms
array                 0.00135370 ms
f_counter : [('000', 1058), ('001', 943), ('002', 1030), ('003', 982), ('004', 1042)]
f_dict    : [('000', 1058), ('001', 943), ('002', 1030), ('003', 982), ('004', 1042)]
f_array   : [('000', 1058), ('001', 943), ('002', 1030), ('003', 982), ('004', 1042)]

1
ดังนั้นคุณเปรียบเทียบอะไรกันแน่ คุณไม่ควรส่งคืนรายการแทนที่จะเป็นเครื่องกำเนิดไฟฟ้าที่ไม่ได้ใช้หรือ
Eric Duminil

Countersไม่ได้ใช้วิธีนั้น ใช้อย่างถูกต้องมันจะกลายเป็นตัวเลือกที่เร็วที่สุดสำหรับตัวอย่างของคุณ ถ้าคุณใช้timeitกับรายการ insted ของเครื่องกำเนิดไฟฟ้า, วิธีการของคุณจะกลายเป็นช้ากว่าหรือCounter dictดูที่นี่
Eric Duminil

สุดท้ายคุณf_arrayอาจจะเร็วขึ้นถ้าคุณแรกแปลงถ่านทุกคนที่จะเป็น int: และการใช้งานแล้วints = [int(c) for c in text] i, j, k = ints[n:n+3]
Eric Duminil


1

นี่คือทางออกของฉัน:

from collections import defaultdict
string = "103264685134845354863"
d = defaultdict(int)
for elt in range(len(string)-2):
    d[string[elt:elt+3]] += 1
d = {key: d[key] for key in d.keys() if d[key] > 1}

ด้วยความคิดสร้างสรรค์เล็กน้อยในการวนซ้ำ (และรายการค้นหาเพิ่มเติมด้วย True / False / None เป็นต้น) คุณควรจะสามารถกำจัดบรรทัดสุดท้ายได้เนื่องจากคุณต้องการสร้างคีย์ใน dict ที่เราเคยไปครั้งเดียวจนถึงจุดนั้น . หวังว่าจะช่วย :)


ดูคำตอบของ pho7 และแสดงความคิดเห็น ลองคิดดูว่าทำไมมันถึงไม่ได้คะแนนมากมาย
greybeard

0

- การบอกจากมุมมองของ C. - คุณสามารถได้ผลลัพธ์แบบ 3 มิติ [10] [10] [10] - ไปจากตำแหน่งที่ 0 ถึงตำแหน่งที่ n-4 โดยที่ n เป็นขนาดของอาร์เรย์สตริง - ในแต่ละสถานที่ตรวจสอบปัจจุบันถัดไปและถัดไปของถัดไป - เพิ่ม cntr เป็น resutls [ปัจจุบัน] [ถัดไป] [ถัดไปของถัดไป] ++; พิมพ์ค่าของ

results[1][2][3]
results[2][3][4]
results[3][4][5]
results[4][5][6]
results[5][6][7]
results[6][7][8]
results[7][8][9]

- เป็นเวลา O (n) ไม่มีการเปรียบเทียบที่เกี่ยวข้อง - คุณสามารถเรียกใช้บางสิ่งที่ขนานกันได้ที่นี่โดยการแบ่งอาร์เรย์และคำนวณการแข่งขันรอบพาร์ติชัน


-1
inputStr = '123456123138276237284287434628736482376487234682734682736487263482736487236482634'

count = {}
for i in range(len(inputStr) - 2):
    subNum = int(inputStr[i:i+3])
    if subNum not in count:
        count[subNum] = 1
    else:
        count[subNum] += 1

print count

ขอบคุณสำหรับคำตอบของคุณ แต่มันคล้ายกับอัลกอริทึมที่ได้รับจาก @abhishek arora 5-6 วันที่ผ่านมา อีกทั้งคำถามดั้งเดิมไม่ได้ถามถึงอัลกอริทึม แต่เป็นคำถามที่แตกต่างกัน (ซึ่งได้รับคำตอบแล้วหลายครั้ง)
it.david
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.