4 กุมภาพันธ์ 2565 อาร์เรย์/โครงสร้างข้อมูล/การเขียนโปรแกรมแบบไดนามิก
ลิงค์ปัญหา:พาร์ทิชันเท่ากับผลรวมย่อย
เราได้รับอาร์เรย์ 'ARR' ที่มีจำนวนเต็มบวก N เราต้องค้นหาว่าเราสามารถแบ่งอาร์เรย์ออกเป็นสองส่วนย่อยเพื่อให้ผลรวมขององค์ประกอบของแต่ละส่วนย่อยเท่ากันหรือไม่
หากแบ่งพาร์ติชันได้ ให้คืนค่าจริงหรือคืนค่าเท็จ
ตัวอย่าง:
ในตัวอย่างนี้ เมื่อเราสามารถแบ่งพาร์ติชันได้ เราจึงคืนค่าจริง
ข้อจำกัดความรับผิดชอบ:อย่ากระโดดไปที่วิธีแก้ปัญหาโดยตรง ลองใช้ตัวคุณเองก่อน
ข้อกำหนดเบื้องต้น: ผลรวมย่อยเท่ากับเป้าหมาย,การเรียกซ้ำในผลที่ตามมา
สารละลาย :
คำถามนี้เป็นการแก้ไขเล็กน้อยจากปัญหาที่กล่าวถึงในผลรวมย่อยเท่ากับเป้าหมาย. เราจำเป็นต้องแบ่งอาร์เรย์ (เช่น S) ออกเป็นสองส่วนย่อย (เช่น S1 และ S2) ตามคำถาม:
- ผลรวมขององค์ประกอบของ S1 + ผลรวมขององค์ประกอบของ S2 = ผลรวมขององค์ประกอบของ S
- ผลรวมขององค์ประกอบ S1 = ผลรวมขององค์ประกอบ S2
เงื่อนไขทั้งสองนี้หมายความว่า S1 = S2 = (S/2)
ตอนนี้,
- ถ้า S (ผลรวมของอิลิเมนต์ของอินพุตอาร์เรย์) เป็นเลขคี่ ไม่มีทางที่เราจะแบ่งมันออกเป็นสองซีกเท่าๆ กัน ดังนั้นเราจึงคืนค่าเท็จได้
- ถ้า S เป็นเลขคู่ เราต้องหาลำดับย่อยในอาร์เรย์อินพุตที่มีผลรวมเท่ากับ S/2 เพราะหากเราพบลำดับย่อยหนึ่งลำดับที่มีผลรวม S/2 องค์ประกอบที่เหลือจะเป็น S/2 โดยอัตโนมัติ ดังนั้นเราจึงแบ่งอาร์เรย์ที่กำหนดได้ ดังนั้นเราจึงคืนค่าจริง
บันทึก:ผู้อ่านควรดูวิดีโอนี้ “การเรียกซ้ำในผลที่ตามมา” เพื่อทำความเข้าใจวิธีที่เราสร้างลำดับที่ตามมาโดยใช้การเรียกซ้ำ
จากที่นี่เราจะพยายามหาลำดับที่ตามมาในอาร์เรย์ที่มี target = S/2 ตามที่กล่าวไว้ในผลรวมย่อยเท่ากับเป้าหมาย
ขั้นตอนในการสร้างโซลูชันแบบเรียกซ้ำ:
ก่อนอื่นเราจะสร้างวิธีแก้ปัญหาแบบเรียกซ้ำโดยจุดสามจุดที่กล่าวถึงในบทนำการเขียนโปรแกรมแบบไดนามิก.
ขั้นตอนที่ 1:แสดงปัญหาในรูปของดัชนี
อาร์เรย์จะมีดัชนี แต่มี "เป้าหมาย" อีกหนึ่งพารามิเตอร์ เราได้รับโจทย์เริ่มต้นเพื่อค้นหาว่ามีลำดับย่อยที่มีผลรวมเท่ากับเป้าหมายในอาร์เรย์ทั้งหมดหรือไม่
ดังนั้น เราสามารถพูดได้ว่าในขั้นต้น เราต้องหา (n-1, เป้าหมาย) ซึ่งหมายความว่าเราต้องค้นหาว่ามีลำดับย่อยในอาร์เรย์ตั้งแต่ดัชนี 0 ถึง n-1 ซึ่งมีผลรวมเท่ากับเป้าหมายหรือไม่ ในทำนองเดียวกัน เราสามารถทำให้เป็นภาพรวมสำหรับดัชนีใดๆ ได้ดังนี้:
กรณีฐาน:
- ถ้าเป้าหมาย == 0 แสดงว่าเราพบผลลัพธ์ที่ตามมาจากขั้นตอนก่อนหน้าแล้ว เราจึงคืนค่าจริงได้
- ถ้า ind==0 แสดงว่าเราอยู่ที่องค์ประกอบแรก ดังนั้นเราต้องส่งคืน arr[ind]==target หากองค์ประกอบเท่ากับเป้าหมาย เราจะคืนค่าจริงเป็นเท็จ
ขั้นตอนที่ 2:ลองใช้ตัวเลือกที่เป็นไปได้ทั้งหมดในดัชนีที่กำหนด
เราจำเป็นต้องสร้างผลที่ตามมาทั้งหมด เราจะใช้เทคนิคการเลือก/ไม่เลือกตามที่กล่าวไว้ในวิดีโอนี้”การเรียกซ้ำในผลที่ตามมา".
เรามีสองทางเลือก:
- ยกเว้นองค์ประกอบปัจจุบันในลำดับต่อมา:ก่อนอื่น เราพยายามหาลำดับที่ตามมาโดยไม่พิจารณาองค์ประกอบดัชนีปัจจุบัน สำหรับสิ่งนี้ เราจะเรียกซ้ำไปยัง f(ind-1,target)
- รวมองค์ประกอบปัจจุบันในผลที่ตามมา:เราจะพยายามหาลำดับที่ตามมาโดยพิจารณาดัชนีปัจจุบันเป็นองค์ประกอบเป็นส่วนหนึ่งของลำดับที่ตามมา เนื่องจากเราได้รวม arr[ind] เป้าหมายที่อัปเดตซึ่งเราต้องค้นหาในส่วนที่เหลือหากอาร์เรย์จะเป็นเป้าหมาย – arr[ind] ดังนั้น เราจะเรียก f(ind-1,target-arr[ind])
บันทึก:เราจะพิจารณาองค์ประกอบปัจจุบันในลำดับต่อมาก็ต่อเมื่อองค์ประกอบปัจจุบันน้อยกว่าหรือเท่ากับเป้าหมาย

ขั้นตอนที่ 3: ส่งคืน (ถ่าย || ไม่ถ่าย)
เนื่องจากเรากำลังมองหาเซตย่อยเพียงชุดเดียว หากเซตย่อยที่รับหรือไม่รับคืนค่าจริง เราก็สามารถคืนค่าจริงจากฟังก์ชันของเราได้ ดังนั้นเราจึงคืนค่า 'หรือ (||)' ของทั้งคู่
รหัสเทียมสุดท้ายหลังจากขั้นตอนที่ 1, 2 และ 3:

ขั้นตอนในการจดจำโซลูชันแบบเรียกซ้ำ:
ถ้าเราวาด recursion tree เราจะเห็นว่ามีปัญหาย่อยที่ทับซ้อนกัน ในการแปลงโซลูชันแบบเรียกซ้ำจะดำเนินการตามขั้นตอนต่อไปนี้:
- สร้างอาร์เรย์ dp ขนาด [n][k+1] ขนาดของอาร์เรย์อินพุตคือ 'n' ดังนั้นดัชนีจะอยู่ระหว่าง '0' และ 'n-1' เสมอ เป้าหมายสามารถรับค่าใดก็ได้ระหว่าง '0' ถึง 'k' ดังนั้นเราจึงใช้อาร์เรย์ dp เป็น dp[n][k+1]
- เราเริ่มต้นอาร์เรย์ dp เป็น -1
- เมื่อใดก็ตามที่เราต้องการหาคำตอบของพารามิเตอร์เฉพาะ (เช่น f(ind,target)) ก่อนอื่นเราจะตรวจสอบว่าคำตอบนั้นคำนวณไว้แล้วโดยใช้อาร์เรย์ dp หรือไม่ (เช่น dp[ind][target]!= -1 ) ถ้าใช่ ให้คืนค่าจากอาร์เรย์ dp
- ถ้าไม่ เรากำลังหาคำตอบสำหรับค่าที่กำหนดเป็นครั้งแรก เราจะใช้ความสัมพันธ์แบบเรียกซ้ำตามปกติ แต่ก่อนที่จะกลับจากฟังก์ชัน เราจะตั้งค่า dp[ind][target] เป็นคำตอบที่เราได้รับ
รหัส:
รหัส C++
#include โดยใช้เนมสเปซ std;bool subsetSumUtil(int ind, int target, vector& arr, vector> &dp){ if(target==0) return true; ถ้า (ind == 0) กลับ arr[0] == เป้าหมาย; if(dp[ind][target]!=-1) ส่งคืน dp[ind][target]; บูล notTaken = subsetSumUtil(ind-1,target,arr,dp); บูล = เท็จ; ถ้า(arr[ind]<=target) Take = subsetSumUtil(ind-1,target-arr[ind],arr,dp); ส่งคืน dp[ind][target]= notTaken||taken;}bool canPartition(int n, vector &arr){ int totSum=0; สำหรับ (int i=0; i> dp(n,เวกเตอร์(k+1,-1)); ส่งคืน subsetSumUtil(n-1,k,arr,dp); } }int main() { เวกเตอร์ arr = {2,3,3,3,4,5}; int n = arr.size(); if(canPartition(n,arr)) cout<<"อาร์เรย์สามารถแบ่งพาร์ติชันออกเป็นสองส่วนย่อยเท่าๆ กัน"; ศาลอื่น<<"อาร์เรย์ไม่สามารถแบ่งพาร์ติชันออกเป็นสองส่วนย่อยเท่าๆ กัน";}
เอาท์พุต:
อาร์เรย์สามารถแบ่งพาร์ติชันออกเป็นสองส่วนย่อยเท่าๆ กัน
ความซับซ้อนของเวลา: O(N*K) + O(N)
เหตุผล: มีสถานะ N*K ดังนั้นเมื่อสูงสุด 'N*K' ปัญหาใหม่จะได้รับการแก้ไข และเรากำลังเรียกใช้ for loop สำหรับ 'N' ครั้งเพื่อคำนวณผลรวมทั้งหมด
ความซับซ้อนของอวกาศ: O(N*K) + O(N)
เหตุผล: เรากำลังใช้ recursion stack space(O(N)) และ 2D array ( O(N*K))
รหัสจาวา
นำเข้า java.util.*;class TUF{static boolean subsetSumUtil(int ind, int target,int arr[],int[][] dp){ if(target==0) return true; ถ้า (ind == 0) กลับ arr[0] == เป้าหมาย; if(dp[ind][target]!=-1) ส่งคืน dp[ind][target]==0?false:true; บูลีน notTaken = subsetSumUtil(ind-1,target,arr,dp); บูลีนถ่าย = เท็จ; ถ้า(arr[ind]<=target) Take = subsetSumUtil(ind-1,target-arr[ind],arr,dp); dp[ind][target]=ไม่ถ่าย||ถ่ายแล้ว?1:0; กลับ notTaken||taken;}บูลีนคงที่ canPartition(int n,int[] arr){ int totSum=0; สำหรับ (int i=0; i
เอาท์พุต:
อาร์เรย์สามารถแบ่งพาร์ติชันออกเป็นสองส่วนย่อยเท่าๆ กัน
ความซับซ้อนของเวลา: O(N*K) + O(N)
เหตุผล: มีสถานะ N*K ดังนั้นเมื่อสูงสุด 'N*K' ปัญหาใหม่จะได้รับการแก้ไข และเรากำลังเรียกใช้ for loop สำหรับ 'N' ครั้งเพื่อคำนวณผลรวมทั้งหมด
ความซับซ้อนของอวกาศ: O(N*K) + O(N)
เหตุผล: เรากำลังใช้ recursion stack space(O(N)) และ 2D array ( O(N*K))
รหัสหลาม
def subsetSumUtil(ind, target, arr, dp): if target == 0: return True ถ้า ind == 0: return arr[0] == target if dp[ind][target] != -1: return dp[ ind][target] notTaken = subsetSumUtil(ind-1, target, arr, dp) Taken = False if arr[ind] <= target: Taken = subsetSumUtil(ind-1, target-arr[ind], arr, dp) dp[ind][target] = notTaken หรือ Take return dp[ind][target]def canPartition(n, arr): totSum = sum(arr) if totSum % 2 == 1: return False else: k = totSum // 2 dp = [[-1 for i in range(k+1)] for j in range(n)] return subsetSumUtil(n-1, k, arr, dp)def main(): arr = [2, 3, 3, 3, 4, 5] n = len(arr) if canPartition(n, arr): พิมพ์("อาร์เรย์สามารถแบ่งพาร์ติชันออกเป็นสองส่วนย่อยเท่าๆ กัน") อื่น: พิมพ์("ไม่สามารถแบ่งอาร์เรย์ออกเป็นสองส่วนย่อยเท่าๆ กัน ") ถ้า __name__ == "__main__": main()
เอาท์พุต:
อาร์เรย์สามารถแบ่งพาร์ติชันออกเป็นสองส่วนย่อยเท่าๆ กัน
ความซับซ้อนของเวลา: O(N*K) + O(N)
เหตุผล: มีสถานะ N*K ดังนั้นเมื่อสูงสุด 'N*K' ปัญหาใหม่จะได้รับการแก้ไข และเรากำลังเรียกใช้ for loop สำหรับ 'N' ครั้งเพื่อคำนวณผลรวมทั้งหมด
ความซับซ้อนของอวกาศ: O(N*K) + O(N)
เหตุผล: เรากำลังใช้ recursion stack space(O(N)) และ 2D array ( O(N*K))
ขั้นตอนในการแปลง Recursive Solution เป็น Tabulation one
หากต้องการแปลงวิธีการบันทึกช่วยจำเป็นแบบตาราง ให้สร้างอาร์เรย์ dp ที่มีขนาดเดียวกับที่ทำในการบันทึกช่วยจำ เราสามารถตั้งค่าประเภทเป็นบูลและเริ่มต้นเป็นเท็จ
ขั้นแรก เราต้องเริ่มต้นเงื่อนไขพื้นฐานของโซลูชันแบบเรียกซ้ำ
- หากเป้าหมาย == 0, ind สามารถรับค่าใดก็ได้ตั้งแต่ 0 ถึง n-1 ดังนั้น เราจำเป็นต้องตั้งค่าของคอลัมน์แรกเป็นจริง
- แถวแรก dp[0][] ระบุว่ามีการพิจารณาเฉพาะองค์ประกอบแรกของอาร์เรย์ ดังนั้นสำหรับค่าเป้าหมายเท่ากับ arr[0] เฉพาะเซลล์ที่มีเป้าหมายนั้นเท่านั้นที่จะเป็นจริง ดังนั้นให้ตั้งค่า dp[0 อย่างชัดเจน ][arr[0]] =true, (dp[0][arr[0]] หมายความว่าเรากำลังพิจารณาองค์ประกอบแรกของอาร์เรย์โดยมีเป้าหมายเท่ากับองค์ประกอบแรกนั่นเอง) โปรดทราบว่าอาจเกิดขึ้นได้ที่ arr[0]>target ดังนั้นเราจึงตรวจสอบก่อน: if(arr[0]<=target) จากนั้นตั้งค่า dp[0][arr[0]] = true
- หลังจากนั้น เราจะตั้งค่าซ้อนกันสำหรับลูปเพื่อสำรวจอาร์เรย์ dp และตามตรรกะที่กล่าวถึงในวิธีการเรียกซ้ำ เราจะตั้งค่าของแต่ละเซลล์ แทนที่จะเรียกซ้ำ เราจะใช้อาร์เรย์ dp เอง
- สุดท้าย เราจะคืนค่า dp[n-1][k] เป็นคำตอบ
รหัส:
รหัส C++
#include โดยใช้เนมสเปซ std;bool canPartition(int n, vector &arr){ int totSum=0; สำหรับ (int i=0; i> dp(n,เวกเตอร์<บูล>(k+1,เท็จ)); สำหรับ (int i=0; i arr = {2,3,3,3,4,5}; int n = arr.size(); if(canPartition(n,arr)) cout<<"อาร์เรย์สามารถแบ่งพาร์ติชันออกเป็นสองส่วนย่อยเท่าๆ กัน"; ศาลอื่น<<"อาร์เรย์ไม่สามารถแบ่งพาร์ติชันออกเป็นสองส่วนย่อยเท่าๆ กัน";}
เอาท์พุต:
อาร์เรย์สามารถแบ่งพาร์ติชันออกเป็นสองส่วนย่อยเท่าๆ กัน
ความซับซ้อนของเวลา: O(N*K) +O(N)
เหตุผล: มีลูปที่ซ้อนกันสองลูปที่อธิบาย O(N*K) และเมื่อเริ่มต้น เรากำลังเรียกใช้ for ลูปเพื่อคำนวณ totSum
ความซับซ้อนของอวกาศ: O(N*K)
เหตุผล: เรากำลังใช้อาร์เรย์ภายนอกขนาด 'N*K' Stack Space ถูกกำจัด
รหัสจาวา
นำเข้า java.util.*;class TUF{static boolean canPartition(int n,int[] arr){ int totSum=0; สำหรับ (int i=0; i
เอาท์พุต:
อาร์เรย์สามารถแบ่งพาร์ติชันออกเป็นสองส่วนย่อยเท่าๆ กัน
ความซับซ้อนของเวลา: O(N*K) +O(N)
เหตุผล: มีลูปที่ซ้อนกันสองลูปที่อธิบาย O(N*K) และเมื่อเริ่มต้น เรากำลังเรียกใช้ for ลูปเพื่อคำนวณ totSum
ความซับซ้อนของอวกาศ: O(N*K)
เหตุผล: เรากำลังใช้อาร์เรย์ภายนอกขนาด 'N*K' Stack Space ถูกกำจัด
รหัสหลาม
def canPartition(n, arr): totSum = sum(arr) if totSum % 2 == 1: return False else: k = totSum // 2 dp = [[False for j in range(k + 1)] for i in range(n)] for i in range(n): dp[i][0] = True if arr[0] <= k: dp[0][arr[0]] = True for ind in range(1, n): สำหรับเป้าหมายในระยะ (1, k + 1): notTaken = dp[ind - 1][target] Taken = False if arr[ind] <= target: Taken = dp[ind - 1][target - arr [ind]] dp[ind][target] = ไม่ถ่ายหรือรับคืน dp[n - 1][k]def main(): arr = [2,3,3,3,4,5] n = len(arr ) if canPartition(n, arr): print("อาร์เรย์สามารถแบ่งพาร์ติชันออกเป็นสองส่วนย่อยเท่าๆ กัน") อื่น: พิมพ์("อาร์เรย์ไม่สามารถแบ่งพาร์ติชันออกเป็นสองส่วนย่อยเท่าๆ กัน")if __name__ == '__main__': main()
เอาท์พุต:
อาร์เรย์สามารถแบ่งพาร์ติชันออกเป็นสองส่วนย่อยเท่าๆ กัน
ความซับซ้อนของเวลา: O(N*K) +O(N)
เหตุผล: มีลูปที่ซ้อนกันสองลูปที่อธิบาย O(N*K) และเมื่อเริ่มต้น เรากำลังเรียกใช้ for ลูปเพื่อคำนวณ totSum
ความซับซ้อนของอวกาศ: O(N*K)
เหตุผล: เรากำลังใช้อาร์เรย์ภายนอกขนาด 'N*K' Stack Space ถูกกำจัด
ส่วนที่ 3: การเพิ่มประสิทธิภาพพื้นที่
ถ้าเราดูความสัมพันธ์อย่างใกล้ชิด
dp[ind][เป้าหมาย] = dp[ind-1][เป้าหมาย] || dp[ind-1][target-arr[ind]]
เราเห็นว่าในการคำนวณค่าของเซลล์ของอาร์เรย์ dp เราต้องการเฉพาะค่าของแถวก่อนหน้า (เช่น prev) ดังนั้นเราจึงไม่จำเป็นต้องจัดเก็บอาร์เรย์ทั้งหมด ดังนั้นเราจึงสามารถปรับพื้นที่ให้เหมาะสมได้
บันทึก:เมื่อใดก็ตามที่เราสร้างแถวใหม่ (พูด cur) เราต้องตั้งค่าให้องค์ประกอบแรกเป็นจริงตามเงื่อนไขฐานของเราอย่างชัดเจน
รหัส:
รหัส C++
#include โดยใช้เนมสเปซ std;bool canPartition(int n, vector &arr){ int totSum=0; สำหรับ (int i=0; i ก่อนหน้า (k+1,เท็จ); ก่อนหน้า[0] = จริง; ถ้า(arr[0]<=k) ก่อนหน้า[arr[0]] = จริง; สำหรับ (int ind = 1; ind cur(k+1,false); เคอร์[0] = จริง; สำหรับ (int target= 1; target<=k; target++){ บูล notTaken = ก่อนหน้า[เป้าหมาย]; บูล = เท็จ; ถ้า(arr[ind]<=target) Take = ก่อนหน้า[target-arr[ind]]; cur[target]= ไม่ถ่าย||ถ่ายแล้ว; } ก่อนหน้า = เคอร์; } กลับก่อนหน้า [k]; } }int main() { เวกเตอร์ arr = {2,3,3,3,4,5}; int n = arr.size(); if(canPartition(n,arr)) cout<<"อาร์เรย์สามารถแบ่งพาร์ติชันออกเป็นสองส่วนย่อยเท่าๆ กัน"; ศาลอื่น<<"อาร์เรย์ไม่สามารถแบ่งพาร์ติชันออกเป็นสองส่วนย่อยเท่าๆ กัน";}
เอาท์พุต:
Array สามารถแบ่งออกเป็นสองส่วนย่อยเท่า ๆ กัน
ความซับซ้อนของเวลา: O(N*K) +O(N)
เหตุผล: มีลูปที่ซ้อนกันสองลูปที่อธิบาย O(N*K) และเมื่อเริ่มต้น เรากำลังเรียกใช้ for ลูปเพื่อคำนวณ totSum
ความซับซ้อนของอวกาศ: O(K)
เหตุผล: เรากำลังใช้อาร์เรย์ภายนอกขนาด 'K+1' เพื่อจัดเก็บเพียงแถวเดียว
รหัสจาวา
นำเข้า java.util.*;class TUF{static boolean canPartition(int n,int[] arr){ int totSum=0; สำหรับ (int i=0; i
เอาท์พุต:
Array สามารถแบ่งออกเป็นสองส่วนย่อยเท่า ๆ กัน
ความซับซ้อนของเวลา: O(N*K) +O(N)
เหตุผล: มีลูปที่ซ้อนกันสองลูปที่อธิบาย O(N*K) และเมื่อเริ่มต้น เรากำลังเรียกใช้ for ลูปเพื่อคำนวณ totSum
ความซับซ้อนของอวกาศ: O(K)
เหตุผล: เรากำลังใช้อาร์เรย์ภายนอกขนาด 'K+1' เพื่อจัดเก็บเพียงแถวเดียว
รหัสหลาม
def canPartition(n, arr): totSum = sum(arr) if totSum % 2 == 1: return False else: k = totSum // 2 prev = [False] * (k + 1) prev[0] = True if arr[0] <= k: ก่อนหน้า[arr[0]] = True สำหรับ ind in range(1, n): cur = [False] * (k + 1) cur[0] = True สำหรับเป้าหมายในช่วง(1 , k + 1): notTaken = ก่อนหน้า[เป้าหมาย] ถ่าย = False ถ้า arr[ind] <= เป้าหมาย: ถ่าย = ก่อนหน้า[เป้าหมาย - arr[ind]] cur[target] = ไม่ถ่ายหรือถ่ายก่อนหน้า = เคอร์ส่งคืนค่าก่อนหน้า[k ]def main(): arr = [2, 3, 3, 3, 4, 5] n = len(arr) if canPartition(n, arr): print("อาร์เรย์สามารถแบ่งพาร์ติชันออกเป็นสองส่วนย่อยเท่าๆ กัน") อื่น : พิมพ์ ("ไม่สามารถแบ่งอาร์เรย์ออกเป็นสองส่วนย่อยเท่า ๆ กัน")if __name__ == "__main__": main()
เอาท์พุต:
Array สามารถแบ่งออกเป็นสองส่วนย่อยเท่า ๆ กัน
ความซับซ้อนของเวลา: O(N*K) +O(N)
เหตุผล: มีลูปที่ซ้อนกันสองลูปที่อธิบาย O(N*K) และเมื่อเริ่มต้น เรากำลังเรียกใช้ for ลูปเพื่อคำนวณ totSum
ความซับซ้อนของอวกาศ: O(K)
เหตุผล: เรากำลังใช้อาร์เรย์ภายนอกขนาด 'K+1' เพื่อจัดเก็บเพียงแถวเดียว
ขอขอบคุณเป็นพิเศษกับอันชูมาน ชาร์มาและอุบาสิกา สำหรับบทความเกี่ยวกับ takeUforward นี้ หากคุณต้องการแบ่งปันความรู้ของคุณกับกลุ่ม TakeUforwardโปรดตรวจสอบบทความนี้