class Solution:
    """
        main idea: for sorted arr, convert general N sum to two pointers + backtracking;
        base case: two sum, termination condition: check if arr[l] + arr[r] == target   arr size == 2  ; general case: N sum 
    
    """
# time O(N^3) space O(N) 
# main idea: use l,r pointers to find valid unique combination(backtracking) in sorted nums; 
    def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
        if not nums or len(nums)< 4:
            return []
        nums.sort()

        res = []
        self.findNsum(nums,target,4,[],res)
        return res
    def findNsum(self,arr,target,N,path,res):
        if len(arr) < N or N < 2:return 
        if N == 2:
            l,r = 0,len(arr)-1
            while l < r:
                if arr[l] + arr[r] == target:
                    res.append(path+[arr[l],arr[r]])
                    l += 1
                    r -= 1
                    while l < r and arr[l]==arr[l-1]:
                        l += 1
                    while l < r and arr[r] == arr[r+1]:
                        r -= 1
                elif arr[l] + arr[r] < target:
                    l += 1
                else:
                    r -= 1
        else:
            for i in range(0,len(arr)-N+1):
                if target < arr[i]*N or target > arr[-1]*N:
                    break
                if i == 0 or (i > 0 and arr[i] != arr[i-1]):
                    self.findNsum(arr[i+1:],target-arr[i],N-1, path+[arr[i]],res)
        return 

optimization

class Solution:
        def fourSum(self, A, target):
            """ tc O(NlgN + N^(K-1)) sc O(K)
            """
            def backtrack( l, r, K, path, target, res):
                # early termination
                if r - l + 1  < K  or K < 2 or  A[l]*K > target or A[r]*K < target: 
                    return  
                if K == 2: # two pointers solve sorted 2-sum problem
                    while l < r:
                        twoSum = A[l] + A[r] 
                        if twoSum == target:
                            res.append(path + [A[l], A[r]]) # backtrack 

                            l += 1 
                            while l < r and A[l] == A[l-1]:
                                l += 1 
                        elif twoSum < target:
                            l += 1
                        else:
                            r -= 1 
                else:# recursively reduce K
                    for i in range(l,r+1):#range(len(A)-K-1):
                        if i == l or (i > l and A[i] != A[i-1]):
                            backtrack(i+1, r, K-1, path+[A[i]], target-A[i], res)
                
                
            res = [] 
            A.sort()
            backtrack(0, len(A)-1, 4, [], target, res)
            return res