Java Generics to Code Efficiently in Competitive Programming


Java generics provide a mechanism for writing reusable and type-safe code. They allow classes, methods, and interfaces to operate on different data types while providing compile-time type checking. One of the primary advantages of using generics in competitive programming is the ability to create generic data structures. These data structures, such as stacks, queues, linked lists, and trees, can be implemented once and reused across multiple problem-solving scenarios.

This tutorial will give examples of Java generics and some methods used in competitive programming.

Java Generics

By utilizing Java generics, you can create versatile and efficient code that can handle a wide range of data types.

Example

In the example code, the findMaximum( ) method is a generic method that takes an array of type 'T', which extends Comparable<T>. It ensures that the elements in the array can be compared with each other. The method iterates through the array, comparing each element with the current maximum, and updates the maximum value accordingly.

public class Main {
   public static <T extends Comparable<T>> T findMaximum(T[] array) {
      if (array == null || array.length == 0) {
         throw new IllegalArgumentException("Array is empty");
      }        
      T maximum = array[0];
      for (int i = 1; i < array.length; i++) {
         if (array[i].compareTo(maximum) > 0) {
            maximum = array[i];
         }
      }        
      return maximum;
   }    
   public static void main(String[] args) {
      Integer[] numbers = { 5, 2, 9, 1, 7 };
      Integer maxNumber = findMaximum(numbers);
      System.out.println("Maximum number: " + maxNumber);       
      String[] words = { "apple", "oreo", "orange", "banana" };
      String maxWord = findMaximum(words);
      System.out.println("Maximum word: " + maxWord);
   }
}

Output

Maximum number: 9
Maximum word: oreo

Now, we will see some famous methods that are used in competitive programming.

Sieve Of Eratosthenes

The Sieve of Eratosthenes is an efficient algorithm used to find all prime numbers up to a given limit. By iteratively sieving out composite numbers, it identifies primes using a boolean array. With a time complexity of approximately O(n*log(log n)), it is a popular method for prime number generation in competitive programming.

Example

In the example code, the sieve( ) method takes an integer 'n' as input and returns a boolean array where each index represents a number up to n, indicating whether it is prime (true) or not (false).

import java.util.*;
public class Main {
   public static boolean[] sieve(int n) {
      boolean[] isPrime = new boolean[n + 1];
      Arrays.fill(isPrime, true);
      isPrime[0] = false;
      isPrime[1] = false;
      for (int i = 2; i * i <= n; i++) {
         if (isPrime[i]) {
            for (int j = i * i; j <= n; j += i) {
               isPrime[j] = false;
            }
         }
      }
      return isPrime;
   }
   public static void main(String[] args) {
      int n = 30;
      boolean[] primes = sieve(n);
      System.out.println("Prime numbers upto " + n + " are as follows:");
      for (int i = 2; i <= n; i++) {
         if (primes[i]) {
            System.out.print(i + " ");
         }
      }
   }
}

Output

Prime numbers upto 30 are as follows:
2 3 5 7 11 13 17 19 23 29

Binary Search

Binary search is an efficient searching algorithm used to locate a target element in a sorted array or collection. It repeatedly divides the search space in half, comparing the target element with the middle element. With a time complexity of O(log n), it quickly identifies the presence or absence of the target element.

Example

In the example code, the binarySearch( ) method takes an array of integers (nums) and a target value (target) as input. It performs a binary search on the sorted array to find the target element. If the target is found, it returns the index of the target element; otherwise, it returns -1 to indicate that the target element is not present in the array.

public class Main {
   public static int binarySearch(int[] nums, int target) {
      int left = 0;
      int right = nums.length - 1;
      while (left <= right) {
         int mid = left + (right - left) / 2;
         if (nums[mid] == target) {
            return mid;
         } else if (nums[mid] < target) {
            left = mid + 1;
         } else {
            right = mid - 1;
         }
      }
      return -1; // Target element not found
   }
   public static void main(String[] args) {
      int[] nums = { 2, 5, 8, 12, 16, 23, 38, 56, 72, 91 };
      int target = 16;
      int index = binarySearch(nums, target);
      if (index != -1) {
         System.out.println("Element found at index " + index);
      } else {
         System.out.println("Element not found");
      }
   }
}

Output

Element found at index 4

Factorial Using Memoization

Memoization involves caching previously computed factorial values to avoid redundant calculations. This technique reduces the number of repetitive computations and significantly improves performance, especially for large inputs.

Example

The example code has a 'memo' map, which serves as a cache to store previously computed factorial values. Before performing the recursive call, the code checks if the factorial for the given value 'n' is already present in the cache. If it is, the cached value is returned directly, avoiding redundant calculations. Otherwise, the recursive call is made to compute the factorial, and the result is stored in the cache for future use.

import java.util.HashMap;
import java.util.Map;
public class Main {
   private static Map<Integer, Integer> memo = new HashMap<>();
   public static int factorial(int n) {
      if (n == 0 || n == 1) {
         return 1;
      }
      if (memo.containsKey(n)) {
         return memo.get(n);
      }
      int result = n * factorial(n - 1);
      memo.put(n, result);
      return result;
   }
   public static void main(String[] args) {
      int n = 5;
      int fact = factorial(n);
      System.out.println("Factorial of " + n + " is: " + fact);
   }
}

Output

Factorial of 5 is: 120

Conclusion

In conclusion, Java generics offer a powerful toolset for coding efficiently in competitive programming. By leveraging generics, programmers can create versatile, type-safe code that adapts to different data types. Additionally, utilizing well-known methods like the Sieve of Eratosthenes and binary search provides effective solutions to prime number generation and efficient element search.

Updated on: 24-Jul-2023

57 Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements