Tutorials Logic, IN info@tutorialslogic.com
Navigation
Home About Us Contact Us Blogs FAQs
Tutorials
All Tutorials
Services
Academic Projects Resume Writing Website Development
Practice
Quiz Challenge Interview Questions Certification Practice
Tools
Online Compiler JSON Formatter Regex Tester CSS Unit Converter Color Picker
Compiler Tools

Algorithm Design Techniques Divide, Greedy, DP: Tutorial, Examples, FAQs & Interview Tips

Algorithm Design Techniques

Algorithm design techniques are standard problem-solving strategies used to build efficient algorithms. Instead of inventing a new method for every problem, we study a small set of powerful techniques and learn when to apply each one.

These techniques are important because many problems in sorting, searching, optimization, graphs, and recursion follow common patterns. Once you recognize the pattern, choosing the right technique becomes much easier.

Why Do We Need Design Techniques?

Different problems require different ways of thinking. A direct brute-force solution may be correct, but it is often too slow for large input sizes. Design techniques help us create solutions that are:

  • Systematic: We follow a proven method instead of guessing.
  • Efficient: We reduce unnecessary work and repeated computation.
  • Scalable: The solution continues to work for larger inputs.
  • Easier to analyze: Time and space complexity become easier to understand.
Technique Main Idea Best Used For Examples
Divide and Conquer Split, solve smaller parts, then combine Problems that can be broken into independent subproblems Merge Sort, Quick Sort, Binary Search
Greedy Method Take the best immediate choice at each step Problems where local choices lead to a global optimum Activity Selection, Kruskal, Huffman Coding
Dynamic Programming Store results of repeated subproblems Optimization problems with overlapping subproblems Knapsack, Fibonacci, LCS
Backtracking Try choices, undo wrong ones, continue searching Constraint satisfaction and exhaustive search N-Queens, Sudoku, Maze solving
Branch and Bound Search with bounds to discard bad branches early Hard optimization problems TSP, 0/1 Knapsack
Randomized Use randomness to improve expected performance When worst-case patterns should be avoided Randomized Quick Sort, Monte Carlo methods

How to Choose the Right Technique

Before selecting a technique, ask the following questions:

  • Can the problem be divided into smaller problems of the same type?
  • Do subproblems overlap and repeat?
  • Does a locally best choice always help build the final optimal answer?
  • Do we need to try many possibilities and reject invalid ones?
  • Can we prune large parts of the search space using bounds or conditions?

The answers to these questions often directly point to divide and conquer, dynamic programming, greedy, backtracking, or branch and bound.

1. Divide and Conquer

Divide and Conquer solves a problem by splitting it into smaller independent subproblems, solving them recursively, and combining their results.

General Steps

  • Divide: Break the problem into smaller subproblems.
  • Conquer: Solve each subproblem recursively.
  • Combine: Merge the smaller solutions into the final answer.

When it works well: When subproblems are independent and the combine step is efficient.

Common examples: Merge Sort, Quick Sort, Binary Search, Strassen Matrix Multiplication.

Divide and Conquer - Binary Search
public class BinarySearch {

    static int search(int[] arr, int low, int high, int target) {
        if (low > high) {
            return -1;
        }

        int mid = low + (high - low) / 2;

        if (arr[mid] == target) {
            return mid;
        }

        if (target < arr[mid]) {
            return search(arr, low, mid - 1, target);
        }

        return search(arr, mid + 1, high, target);
    }

    public static void main(String[] args) {
        int[] arr = {3, 8, 14, 19, 27, 31, 42, 56};
        System.out.println(search(arr, 0, arr.length - 1, 27));
        System.out.println(search(arr, 0, arr.length - 1, 10));
    }
}

/*
Analysis
- Each step cuts the search space into half
- Time Complexity: O(log n)
- Space Complexity: O(log n) due to recursion
*/

2. Greedy Method

Greedy algorithms build a solution step by step by always choosing the option that looks best at the current moment. The choice is not reconsidered later.

Two Important Conditions

  • Greedy choice property: A local optimum leads toward a global optimum.
  • Optimal substructure: An optimal solution contains optimal solutions to subproblems.

When it works well: Scheduling, minimum spanning trees, shortest path with non-negative weights, data compression.

Main limitation: Greedy is fast, but it does not always guarantee the best answer.

Greedy - Activity Selection
public class ActivitySelection {

    static void selectActivities(int[] start, int[] finish) {
        int n = start.length;
        int last = 0;

        System.out.println("Selected activities:");
        System.out.println("Activity 0");

        for (int i = 1; i < n; i++) {
            if (start[i] >= finish[last]) {
                System.out.println("Activity " + i);
                last = i;
            }
        }
    }

    public static void main(String[] args) {
        int[] start = {1, 3, 0, 5, 8, 5};
        int[] finish = {2, 4, 6, 7, 9, 9};
        selectActivities(start, finish);
    }
}

/*
Greedy idea:
- Always choose the activity that finishes earliest
- This leaves maximum room for future activities
*/

3. Dynamic Programming

Dynamic Programming (DP) is used when a problem has repeated subproblems. Instead of solving the same subproblem again and again, we store the result and reuse it.

Core Properties

  • Overlapping subproblems: The same smaller problems appear many times.
  • Optimal substructure: The optimal solution can be built from optimal solutions of smaller parts.

Main Approaches

  • Memoization: Top-down recursion with storage.
  • Tabulation: Bottom-up tl-table building.

When it works well: Knapsack, Fibonacci, LCS, matrix chain multiplication, shortest path in weighted graphs.

Dynamic Programming - Fibonacci
public class FibonacciDP {

    static long fibonacci(int n) {
        if (n <= 1) {
            return n;
        }

        long[] dp = new long[n + 1];
        dp[0] = 0;
        dp[1] = 1;

        for (int i = 2; i <= n; i++) {
            dp[i] = dp[i - 1] + dp[i - 2];
        }

        return dp[n];
    }

    public static void main(String[] args) {
        System.out.println(fibonacci(10));
        System.out.println(fibonacci(50));
    }
}

/*
Analysis
- Naive recursion: O(2^n)
- DP version: O(n)
- Extra space: O(n)
*/

4. Backtracking

Backtracking tries to build a solution step by step. If a partial solution becomes invalid, the algorithm undoes the last choice and tries another path.

Backtracking Pattern

  • Choose: Make one possible decision.
  • Explore: Continue recursively.
  • Unchoose: Undo the decision if it does not work.

When it works well: Constraint satisfaction problems, permutations, combinations, puzzles, path finding.

Main limitation: The worst-case time is often exponential, but pruning can still make it practical.

Backtracking - Generate Permutations
public class Permutations {

    static void swap(char[] arr, int i, int j) {
        char temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    static void generate(char[] arr, int index) {
        if (index == arr.length) {
            System.out.println(new String(arr));
            return;
        }

        for (int i = index; i < arr.length; i++) {
            swap(arr, index, i);
            generate(arr, index + 1);
            swap(arr, index, i);
        }
    }

    public static void main(String[] args) {
        generate("ABC".toCharArray(), 0);
    }
}

5. Branch and Bound

Branch and Bound is an advanced search technique used mainly for optimization problems. It is similar to backtracking, but instead of exploring every possibility blindly, it calculates a bound for each partial solution.

How It Works

  • Branch: Divide the problem into subproblems.
  • Bound: Estimate the best possible result from each subproblem.
  • Prune: Ignore a branch if it cannot beat the current best solution.

When it works well: Travelling Salesman Problem, assignment problem, integer programming, 0/1 Knapsack.

Compared to plain backtracking, branch and bound usually prunes more aggressively because it uses a numerical estimate of how good a branch can still become.

6. Randomized Algorithms

Randomized algorithms use random choices as part of their logic. The goal is usually to improve average performance, simplify the method, or avoid specially crafted worst-case inputs.

Why Randomization Helps

  • It can reduce the chance of consistently bad inputs.
  • It may produce good expected performance even when deterministic methods struggle.
  • It is useful in approximation, simulation, and probabilistic checking.

Examples: Randomized Quick Sort, randomized selection, Monte Carlo algorithms, Las Vegas algorithms.

Comparison of Major Techniques

Technique Strength Weakness Typical Complexity Pattern
Divide and Conquer Very efficient for recursive splitting problems Not useful if subproblems heavily overlap Often O(n log n) or O(log n)
Greedy Simple and fast May fail to find the optimal answer Often polynomial
Dynamic Programming Converts exponential recursion into polynomial time May require large memory tables Often O(n^2), O(nW), or similar
Backtracking Can find exact solutions for constraint problems Worst case often exponential Often O(b^d)
Branch and Bound Better pruning for optimization problems Still expensive for very large search spaces Problem-dependent
Randomized Strong expected performance Result may depend on probability Expected complexity usually analyzed

Technique Selection Guide

If the Problem Looks Like... Technique to Consider Reason
Repeated smaller independent parts Divide and Conquer The problem naturally splits and combines
Repeated subproblems in recursion Dynamic Programming Stored results prevent recomputation
Best immediate choice appears safe Greedy Local optimum may lead to global optimum
Need to test many combinations with constraints Backtracking Invalid partial solutions can be rejected early
Optimization with expensive search space Branch and Bound Bounds help skip hopeless branches
Worst-case inputs should be avoided probabilistically Randomized Random choices improve expected behavior

Common Mistakes While Learning These Techniques

  • Using greedy without proving that the greedy choice is always safe.
  • Confusing divide and conquer with dynamic programming.
  • Applying dynamic programming even when subproblems do not overlap.
  • Forgetting the "undo" step in backtracking.
  • Ignoring space complexity while optimizing time complexity.
  • Using branch and bound without a useful bounding function.

Key Takeaways

  • Algorithm design techniques are reusable strategies for solving classes of problems.
  • Divide and conquer works by splitting, solving, and combining.
  • Greedy is fast and simple, but only works for suitable problems.
  • Dynamic programming is ideal when subproblems overlap.
  • Backtracking and branch and bound are powerful for search and optimization.
  • Randomized algorithms improve expected behavior using probability.
  • The best technique depends on the structure of the problem, not personal preference.

What to Study Next

After understanding algorithm design techniques, the next useful topic is asymptotic notation. It helps you measure and compare how efficient each technique really is as the input size grows.

Ready to Level Up Your Skills?

Explore 500+ free tutorials across 20+ languages and frameworks.