Example 2
for ( int i = 0 ; i < n; i ++ ) {
for ( int j = 0 ; j < n; j ++ ) {
Total: O ( n ) + O ( n ) = O ( n ) O(n) + O(n) = O(n) O ( n ) + O ( n ) = O ( n ) .
A method that calls itself. Every recursive solution must have:
Base case(s): The simplest case(s) that can be solved directly.Recursive case(s): The method calls itself with a simpler (smaller) input.public static int factorial ( int n) {
return n * factorial (n - 1 );
Proof of correctness by induction. Base case: factorial(0) = 1 = 0!. Inductive step: assume factorial(k) = k! for all k < n k \lt n k < n . Then factorial(n) = n * factorial(n-1) = n * (n-1)! = n!. ■ \blacksquare ■
public static int fib ( int n) {
return fib (n - 1 ) + fib (n - 2 );
Time complexity: O ( 2 n ) O(2^n) O ( 2 n ) because each call spawns two subcalls with redundant computation.
Efficient alternative: O ( n ) O(n) O ( n ) iterative approach:
public static int fibIterative ( int n) {
for ( int i = 2 ; i <= n; i ++ ) {
public static String reverse ( String s) {
return reverse (s. substring ( 1 )) + s. charAt ( 0 );
Trace for reverse("abcd"):
reverse("abcd") = reverse("bcd") + 'a'
reverse("bcd") = reverse("cd") + 'b'
reverse("cd") = reverse("d") + 'c'
Trace of selection sort on [5, 3, 8, 1, 2]:
Pass Array State Comparisons Swaps 1 [1, 3, 8, 5, 2] 4 1 2 [1, 2, 8, 5, 3] 3 1 3 [1, 2, 3, 5, 8] 2 1 4 [1, 2, 3, 5, 8] 1 0
Total comparisons: 4 + 3 + 2 + 1 = 10 = n ( n − 1 ) / 2 4 + 3 + 2 + 1 = 10 = n(n-1)/2 4 + 3 + 2 + 1 = 10 = n ( n − 1 ) /2 for n = 5 n = 5 n = 5 .
Trace of insertion sort on [4, 2, 7, 1, 3]:
Step Array State Key Shifts Comparisons 1 [4, 2, 7, 1, 3] 2 1 1 2 [4, 2, 7, 1, 3] 7 0 1 3 [4, 2, 7, 1, 3] 1 3 3 4 [1, 2, 4, 7, 3] 3 0 1
Total comparisons: 1 + 1 + 3 + 1 = 6 1 + 1 + 3 + 1 = 6 1 + 1 + 3 + 1 = 6 .
Trace of merge sort on [38, 27, 43, 3]:
Split: [38, 27] and [43, 3].
Sort [38, 27]: split into [38] and [27], merge to [27, 38]. Sort [43, 3]: split into [43] and [3], Merge to [3, 43].
Merge [27, 38] and [3, 43]: [3, 27, 38, 43].
Quick sort has O ( n 2 ) O(n^2) O ( n 2 ) worst case when the pivot is always the smallest or largest element. This Happens when the array is already sorted (or reverse sorted) and the first or last element is used As the pivot.
Mitigation: Choose the middle element or a random element as the pivot.
Iterative binary search uses O ( 1 ) O(1) O ( 1 ) space. The recursive version uses O ( log n ) O(\log n) O ( log n ) stack space Due to recursive calls.
public static int binarySearchIterative ( int [] arr, int target) {
int high = arr.length - 1 ;
int mid = low + (high - low) / 2 ;
if (arr[mid] == target) return mid;
else if (arr[mid] < target) low = mid + 1 ;
Divide and conquer: Break the problem into smaller subproblems, solve each recursively, combine Results. Examples: merge sort, binary search.
Greedy: Make the locally optimal choice at each step. Examples: Dijkstra’s shortest path, Huffman coding, coin change (with standard denominations).
Dynamic programming: Solve overlapping subproblems by storing results in a table. Examples: Fibonacci (with memoisation), knapsack problem, longest common subsequence.
Worked Example. Fibonacci with memoisation reduces O ( 2 n ) O(2^n) O ( 2 n ) to O ( n ) O(n) O ( n ) .
public static long fibMemo ( int n) {
long [] memo = new long [n + 1 ];
for ( int i = 2 ; i <= n; i ++ ) {
memo[i] = memo[i - 1 ] + memo[i - 2 ];
Trace of merge sort on [38, 27, 43, 3]:
Split: [38, 27] and [43, 3].
Sort [38, 27]: split into [38] and [27], merge to [27, 38]. Sort [43, 3]: split into [43] and [3], Merge to [3, 43].
Merge [27, 38] and [3, 43]: [3, 27, 38, 43].
A divide-and-conquer algorithm that picks a pivot, partitions elements around it, and recursively Sorts each partition.
Java implementation:
public static void quickSort ( int [] arr, int low, int high) {
int pi = partition (arr, low, high);
quickSort (arr, low, pi - 1 );
quickSort (arr, pi + 1 , high);
private static int partition ( int [] arr, int low, int high) {
for ( int j = low; j < high; j ++ ) {
Quick Sort worst case. O ( n 2 ) O(n^2) O ( n 2 ) when the pivot is always the smallest or largest element. This Happens when the array is already sorted (or reverse sorted) and the first or last element is used As the pivot.
Mitigation: Choose the middle element or a random element as the pivot.
Quick Sort trace on [5, 3, 8, 1, 2]:
Pivot = 2 (last element). Partition: [1, 2, 8, 5, 3]. Pivot index = 1.
Sort [1]: single element, done.
Sort [8, 5, 3]: Pivot = 3. Partition: [3, 5, 8]. Pivot index = 2.
Sort [5, 8]: Pivot = 8. Partition: [5, 8]. Pivot index = 1.
Final: [1, 2, 3, 5, 8].
Iterative binary search uses O ( 1 ) O(1) O ( 1 ) space. The recursive version uses O ( log n ) O(\log n) O ( log n ) stack space Due to recursive calls.
public static int binarySearchIterative ( int [] arr, int target) {
int high = arr.length - 1 ;
int mid = low + (high - low) / 2 ;
if (arr[mid] == target) return mid;
else if (arr[mid] < target) low = mid + 1 ;
Divide and conquer: Break the problem into smaller subproblems, solve each recursively, combine Results. Examples: merge sort, binary search.
Greedy: Make the locally optimal choice at each step. Examples: Dijkstra’s shortest path, Huffman coding, coin change (with standard denominations).
Dynamic programming: Solve overlapping subproblems by storing results in a table. Examples: Fibonacci (with memoisation), knapsack problem, longest common subsequence.
Worked Example. Fibonacci with memoisation reduces O ( 2 n ) O(2^n) O ( 2 n ) to O ( n ) O(n) O ( n ) .
public static long fibMemo ( int n) {
long [] memo = new long [n + 1 ];
for ( int i = 2 ; i <= n; i ++ ) {
memo[i] = memo[i - 1 ] + memo[i - 2 ];
Palindrome check (Java):
public static boolean isPalindrome ( String s) {
int right = s. length () - 1 ;
if (s. charAt (left) != s. charAt (right)) return false ;
Time complexity: O ( n ) O(n) O ( n ) — compares each pair of characters once.
Proof of correctness. The loop compares characters at symmetric positions: position 0 with Position n-1, position 1 with position n-2, etc. If any pair differs, the string is not a palindrome And the method returns false. If all pairs match, the string reads the same forwards and backwards, So it is a palindrome. lacksquare
String reversal (iterative):
public static String reverse ( String s) {
StringBuilder sb = new StringBuilder ();
for ( int i = s. length () - 1 ; i >= 0 ; i -- ) {
Time complexity: O ( n ) O(n) O ( n ) — appends each character once.
Finding the mode (most frequent element):
public static int findMode ( int [] arr) {
for ( int i = 0 ; i < arr.length; i ++ ) {
for ( int j = 0 ; j < arr.length; j ++ ) {
if (arr[j] == arr[i]) count ++ ;
Time complexity: O ( n 2 ) O(n^2) O ( n 2 ) — nested loops.
Two-pointer technique:
public static boolean hasPairWithSum ( int [] arr, int target) {
int right = arr.length - 1 ;
int sum = arr[left] + arr[right];
if (sum == target) return true ;
else if (sum < target) left ++ ;
Time complexity: O ( n ) O(n) O ( n ) — requires a sorted array.
Trace selection sort on [5, 3, 8, 1, 2]. Show the array after each pass.
Write a recursive Java method sumDigits(int n) that returns the sum of the digits of n.
Explain why the recursive Fibonacci algorithm has O ( 2 n ) O(2^n) O ( 2 n ) time complexity.
Write a Java method that performs binary search on a sorted array of strings.
Analyze the time complexity of the following code:
for ( int i = 0 ; i < n; i ++ ) {
for ( int j = i; j < n; j ++ ) {
System.out. println (i * j);
Compare the number of comparisons made by linear search and binary search for an array of 1,048,576 elements when searching for an element that is not in the array.
Write pseudocode for insertion sort and trace it on the list [4, 2, 7, 1, 3].
Explain why merge sort is preferred over selection sort for sorting large datasets.
Write a recursive Java method gcd(int a, int b) that computes the greatest common divisor using Euclid’s algorithm.
Explain the loop invariant for binary search and use it to prove correctness.
Write a Java method isSorted(int[] arr) that returns true if the array is sorted in ascending order. What is the time complexity?
Prove that insertion sort’s best-case time complexity is O ( n ) O(n) O ( n ) when the input is already sorted.
Write a recursive method power(int base, int exp) that computes base^exp in O ( log n ) O(\log n) O ( log n ) time using the identity b n = ( b n / 2 ) 2 b^n = (b^{n/2})^2 b n = ( b n /2 ) 2 .
Explain what makes an algorithm “stable” in the context of sorting. Give an example where stability matters.
Write pseudocode for a procedure that merges two sorted lists into one sorted list. What is the time complexity?
Analyze the space complexity of merge sort. Why does it use O ( n ) O(n) O ( n ) extra space?
Write a Java method to find the kth smallest element in an unsorted array. What is the time complexity of a simple approach vs. An optimal approach?
Explain the concept of divide and conquer using merge sort as an example. What are the three steps, and what is the recurrence relation?
Trace quick sort on the array [10, 80, 30, 90, 40, 50, 70]. Use the last element as the pivot.
Write a Java method that checks whether a sorted array contains two elements that sum to a given target. Your solution should be O ( n ) O(n) O ( n ) .
Algorithms and programming are about solving problems systematically . An algorithm is a step-by-step procedure; a program is its implementation in a specific language. The key insight is that the same algorithm can be written in many languages, and the same language can express many algorithms.
Algorithm design intuition: Before writing code, think about the strategy . Brute force tries everything (simple but slow). Divide and conquer breaks the problem into smaller pieces (like merge sort). Greedy algorithms make locally optimal choices (like Dijkstra’s). Dynamic programming stores solutions to subproblems (like the knapsack problem). The right strategy depends on the problem structure.
Programming paradigm intuition: Imperative programming (loops, variables, state) tells the computer how to do something. Functional programming (pure functions, no side effects) tells the computer what to compute. Object-oriented programming (classes, inheritance, encapsulation) organises code around data and behaviour. Each paradigm excels in different domains.
Forgetting the base case in recursion. Causes infinite recursion and stack overflow.
Confusing binary search with linear search. Binary search requires a sorted array.
Using selection sort or insertion sort for large datasets. These O ( n 2 ) O(n^2) O ( n 2 ) algorithms are too slow; use merge sort (O ( n log n ) O(n \log n) O ( n log n ) ) instead.
Off-by-one errors in binary search. The condition is low <= high (inclusive).
Confusing Big-O with exact running time. Big-O describes growth rate, not actual time.
Forgetting that Big-O is an upper bound. An O ( n ) O(n) O ( n ) algorithm is also O ( n 2 ) O(n^2) O ( n 2 ) and O ( n 3 ) O(n^3) O ( n 3 ) but O ( n ) O(n) O ( n ) is the tightest (best) bound.
Not recognizing that AP CSP pseudocode uses 1-based indexing while Java uses 0-based indexing.
Integer overflow in (low + high) / 2. Use low + (high - low) / 2 instead.
Trace the execution of selection sort on the array [5, 3, 8, 1, 2]. Show the array after each pass.
Write a recursive Java method sumDigits(int n) that returns the sum of the digits of n.
Explain why the recursive Fibonacci algorithm has O ( 2 n ) O(2^n) O ( 2 n ) time complexity.
Write a Java method that performs binary search on a sorted array of strings.
Analyze the time complexity of the following code:
for ( int i = 0 ; i < n; i ++ ) {
for ( int j = i; j < n; j ++ ) {
System.out. println (i * j);
Compare the number of comparisons made by linear search and binary search for an array of 1,048,576 elements when searching for an element that is not in the array.
Write pseudocode for insertion sort and trace it on the list [4, 2, 7, 1, 3].
Explain why merge sort is preferred over selection sort for sorting large datasets.
Write a recursive Java method gcd(int a, int b) that computes the greatest common divisor using Euclid’s algorithm.
Explain the loop invariant for binary search and use it to prove correctness.
Write a Java method isSorted(int[] arr) that returns true if the array is sorted in ascending order. What is the time complexity?
Prove that insertion sort’s best-case time complexity is O ( n ) O(n) O ( n ) when the input is already sorted.
Write a recursive method power(int base, int exp) that computes base^exp in O ( log n ) O(\log n) O ( log n ) time using the identity b n = ( b n / 2 ) 2 b^n = (b^{n/2})^2 b n = ( b n /2 ) 2 .
Explain what makes an algorithm “stable” in the context of sorting. Give an example where stability matters.
Write pseudocode for a procedure that merges two sorted lists into one sorted list. What is the time complexity?
Analyze the space complexity of merge sort. Why does it use O ( n ) O(n) O ( n ) extra space?
Write a Java method to find the kth smallest element in an unsorted array. What is the time complexity of a simple approach vs. An optimal approach?
Explain the concept of divide and conquer using merge sort as an example. What are the three steps, and what is the recurrence relation?
Question 1: Time complexity of nested loops Analyze the time complexity of the following code:
for ( int i = 1 ; i <= n; i = i * 2 ) {
for ( int j = 0 ; j < n; j ++ ) {
System.out. println (i + j);
Answer The outer loop runs log 2 ( n ) \log_2(n) log 2 ( n ) times because i doubles each iteration. The inner loop runs n n n times for each outer iteration. Total: O ( n log n ) O(n \log n) O ( n log n ) .
Question 2: Binary search trace Trace binary search on [2, 5, 8, 12, 16, 23, 38] searching for 7. Show low``highAnd mid at each step.
Answer Step 1: low=0, high=6, mid=3. Arr[3]=12 > 7, high=2. Step 2: low=0, high=2, mid=1. Arr[1]=5 < 7, low=2. Step 3: low=2, high=2, mid=2. Arr[2]=8 > 7, high=1. Step 4: low=2 > high=1. Not found, return -1.
Question 3: Recursive algorithm What does mystery(27) return and what is the time complexity?
return mystery (n / 3 ) + n;
Answer Mystery(27) = mystery(9) + 27 = (mystery(3) + 9) + 27 = ((mystery(1) + 3) + 9) + 27 = 40.
Time complexity: O ( log n ) O(\log n) O ( log n ) since n is divided by 3 each call.
Question 4: Selection sort trace Trace selection sort on [64, 25, 12, 22, 11]. Show the array after each pass and count swaps.
Answer Pass 1: Min=11 (idx 4), swap with 0: [11, 25, 12, 22, 64]. 1 swap. Pass 2: Min=12 (idx 2), swap with 1: [11, 12, 25, 22, 64]. 1 swap. Pass 3: Min=22 (idx 3), swap with 2: [11, 12, 22, 25, 64]. 1 swap. Pass 4: Already sorted. 0 swaps. Total: 3 swaps.
Question 5: Merge sort comparisons What is the maximum number of comparisons merge sort makes for 8 elements?
Answer Using C ( n ) = 2 C ( n / 2 ) + n − 1 C(n) = 2C(n/2) + n - 1 C ( n ) = 2 C ( n /2 ) + n − 1 : C(1)=0$$C(2)=1$$C(4)=5$$C(8)=17 . Maximum 17 comparisons.
A[3_Algorithms And Programming] --> B[Key Concepts]
A --> D[Practical Applications]
B --> E[Fundamental definitions]
D --> G[Real-world usage]
This topic covers the core concepts of algorithms and programming, including underlying theory, practical implementation, and key applications.
Key concepts include:
Big O notation and complexity analysis searching algorithms (binary, linear) sorting algorithms (bubble, merge, quick) graph algorithms (Dijkstra, BFS, DFS) dynamic programming Understanding these concepts thoroughly is essential for both examinations and practical programming, and requires both theoretical knowledge and hands-on practice.
Worked examples demonstrating the application of key concepts are covered in the detailed sub-pages linked above.