Backtracking Algorithms for Combination Sum and Palindrome Partitioning

Combination Sum

The objective is to find all unique combinations from a list of candidate numbers that sum up to a given target. Each number may be used multiple times.

The solution uses recursive backtracking with the following approach:

  • Parameters include the candidate array, target value, current combination, and results collection
  • Termination occurss when the current sum exceeds the target, or equals it (at which point the combination is stored)
  • During iteration, elements are selected starting from the current position to avoid duplicate combinations
class Solution:
    def explore_combinations(self, options, goal, current_path, outcomes):
        path_sum = sum(current_path)
        if path_sum > goal:
            return
        if path_sum == goal:
            outcomes.append(current_path[:])
            return
        
        for idx, value in enumerate(options):
            current_path.append(value)
            # Pass subarray from current index to allow repeated use
            self.explore_combinations(options[idx:], goal, current_path, outcomes)
            current_path.pop()
    
    def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
        solutions = []
        self.explore_combinations(candidates, target, [], solutions)
        return solutions

Combination Sum II

This variant requires each element to be used at most once and eliminates duplicate combinations in the result set.

Efficient deduplication is achieved during traversal rather than post-processing:

  • Sort the candidate array so identical values become adjacent
  • Use a tracking array to record whether an element has been used in the current branch
  • Skip over elements that would create duplicate combinations at the same tree level
class Solution:
    def traverse_with_dedup(self, begin_pos, usage_flags, elements, limit, segment, results):
        segment_total = sum(segment)
        if segment_total > limit:
        if segment_total == limit:
            results.append(segment[:])
            return
        
        for pos in range(begin_pos, len(elements)):
            # Deduplicate at layer level
            if (pos > 0 and 
                elements[pos] == elements[pos-1] and 
                usage_flags[pos-1] == 0):
                continue
                
            segment.append(elements[pos])
            usage_flags[pos] = 1
            self.traverse_with_dedup(pos + 1, usage_flags, elements, limit, segment, results)
            segment.pop()
            usage_flags[pos] = 0
    
    def combinationSum2(self, candidates, target):
        final_results = []
        if sum(candidates) < target:
            return final_results
            
        candidates.sort()
        flags = [0] * len(candidates)
        self.traverse_with_dedup(0, flags, candidates, target, [], final_results)
        return final_results

Palindrome Partitioning

The task involves splitting a string into substrings where each substring is a palindrome.

The backtracking strategy treats string segmentation similarly to combination generation:

  • Instead of selecting individual elements, contiguous substrings are evaluated
  • A helper function checks if a substring is palindromic using two pointers
  • Recursion continues until the entire string is processed
class Solution:
    def generate_partitions(self, text, current_segments, all_partitions, start_position):
        if start_position == len(text):
            all_partitions.append(current_segments[:])
            return
        
        for end_position in range(start_position, len(text)):
            substring = text[start_position:end_position + 1]
            if self.check_palindrome(substring):
                current_segments.append(substring)
                self.generate_partitions(text, current_segments, all_partitions, end_position + 1)
                current_segments.pop()
    
    def check_palindrome(self, sequence):
        left_pointer, right_pointer = 0, len(sequence) - 1
        while left_pointer < right_pointer:
            if sequence[left_pointer] != sequence[right_pointer]:
                return False
            left_pointer += 1
            right_pointer -= 1
        return True
    
    def partition(self, s: str) -> List[List[str]]:
        valid_partitions = []
        self.generate_partitions(s, [], valid_partitions, 0)
        return valid_partitions

Tags: backtracking Recursion combinatorics palindrome

Posted on Fri, 05 Jun 2026 17:01:53 +0000 by abushahin