ตรวจจับวันที่ในปฏิทินที่ต่อเนื่องกัน
ใช้การคำนวณวันที่และหมายเลขแถวเพื่อค้นหาช่วงวันที่ที่ไม่ขาดตอน
ตรวจจับวันที่ในปฏิทินที่ต่อเนื่องกัน เป็นบทเรียน Coding Interview Prep ฟรีบน CoddyKit นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Coding Interview Prep และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Coding Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน
โจทย์การสัมภาษณ์
ผู้สัมภาษณ์ชอบคำถามเกี่ยวกับช่วงต่อเนื่อง เพราะคำถามเหล่านี้แสดงให้เห็นว่าคุณเข้าใจ ฟังก์ชันหน้าต่างและการคำนวณวันที่อย่างแท้จริงหรือไม่ โจทย์ทั่วไปคือ "จากตารางวันที่ผู้ใช้เข้าสู่ระบบ ให้หาช่วงวันที่ในปฏิทินที่ต่อเนื่องกันโดยไม่มีวันขาด"
สัญชาตญาณแรกอาจเป็นการเชื่อมตารางกับตัวเองเพื่อเปรียบเทียบแต่ละแถวกับแถวถัดไป แต่วิธีนี้ทำงานได้แย่ลงอย่างมากเมื่อตารางมีขนาดใหญ่และเขียนได้ไม่สะดวก คำตอบแบบมืออาชีพใช้เทคนิค ช่องว่างและกลุ่มช่วงต่อเนื่อง ในบทเรียนนี้คุณจะเรียนรู้วิธีตรวจหาวันที่ต่อเนื่องอย่างเป็นระเบียบด้วยหมายเลขแถวและการลบวันที่
ข้อมูลตัวอย่าง
ตลอดบทเรียนนี้ เราใช้ตาราง logins ที่มีหนึ่งแถวต่อผู้ใช้ต่อวันที่ผู้ใช้นั้นใช้งานอยู่ โดยถือว่ากำจัดรายการซ้ำไว้แล้ว (หนึ่งครั้งที่เข้าสู่ระบบต่อหนึ่งวันในปฏิทิน)
user_id— ผู้ที่เข้าสู่ระบบlogin_date— ค่าประเภท DATE
สำหรับผู้ใช้ 1 วันที่คือ 1, 2, 3 มกราคม จากนั้นมีช่องว่าง แล้วจึงเป็น 6, 7 มกราคม เราคาดว่าจะได้สองช่วงต่อเนื่อง: ช่วงยาว 3 วันและช่วงยาว 2 วัน
SELECT * FROM logins ORDER BY user_id, login_date;
-- user_id | login_date
-- 1 | 2024-01-01
-- 1 | 2024-01-02
-- 1 | 2024-01-03
-- 1 | 2024-01-06
-- 1 | 2024-01-07แนวคิดหลัก
นี่คือเคล็ดลับที่ช่วยไขปัญหาเรื่องวันที่ต่อเนื่องทุกแบบ หากคุณเรียงแถวตามวันที่และกำหนด หมายเลขแถว ให้แต่ละแถวตามลำดับ สำหรับช่วงวันที่ต่อเนื่องใด ๆ ผลต่างระหว่างวันที่กับหมายเลขแถวจะคงที่
เพราะทั้งวันที่และหมายเลขแถวเพิ่มขึ้นทีละ 1 ในแต่ละวันที่ต่อเนื่อง ผลต่างจึงไม่เปลี่ยนแปลง เมื่อเกิดช่องว่าง วันที่จะกระโดดไปข้างหน้า แต่หมายเลขแถวไม่เปลี่ยนตาม ทำให้ค่าคงที่สิ้นสุดลงและเริ่มกลุ่มใหม่
มองเห็นผลต่าง
ลองไล่ดูด้วยมือสำหรับผู้ใช้ 1 โดย ROW_NUMBER จะนับเป็น 1, 2, 3, 4, 5 ให้ลบหมายเลขแถวในฐานะจำนวนวันออกจากวันที่ แล้วสังเกตผลลัพธ์
- 1 มกราคม − 1 = 31 ธันวาคม
- 2 มกราคม − 2 = 31 ธันวาคม
- 3 มกราคม − 3 = 31 ธันวาคม
- 6 มกราคม − 4 = 2 มกราคม
- 7 มกราคม − 5 = 2 มกราคม
สามแถวแรกมีค่า 31 ธันวาคมร่วมกัน ส่วนสองแถวสุดท้ายมีค่า 2 มกราคมร่วมกัน ค่าอ้างอิงร่วมนี้คือ คีย์กลุ่ม ของเรา
การเพิ่ม ROW_NUMBER
ขั้นตอนแรกที่ลงมือทำคือการเพิ่มหมายเลขแถว โดยแบ่งกลุ่มตามผู้ใช้ เพื่อไม่ให้ช่วงต่อเนื่องข้ามขอบเขตของผู้ใช้ และเรียงลำดับตามวันที่
PARTITION BY user_id จะเริ่มตัวนับใหม่สำหรับผู้ใช้แต่ละราย ส่วน ORDER BY login_date รับประกันว่าลำดับจะเป็นไปตามปฏิทิน
SELECT
user_id,
login_date,
ROW_NUMBER() OVER (
PARTITION BY user_id
ORDER BY login_date
) AS rn
FROM logins;การคำนวณค่าอ้างอิงของกลุ่ม
ตอนนี้ให้ลบจำนวนวันตามค่า rn ออกจาก login_date ใน PostgreSQL คุณสามารถลบจำนวนเต็มของวันออกจากวันที่ได้โดยตรง ผลลัพธ์คือค่าอ้างอิงคงที่ที่ใช้ระบุแต่ละกลุ่มช่วงต่อเนื่อง
โปรดสังเกตว่าเราไม่สามารถอ้างอิงชื่อแทน rn ใน SELECT เดียวกับที่กำหนดชื่อนั้นได้ ดังนั้นจึงต้องห่อคำสั่งก่อนหน้าด้วย CTE หรือแบบสอบถามย่อยก่อน
WITH numbered AS (
SELECT
user_id,
login_date,
ROW_NUMBER() OVER (
PARTITION BY user_id ORDER BY login_date
) AS rn
FROM logins
)
SELECT
user_id,
login_date,
login_date - rn AS grp
FROM numbered;การจัดกลุ่มช่วงต่อเนื่อง
เมื่อมีค่าอ้างอิงแล้ว ทุกช่วงต่อเนื่องจะมีค่า grp เดียวกัน ให้จัดกลุ่มตาม user_id และ grp จากนั้นรวมค่าเพื่อหาวันเริ่มต้น วันสิ้นสุด และความยาวของแต่ละช่วง
MIN(login_date)— วันแรกของช่วงต่อเนื่องMAX(login_date)— วันสุดท้ายของช่วงต่อเนื่องCOUNT(*)— จำนวนวันในช่วงต่อเนื่อง
WITH numbered AS (
SELECT user_id, login_date,
ROW_NUMBER() OVER (
PARTITION BY user_id ORDER BY login_date
) AS rn
FROM logins
)
SELECT
user_id,
MIN(login_date) AS streak_start,
MAX(login_date) AS streak_end,
COUNT(*) AS streak_len
FROM numbered
GROUP BY user_id, login_date - rn
ORDER BY user_id, streak_start;ความแตกต่างระหว่างรูปแบบภาษา
ไวยากรณ์สำหรับการคำนวณวันที่แตกต่างกันไป ควรกล่าวถึงเรื่องนี้ในการสัมภาษณ์เพื่อแสดงให้เห็นว่าคุณมีความรู้ครอบคลุม
- PostgreSQL:
login_date - rn(ลบจำนวนเต็มของวันออกจากวันที่) - MySQL:
DATE_SUB(login_date, INTERVAL rn DAY) - SQL Server:
DATEADD(day, -rn, login_date)
ตรรกะเหมือนกันทั้งหมด มีเพียงชื่อฟังก์ชันที่เปลี่ยนไป แบบจำลองทางความคิดที่ใช้ได้กับทุกระบบคือ "เลื่อนวันที่แต่ละรายการย้อนกลับตามตำแหน่งของมัน เพื่อให้ช่วงต่อเนื่องที่สมบูรณ์รวมกันเป็นค่าคงที่เดียว"
-- SQL Server version of the anchor
DATEADD(day, -1 * rn, login_date) AS grpเหตุใดจึงไม่เชื่อมตารางกับตัวเอง
ผู้สัมภาษณ์อาจถามว่าทำไมคุณจึงหลีกเลี่ยงการเชื่อมตารางกับตัวเอง เช่น l1.login_date = l2.login_date + 1 เหตุผลที่ควรตอบมีดังนี้:
- การเชื่อมตารางกับตัวเองทดสอบได้เพียง ความติดกัน ไม่ใช่ช่วงต่อเนื่องทั้งหมด การประกอบช่วงต่อเนื่องให้ครบยังต้องจัดกลุ่มอยู่ดี
- อาจทำให้จำนวนแถวเพิ่มเป็นทวีคูณ และมีความซับซ้อน O(n²) หากไม่มีดัชนีที่ดี
- วิธีใช้หมายเลขแถวเป็นการประมวลผลแบบเรียงลำดับรอบเดียว จึงรองรับการขยายได้ดีกว่ามาก
ฟังก์ชันหน้าต่างคือคำตอบสมัยใหม่ที่คาดหวังสำหรับปัญหาเหล่านี้
การป้องกันรายการซ้ำ
เทคนิคทั้งหมดนี้ถือว่ามี หนึ่งแถวต่อผู้ใช้ต่อวัน หากแหล่งข้อมูลมีการเข้าสู่ระบบหลายครั้งในวันเดียวกัน สองแถวที่มีวันที่เดียวกันจะได้รับหมายเลขแถวต่างกัน ซึ่งทำให้ค่าอ้างอิงผิดพลาด
ป้องกันปัญหาด้วยการกำจัดรายการซ้ำก่อน โดยแปลงค่าประทับเวลาเป็นวันที่แล้วใช้ DISTINCT หรือใช้ DENSE_RANK กับวันที่แทน ROW_NUMBER เพื่อให้วันที่เท่ากันมีหมายเลขเดียวกัน
WITH days AS (
SELECT DISTINCT user_id, login_ts::date AS login_date
FROM raw_logins
)
SELECT * FROM days;คำตอบฉบับสมบูรณ์
เมื่อนำทุกส่วนมารวมกัน จะได้คำตอบที่กระชับและพร้อมใช้ในการสัมภาษณ์ ซึ่งแสดงช่วงวันที่ต่อเนื่องแต่ละช่วงพร้อมวันเริ่มต้น วันสิ้นสุด และความยาว
โครงร่างเดียวกันนี้ — กำจัดรายการซ้ำ กำหนดหมายเลข ลบค่า และจัดกลุ่ม — แก้ปัญหาเกี่ยวกับ "ความต่อเนื่อง" ได้เกือบทุกแบบที่คุณอาจได้รับ
WITH days AS (
SELECT DISTINCT user_id, login_ts::date AS login_date
FROM raw_logins
),
numbered AS (
SELECT user_id, login_date,
ROW_NUMBER() OVER (
PARTITION BY user_id ORDER BY login_date
) AS rn
FROM days
)
SELECT user_id,
MIN(login_date) AS streak_start,
MAX(login_date) AS streak_end,
COUNT(*) AS streak_len
FROM numbered
GROUP BY user_id, login_date - rn
ORDER BY user_id, streak_start;ตรวจสอบความเข้าใจ
ทดสอบความเข้าใจเคล็ดลับหลักของคุณ
สรุป
คุณได้เรียนรู้รูปแบบพื้นฐานสำหรับวันที่ต่อเนื่องแล้ว:
- กำจัดรายการซ้ำให้เหลือหนึ่งแถวต่อผู้ใช้ต่อวัน
- ROW_NUMBER เรียงตามวันที่และแบ่งกลุ่มตามผู้ใช้
- ลบหมายเลขแถวออกจากวันที่เพื่อให้ได้ค่าอ้างอิงคงที่สำหรับแต่ละช่วงต่อเนื่อง
- GROUP BY ค่าอ้างอิงแล้วรวมค่าเพื่อหาวันเริ่มต้น วันสิ้นสุด และความยาว
โครงร่างช่องว่างและกลุ่มช่วงต่อเนื่องนี้รองรับการประมวลผลรอบเดียวและมีประสิทธิภาพดีกว่าการเชื่อมตารางกับตัวเอง บทถัดไปคุณจะใช้โครงร่างนี้เพื่อคำนวณช่วงต่อเนื่องที่ยาวที่สุดสำหรับผู้ใช้แต่ละราย
คำถามที่พบบ่อย
บทเรียน “ตรวจจับวันที่ในปฏิทินที่ต่อเนื่องกัน” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “ตรวจจับวันที่ในปฏิทินที่ต่อเนื่องกัน” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Coding Interview Prep ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Coding Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “ตรวจจับวันที่ในปฏิทินที่ต่อเนื่องกัน”
ใช้การคำนวณวันที่และหมายเลขแถวเพื่อค้นหาช่วงวันที่ที่ไม่ขาดตอน คุณปฏิบัติ Coding Interview Prep ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Coding Interview Prep หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน Coding Interview Prep บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน
บทเรียน “ตรวจจับวันที่ในปฏิทินที่ต่อเนื่องกัน” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน Coding Interview Prep นี้ได้ไหม
ได้ บทเรียน Coding Interview Prep ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- ตรวจจับวันที่ในปฏิทินที่ต่อเนื่องกัน
- ช่วงต่อเนื่องที่ยาวที่สุดของผู้ใช้แต่ละคน
- แถวต่อเนื่อง N แถวที่ตรงตามเงื่อนไข
- ช่วงต่อเนื่องที่ยังดำเนินอยู่จนถึงวันนี้