Arrays, Strings, and StringBuilder in Java

When diving into Java programming, the foundational tools for handling data and text are Arrays, Strings, and Builders. Each serves a distinct purpose, and together, they form the backbone of efficient data processing and text manipulation.

Arrays offer fixed-size, contiguous storage, making them the go-to choice when you need fast, indexed access to elements. Arrays lay the groundwork for more sophisticated data structures such as linked lists, trees, and hash tables. Whether you're storing integers for computation or objects for a sorting algorithm, arrays ensure you have a predictable memory layout and direct access to elements.

Strings are immutable sequences of characters, playing a critical role in text processing. Because Strings are immutable, they are safe to use across threads and reduce errors from unintended modifications. Immutable objects also facilitate caching, making Strings ideal for handling fixed or frequently reused text data. Think of applications that require processing user input, generating reports, or sending formatted responses — Strings are indispensable.

However, immutability in Strings can become a bottleneck when repeated modifications are necessary. This is where StringBuilder (and its thread-safe counterpart, StringBuffer) come into play. These mutable classes offer efficient text manipulation by avoiding the creation of multiple intermediate objects. Whether you are concatenating a series of log entries, parsing input data, or constructing large text blocks dynamically, StringBuilder provides a much-needed boost in performance.

Common Scenarios Where They Shine:

  1. Processing Large Datasets:
    Arrays handle large, fixed-size data collections efficiently, making them suitable for batch processing, sorting, and searching operations.

  2. Efficient Text Concatenation:
    When building a long message from multiple pieces, using a StringBuilder avoids the overhead of creating numerous String objects.

  3. Parsing and Formatting Input/Output:
    Strings are essential for parsing structured input (like CSV or JSON), while StringBuilder helps format dynamic output for reports or logs.

Arrays in Java

Introduction to Arrays

Definition:
An array is a fixed-size, contiguous collection of elements of the same data type. Arrays offer direct indexed access, making operations like lookups very efficient. They differ from other collections such as ArrayList or LinkedList in that their size is determined at the time of creation and cannot be changed dynamically.

Syntax:
Arrays can be declared, initialized, and accessed in the following ways:

// Declaration and initialization
int[] numbers = new int[5];
numbers[0] = 10;

// Inline initialization
int[] values = {1, 2, 3, 4, 5};

// Accessing elements
System.out.println(numbers[0]);  // Output: 10

Multi-dimensional Arrays

Java supports multi-dimensional arrays such as 2D and 3D arrays.

2D Arrays:
A 2D array can be visualized as a table with rows and columns.

int[][] matrix = new int[3][3];

// Initializing elements
matrix[0][0] = 1;
matrix[1][2] = 5;

// Inline initialization
int[][] presetMatrix = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

Jagged Arrays:
Jagged arrays are multi-dimensional arrays where each row can have a different number of columns.

int[][] jagged = new int[3][];
jagged[0] = new int[2];
jagged[1] = new int[4];
jagged[2] = new int[3];

// Initializing jagged array elements
jagged[0][0] = 1;
jagged[1][2] = 5;

Array Operations

Insertion:
Adding an element at a specific position (note that arrays are fixed in size, so this often involves shifting elements).

int[] numbers = {1, 2, 4, 5};
int pos = 2;
int newValue = 3;

// Shifting elements
for (int i = numbers.length - 1; i > pos; i--) {
    numbers[i] = numbers[i - 1];
}
numbers[pos] = newValue;

Deletion:
Removing an element by shifting subsequent elements.

int[] numbers = {1, 2, 3, 4, 5};
int pos = 2;  // Delete the element at index 2

for (int i = pos; i < numbers.length - 1; i++) {
    numbers[i] = numbers[i + 1];
}

Traversal:
Looping through array elements.

int[] numbers = {1, 2, 3, 4, 5};

// Using a for loop
for (int i = 0; i < numbers.length; i++) {
    System.out.println(numbers[i]);
}

// Using an enhanced for loop
for (int num : numbers) {
    System.out.println(num);
}

Utility Methods (Arrays Class)

The java.util.Arrays class provides many useful static methods for array manipulation.

  • sort(): Sorts the array in ascending order.

      int[] arr = {3, 1, 4, 1, 5};
      Arrays.sort(arr);
      System.out.println(Arrays.toString(arr));  // Output: [1, 1, 3, 4, 5]
    
  • binarySearch(): Searches for a value in a sorted array.

      int[] arr = {1, 2, 3, 4, 5};
      int index = Arrays.binarySearch(arr, 3);
      System.out.println(index);  // Output: 2
    
  • copyOf() / copyOfRange(): Creates a copy of the array.

      int[] arr = {1, 2, 3};
      int[] copy = Arrays.copyOf(arr, 5);
      System.out.println(Arrays.toString(copy));  // Output: [1, 2, 3, 0, 0]
    
  • equals(): Compares two arrays for equality.

      int[] arr1 = {1, 2, 3};
      int[] arr2 = {1, 2, 3};
      System.out.println(Arrays.equals(arr1, arr2));  // Output: true
    
  • fill(): Fills the array with a specified value.

      int[] arr = new int[5];
      Arrays.fill(arr, 7);
      System.out.println(Arrays.toString(arr));  // Output: [7, 7, 7, 7, 7]
    
  • asList(): Converts an array to a List.

      String[] words = {"Java", "Python", "C++"};
      List<String> list = Arrays.asList(words);
      System.out.println(list);  // Output: [Java, Python, C++]
    
  • parallelSort(): Performs parallel sorting for large datasets.

      int[] arr = {5, 2, 9, 1, 3};
      Arrays.parallelSort(arr);
      System.out.println(Arrays.toString(arr));  // Output: [1, 2, 3, 5, 9]
    

Sorting Techniques

  • Built-in Sorting (Arrays.sort()):

    • Uses Dual-Pivot Quicksort for primitive types.

    • Uses TimSort for objects (a hybrid of MergeSort and InsertionSort).

    int[] arr = {3, 1, 4, 1, 5};
    Arrays.sort(arr);
    System.out.println(Arrays.toString(arr));  // Output: [1, 1, 3, 4, 5]
  • Parallel Sorting (Arrays.parallelSort()):

    • Suitable for large datasets, utilizes multiple threads.

    • Internally splits the array into segments and sorts them concurrently.

    int[] arr = {10, 2, 8, 6, 7, 3};
    Arrays.parallelSort(arr);
    System.out.println(Arrays.toString(arr));  // Output: [2, 3, 6, 7, 8, 10]

Best Practices for Arrays

  • When to Use Arrays:

    • When you need fixed-size collections with fast indexed access.
  • Performance Considerations:

    • Prefer arrays for primitive types to avoid the overhead of objects.

    • For dynamic sizing, consider using ArrayList.

Strings in Java

In Java, a String is an immutable sequence of characters. The immutability of Strings means that once a String object is created, its value cannot be changed. Instead, any modification creates a new String object. This property makes Strings safe for use in multithreaded environments and allows them to be cached or pooled, reducing memory overhead.

Why Immutability Matters:

  • Thread Safety: Since Strings are immutable, multiple threads can safely access the same String without synchronization.

  • Caching: The JVM maintains a string pool to reduce memory usage. If two String literals have the same value, they refer to the same object in the pool.

  • Security: Immutable Strings are useful in security contexts, such as storing passwords, because they cannot be modified.

String Pooling and Memory Management

When a String is created as a literal, it is automatically added to the string pool if it doesn't already exist. If a String with the same value exists, the new reference points to the existing String in the pool.

String s1 = "hello";       // Added to the string pool
String s2 = "hello";       // Points to the same object in the pool

System.out.println(s1 == s2);  // Output: true

When using the new keyword, a new String object is created on the heap, bypassing the string pool.

String s3 = new String("hello");  // New object on the heap
System.out.println(s1 == s3);     // Output: false

Creating Strings

There are two primary ways to create Strings in Java:

  1. String Literals (Added to the String Pool):

     String s1 = "hello";
    
  2. Using new String() (Creates a New Object):

     String s2 = new String("hello");
    

Common Methods in the String Class

Manipulation Methods
  • concat(): Concatenates the specified string to the end.

      String str = "Hello";
      String result = str.concat(", World!");
      System.out.println(result);  // Output: Hello, World!
    
  • toUpperCase(), toLowerCase(): Converts to upper or lower case.

      String str = "Java";
      System.out.println(str.toUpperCase());  // Output: JAVA
    
  • trim(): Removes leading and trailing spaces.

      String str = "  Hello  ";
      System.out.println(str.trim());  // Output: Hello
    
  • replace(), replaceAll(): Replaces characters or substrings.

      String str = "banana";
      System.out.println(str.replace('a', 'o'));  // Output: bonono
    
  • substring(): Extracts a substring.

      String str = "Programming";
      System.out.println(str.substring(0, 4));  // Output: Prog
    
Search Methods
  • indexOf(): Returns the index of the first occurrence of a character or substring.

      String str = "Hello, World!";
      System.out.println(str.indexOf('W'));  // Output: 7
    
  • lastIndexOf(): Returns the last occurrence.

      System.out.println(str.lastIndexOf('o'));  // Output: 8
    
  • contains(): Checks if the string contains a substring.

      System.out.println(str.contains("World"));  // Output: true
    
  • startsWith(), endsWith(): Checks the prefix or suffix.

      System.out.println(str.startsWith("Hello"));  // Output: true
    
Comparison Methods
  • equals(), equalsIgnoreCase(): Compares strings for equality.

      String str1 = "Java";
      String str2 = "java";
      System.out.println(str1.equals(str2));           // Output: false
      System.out.println(str1.equalsIgnoreCase(str2)); // Output: true
    
  • compareTo(): Lexicographically compares two strings.

      System.out.println(str1.compareTo(str2));  // Output: -32
    
  • matches(): Checks if the string matches a regex.

      System.out.println(str1.matches("[Jj]ava"));  // Output: true
    
Splitting and Joining
  • split(): Splits the string based on a delimiter.

      String data = "apple,banana,cherry";
      String[] fruits = data.split(",");
      for (String fruit : fruits) {
          System.out.println(fruit);
      }
      // Output: apple banana cherry
    
  • join(): Joins strings with a delimiter.

      String result = String.join("-", "2024", "05", "01");
      System.out.println(result);  // Output: 2024-05-01
    
Character Access
  • charAt(): Returns the character at a specified index.

      String str = "Java";
      System.out.println(str.charAt(1));  // Output: a
    
  • getChars(): Copies characters into a char array.

      char[] chars = new char[3];
      str.getChars(0, 3, chars, 0);
      System.out.println(chars);  // Output: Jav
    
Length and Empty Check
  • length(): Returns the length of the string.

      String str = "Hello";
      System.out.println(str.length());  // Output: 5
    
  • isEmpty(): Checks if the string is empty.

      System.out.println(str.isEmpty());  // Output: false
    

String Interning and Performance

Concept of intern() and String Pools

In Java, string interning is a technique where the JVM stores a single copy of each distinct string literal in a common pool, called the string pool or intern pool. This pool is part of the heap memory and helps optimize memory usage by reusing string literals instead of creating duplicate objects.

When a string is created using a string literal, it is automatically placed in the string pool:

String str1 = "hello";
String str2 = "hello";

System.out.println(str1 == str2);  // Output: true (same reference)

If a string is created using the new keyword, it is stored in the heap rather than the string pool:

String str3 = new String("hello");

System.out.println(str1 == str3);  // Output: false (different references)

You can force a string created with new to be placed in the string pool using the intern() method:

String str4 = new String("hello").intern();

System.out.println(str1 == str4);  // Output: true (both reference the string pool)

Memory Optimization with Interning

String interning reduces memory usage by ensuring that only one copy of each distinct string literal is kept in the pool. This is particularly beneficial when dealing with a large number of duplicate strings.

Example:

String name1 = "Java";
String name2 = new String("Java").intern();

System.out.println(name1 == name2);  // Output: true

In this example, after calling intern(), name2 references the same string pool object as name1.

Immutability and Concatenation Inefficiencies

Since Strings are immutable, any modification, such as concatenation or replacement, results in the creation of a new string object. This can lead to inefficiencies when performing repeated string modifications, especially within loops.

Example of Inefficient String Concatenation:

String result = "";
for (int i = 0; i < 5; i++) {
    result += i;  // Each iteration creates a new String object
}
System.out.println(result);  // Output: 01234

In the above example, each iteration creates a new string object, which can be very slow and memory-intensive for large loops. To avoid the inefficiencies of immutable strings, Java provides StringBuilder for mutable string manipulation. Unlike String, StringBuilder allows modifications without creating new objects each time.

StringBuilder and StringBuffer in Java

Java provides two classes for mutable sequences of characters: StringBuilder and StringBuffer. These classes are ideal for situations where repeated string modifications are required.

  • StringBuilder:
    A mutable sequence of characters that is not thread-safe. This means that multiple threads accessing a StringBuilder simultaneously can lead to unpredictable results. It is faster compared to StringBuffer due to the absence of synchronization overhead.

  • StringBuffer:
    Similar to StringBuilder, but it is thread-safe due to internal synchronization (locking mechanisms). This ensures that operations on StringBuffer are safe in a multi-threaded environment, but this synchronization makes it slower compared to StringBuilder.

Thread Safety in StringBuffer means that only one thread can modify the instance at a time, ensuring consistency of data when accessed concurrently by multiple threads.

Creating Instances

Creating a StringBuilder or StringBuffer is straightforward:

// Creating a StringBuilder
StringBuilder sb = new StringBuilder("Hello");

// Creating a StringBuffer
StringBuffer sbf = new StringBuffer("Hello");

You can also create instances with an initial capacity to avoid frequent resizing:

StringBuilder sb = new StringBuilder(50);

Important Methods in StringBuilder

Both StringBuilder and StringBuffer offer the same set of methods. Here's a list of commonly used methods with examples:

  • append(): Appends the specified string to the end.

      StringBuilder sb = new StringBuilder("Hello");
      sb.append(", World!");
      System.out.println(sb);  // Output: Hello, World!
    
  • insert(): Inserts a string at a specified position.

      sb.insert(5, " Java");
      System.out.println(sb);  // Output: Hello Java, World!
    
  • delete(): Deletes a sequence of characters between two indices.

      sb.delete(5, 10);
      System.out.println(sb);  // Output: Hello, World!
    
  • deleteCharAt(): Deletes the character at a specified index.

      sb.deleteCharAt(5);
      System.out.println(sb);  // Output: Hello World!
    
  • replace(): Replaces characters between two indices with a specified string.

      sb.replace(6, 11, "Java");
      System.out.println(sb);  // Output: Hello Java!
    
  • reverse(): Reverses the contents of the StringBuilder.

      sb.reverse();
      System.out.println(sb);  // Output: !avaJ olleH
    
  • capacity(): Returns the current capacity of the buffer.

      System.out.println(sb.capacity());
    
  • ensureCapacity(): Ensures the buffer has a minimum capacity.

      sb.ensureCapacity(50);
    
  • charAt(): Returns the character at the specified index.

      System.out.println(sb.charAt(0));  // Output: H
    
  • setCharAt(): Sets the character at the specified index.

      sb.setCharAt(0, 'h');
      System.out.println(sb);  // Output: hello Java!
    
  • toString(): Converts the StringBuilder to a String.

      String result = sb.toString();
      System.out.println(result);
    

Code Example

StringBuilder sb = new StringBuilder("Java");
sb.append(" Programming").insert(0, "Learn ");
System.out.println(sb.toString());  // Output: Learn Java Programming

Differences Between String, StringBuilder, and StringBuffer

FeatureStringStringBuilderStringBuffer
MutabilityImmutableMutableMutable
Thread SafetyThread-safeNot thread-safeThread-safe
PerformanceSlower for concatenationFasterSlower due to synchronization
Use CaseFixed or rarely modified stringsFrequent modifications in a single-threaded environmentFrequent modifications in a multi-threaded environment

Regular Expressions with Strings in Java

Introduction to Regular Expressions

Regular Expressions (often abbreviated as regex) are sequences of characters that define a search pattern. They are incredibly powerful tools used for tasks such as:

  • Validating input (e.g., email addresses, phone numbers).

  • Searching within text (finding patterns).

  • Replacing parts of text (e.g., removing special characters).

  • Splitting text based on patterns (e.g., breaking a sentence into words).

In Java, the String class provides methods like matches(), replaceAll(), and split() that allow you to work with regular expressions effectively.

Java supports regex through the java.util.regex package, but for basic use cases, the String class methods are sufficient.

Regex Basics

Before diving into methods, let’s understand some basic components of regex:

  • Literal Characters: Match themselves directly.
    Example: a, b, 1, A

  • Metacharacters: Special symbols with special meanings.
    Example: ., *, +, ?, ^, $

  • Character Classes: Match any character within a set.
    Example: [abc] matches a, b, or c.

  • Quantifiers: Specify how many times a pattern should occur.
    Example: a+ matches one or more as.

  • Anchors: Specify positions in the text.
    Example: ^ (start of string), $ (end of string).

Common Regex Patterns

Here are some commonly used regex patterns:

  • . – Matches any single character (except newline).

  • \d – Matches a digit (0-9).

  • \D – Matches a non-digit.

  • \w – Matches a word character (letters, digits, and underscores).

  • \W – Matches a non-word character.

  • \s – Matches a whitespace (space, tab, newline).

  • \S – Matches a non-whitespace.

  • ^ – Anchors the pattern to the beginning of the string.

  • $ – Anchors the pattern to the end of the string.

  • * – Matches zero or more occurrences of the preceding character.

  • + – Matches one or more occurrences of the preceding character.

  • ? – Matches zero or one occurrence of the preceding character.

  • \ – Escapes a special character.

Using matches()

The matches() method checks whether the entire string matches the given regex pattern. If the pattern matches the whole string, it returns true; otherwise, false.

Example 1: Validating an Email Address

String email = "user@example.com";
boolean isValid = email.matches("^[\\w.-]+@[a-zA-Z\\d.-]+\\.[a-zA-Z]{2,}$");

System.out.println(isValid);  // Output: true

Explanation:

  • ^ – Start of the string.

  • [\\w.-]+ – One or more word characters (\w), dots (.), or hyphens (-).

  • @ – The @ symbol.

  • [a-zA-Z\\d.-]+ – Domain name with letters, digits, dots, or hyphens.

  • \\. – A literal dot (.).

  • [a-zA-Z]{2,} – At least two letters for the top-level domain (e.g., com, org).

  • $ – End of the string.

Example 2: Checking for a Numeric String

String number = "12345";
boolean isNumeric = number.matches("\\d+");

System.out.println(isNumeric);  // Output: true

Explanation:

  • \\d+ – One or more digits (\d).

Using replaceAll()

The replaceAll() method replaces all substrings matching the regex pattern with the specified replacement string.

Example 1: Removing All Digits from a String

String text = "The year is 2024!";
String result = text.replaceAll("\\d", "");

System.out.println(result);  // Output: The year is !

Explanation:

  • \\d – Matches any digit.

  • "" – Replaces each digit with an empty string (removes it).

Example 2: Replacing Multiple Spaces with a Single Space

String text = "This   is   a   sentence.";
String result = text.replaceAll("\\s+", " ");

System.out.println(result);  // Output: This is a sentence.

Explanation:

  • \\s+ – One or more whitespace characters.

  • " " – Replaces them with a single space.

Example 3: Censoring Words

String text = "This is a bad word.";
String result = text.replaceAll("bad", "***");

System.out.println(result);  // Output: This is a *** word.

Using split()

The split() method splits the string based on the regex pattern and returns an array of substrings.

Example 1: Splitting a Sentence into Words

String sentence = "Java is fun to learn!";
String[] words = sentence.split("\\s");

for (String word : words) {
    System.out.println(word);
}

Output:

Java
is
fun
to
learn!

Explanation:

  • \\s – Matches any whitespace character (space, tab, etc.).

Example 2: Splitting by Commas

String data = "apple,banana,grape,orange";
String[] fruits = data.split(",");

for (String fruit : fruits) {
    System.out.println(fruit);
}

Output:

apple
banana
grape
orange

Combining Methods for Complex Tasks

You can combine regex methods to perform more complex operations.

Example: Extracting and Formatting Data

Suppose you have a string with mixed data and want to extract digits and replace them with formatted numbers.

String data = "Item123Price456";
String formatted = data.replaceAll("(\\D+)(\\d+)", "$1: $2 ");

System.out.println(formatted);  // Output: Item: 123 Price: 456

Explanation:

  • (\\D+) – Captures one or more non-digits.

  • (\\d+) – Captures one or more digits.

  • "$1: $2 " – Replaces the matched pattern with the first group ($1), followed by :, then the second group ($2), and a space.

Example Programs

1. Anagram Checker (String)

Problem

Check if two strings are anagrams. Two strings are anagrams if they contain the same characters in a different order.

Solution Approach

  1. Convert both strings to lowercase (to make it case-insensitive).

  2. Remove any whitespace if necessary.

  3. Convert both strings to character arrays and sort them.

  4. Compare the sorted arrays. If they are equal, the strings are anagrams.

Java Code

import java.util.Arrays;

public class AnagramChecker {
    public static boolean areAnagrams(String str1, String str2) {
        // Convert to lowercase and remove whitespace
        str1 = str1.toLowerCase().replaceAll("\\s+", "");
        str2 = str2.toLowerCase().replaceAll("\\s+", "");

        // Check if lengths are different
        if (str1.length() != str2.length()) {
            return false;
        }

        // Convert to char arrays and sort
        char[] arr1 = str1.toCharArray();
        char[] arr2 = str2.toCharArray();

        Arrays.sort(arr1);
        Arrays.sort(arr2);

        return Arrays.equals(arr1, arr2);
    }

    public static void main(String[] args) {
        String str1 = "Listen";
        String str2 = "Silent";

        System.out.println(areAnagrams(str1, str2));  // Output: true
    }
}

Explanation

  1. toLowerCase(): Ensures case insensitivity.

  2. replaceAll("\\s+", ""): Removes all whitespace.

  3. Arrays.sort(): Sorts the character arrays.

  4. Arrays.equals(): Compares the sorted arrays.


2. Reverse Words in a Sentence (StringBuilder)

Problem

Reverse the order of words in a given sentence.

Solution Approach

  1. Split the sentence by spaces into words.

  2. Use StringBuilder to reverse the words efficiently.

  3. Join the reversed words with spaces.

Java Code

public class ReverseWords {
    public static String reverseWords(String sentence) {
        String[] words = sentence.split("\\s+");
        StringBuilder reversed = new StringBuilder();

        for (int i = words.length - 1; i >= 0; i--) {
            reversed.append(words[i]).append(" ");
        }

        return reversed.toString().trim();
    }

    public static void main(String[] args) {
        String sentence = "Java is fun to learn";
        System.out.println(reverseWords(sentence));  // Output: learn to fun is Java
    }
}

Explanation

  1. split("\\s+"): Splits the sentence into words based on one or more spaces.

  2. StringBuilder: Efficient for appending strings.

  3. trim(): Removes any trailing spaces.


3. Palindrome Checker (String)

Problem

Check if a given string is a palindrome (reads the same forwards and backwards).

Solution Approach

  1. Normalize the string by removing non-alphanumeric characters and converting to lowercase.

  2. Use two pointers to compare characters from the beginning and end.

Java Code

public class PalindromeChecker {
    public static boolean isPalindrome(String str) {
        str = str.replaceAll("[^a-zA-Z0-9]", "").toLowerCase();

        int left = 0;
        int right = str.length() - 1;

        while (left < right) {
            if (str.charAt(left) != str.charAt(right)) {
                return false;
            }
            left++;
            right--;
        }

        return true;
    }

    public static void main(String[] args) {
        String str = "A man, a plan, a canal: Panama";
        System.out.println(isPalindrome(str));  // Output: true
    }
}

Explanation

  1. replaceAll("[^a-zA-Z0-9]", ""): Removes all non-alphanumeric characters.

  2. Two pointers: left starts from the beginning, right from the end.

  3. Compare characters: If any mismatch occurs, return false.


4. Remove Duplicates from a String (String)

Problem

Remove all duplicate characters from a string while maintaining the original order.

Solution Approach

  1. Use a LinkedHashSet to maintain insertion order and ensure uniqueness.

  2. Convert the set back to a string.

Java Code

import java.util.LinkedHashSet;

public class RemoveDuplicates {
    public static String removeDuplicates(String str) {
        LinkedHashSet<Character> set = new LinkedHashSet<>();
        for (char c : str.toCharArray()) {
            set.add(c);
        }

        StringBuilder result = new StringBuilder();
        for (char c : set) {
            result.append(c);
        }

        return result.toString();
    }

    public static void main(String[] args) {
        String str = "programming";
        System.out.println(removeDuplicates(str));  // Output: progamin
    }
}

Explanation

  1. LinkedHashSet: Maintains the order of first occurrences.

  2. StringBuilder: Efficiently constructs the final string without duplicates.


5. Find the First Non-Repeated Character (String)

Problem

Find the first character in a string that does not repeat.

Solution Approach

  1. Use a LinkedHashMap to store character counts while maintaining order.

  2. Iterate through the string to find the first character with a count of 1.

Java Code

import java.util.LinkedHashMap;
import java.util.Map;

public class FirstNonRepeatedChar {
    public static Character firstNonRepeatedChar(String str) {
        LinkedHashMap<Character, Integer> charCount = new LinkedHashMap<>();

        for (char c : str.toCharArray()) {
            charCount.put(c, charCount.getOrDefault(c, 0) + 1);
        }

        for (Map.Entry<Character, Integer> entry : charCount.entrySet()) {
            if (entry.getValue() == 1) {
                return entry.getKey();
            }
        }

        return null;  // No non-repeated character found
    }

    public static void main(String[] args) {
        String str = "swiss";
        System.out.println(firstNonRepeatedChar(str));  // Output: w
    }
}

Explanation

  1. LinkedHashMap: Stores characters and their counts while maintaining order.

  2. getOrDefault(c, 0) + 1: Increments the count for each character.


6. Count Vowels and Consonants (String)

Problem

Count the number of vowels and consonants in a given string.

Solution Approach

  1. Convert the string to lowercase.

  2. Iterate through each character and check if it's a vowel or a consonant.

  3. Count vowels and consonants separately.

Java Code

public class VowelConsonantCounter {
    public static void countVowelsAndConsonants(String str) {
        str = str.toLowerCase();
        int vowels = 0, consonants = 0;

        for (char c : str.toCharArray()) {
            if (c >= 'a' && c <= 'z') {
                if ("aeiou".indexOf(c) != -1) {
                    vowels++;
                } else {
                    consonants++;
                }
            }
        }

        System.out.println("Vowels: " + vowels);
        System.out.println("Consonants: " + consonants);
    }

    public static void main(String[] args) {
        String str = "Java Programming";
        countVowelsAndConsonants(str);  
        // Output: Vowels: 5, Consonants: 9
    }
}

Explanation

  1. Convert to lowercase to simplify comparison.

  2. Check if each character is between 'a' and 'z'.

  3. "aeiou".indexOf(c) != -1: Checks if the character is a vowel.


7. Log Parser with Regular Expressions (String + Regex)

Problem

Extract information such as timestamps, log levels, and messages from log entries.

Solution Approach

  1. Use a regular expression to match timestamps, log levels, and messages.

  2. Extract and display each part.

Java Code

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class LogParser {
    public static void parseLog(String log) {
        String regex = "(\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}) (\\w+): (.*)";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(log);

        if (matcher.matches()) {
            System.out.println("Timestamp: " + matcher.group(1));
            System.out.println("Log Level: " + matcher.group(2));
            System.out.println("Message: " + matcher.group(3));
        } else {
            System.out.println("Invalid log format");
        }
    }

    public static void main(String[] args) {
        String log = "2024-05-01 12:30:45 ERROR: Something went wrong";
        parseLog(log);
        // Output:
        // Timestamp: 2024-05-01 12:30:45
        // Log Level: ERROR
        // Message: Something went wrong
    }
}

Explanation

  1. Regex Breakdown:

    • (\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}): Matches the timestamp.

    • (\\w+): Matches the log level (e.g., ERROR, INFO).

    • (.*): Matches the message.

  2. matcher.group(n): Extracts matched groups.


8. Longest Substring Without Repeating Characters (String)

Problem

Find the longest substring without repeating characters.

Solution Approach

  1. Use a sliding window and a HashSet to track characters.

  2. Expand the window while characters are unique.

  3. Update the maximum length when duplicates are encountered.

Java Code

import java.util.HashSet;

public class LongestUniqueSubstring {
    public static int longestUniqueSubstring(String str) {
        int maxLength = 0;
        int left = 0;
        HashSet<Character> set = new HashSet<>();

        for (int right = 0; right < str.length(); right++) {
            while (set.contains(str.charAt(right))) {
                set.remove(str.charAt(left));
                left++;
            }
            set.add(str.charAt(right));
            maxLength = Math.max(maxLength, right - left + 1);
        }

        return maxLength;
    }

    public static void main(String[] args) {
        String str = "abcabcbb";
        System.out.println(longestUniqueSubstring(str));  // Output: 3 ("abc")
    }
}

Explanation

  1. Sliding Window: left and right track the window boundaries.

  2. HashSet: Tracks unique characters in the current window.

  3. Update maxLength each time a longer unique substring is found.


9. String Compression (String)

Problem

Compress a string by replacing consecutive repeated characters with the character followed by the count.

Solution Approach

  1. Iterate through the string.

  2. Count consecutive characters.

  3. Append the character and count to a StringBuilder.

Java Code

public class StringCompression {
    public static String compress(String str) {
        StringBuilder result = new StringBuilder();
        int count = 1;

        for (int i = 0; i < str.length(); i++) {
            if (i < str.length() - 1 && str.charAt(i) == str.charAt(i + 1)) {
                count++;
            } else {
                result.append(str.charAt(i));
                if (count > 1) {
                    result.append(count);
                }
                count = 1;
            }
        }

        return result.toString();
    }

    public static void main(String[] args) {
        String str = "aabbbcc";
        System.out.println(compress(str));  // Output: a2b3c2
    }
}

Explanation

  1. Count consecutive characters: Increment count if the next character is the same.

  2. Append character and count to StringBuilder.

  3. Reset count after each unique character.


10. Check for Balanced Parentheses (String + Stack)

Problem

Check if a string contains balanced parentheses {}, [], ().

Solution Approach

  1. Use a Stack to track opening brackets.

  2. For each closing bracket, check if it matches the top of the stack.

  3. At the end, the stack should be empty if balanced.

Java Code

import java.util.Stack;

public class BalancedParentheses {
    public static boolean isBalanced(String str) {
        Stack<Character> stack = new Stack<>();

        for (char c : str.toCharArray()) {
            if (c == '(' || c == '{' || c == '[') {
                stack.push(c);
            } else if (c == ')' && !stack.isEmpty() && stack.peek() == '(') {
                stack.pop();
            } else if (c == '}' && !stack.isEmpty() && stack.peek() == '{') {
                stack.pop();
            } else if (c == ']' && !stack.isEmpty() && stack.peek() == '[') {
                stack.pop();
            } else if (c == ')' || c == '}' || c == ']') {
                return false;
            }
        }

        return stack.isEmpty();
    }

    public static void main(String[] args) {
        String str = "([{}])";
        System.out.println(isBalanced(str));  // Output: true
    }
}

Explanation

  1. Push opening brackets onto the stack.

  2. Match closing brackets with the top of the stack.

  3. If mismatched or stack is not empty, return false..


11. Find the Missing Number in an Array (Array)

Problem

Given an array of numbers from 1 to n with one number missing, find the missing number.

Solution Approach

  1. Calculate the sum of numbers from 1 to n using the formula ( \text{sum} = n \times (n + 1) / 2 ).

  2. Subtract the sum of array elements from the expected sum to get the missing number.

Java Code

public class MissingNumber {
    public static int findMissingNumber(int[] arr, int n) {
        int expectedSum = n * (n + 1) / 2;
        int actualSum = 0;

        for (int num : arr) {
            actualSum += num;
        }

        return expectedSum - actualSum;
    }

    public static void main(String[] args) {
        int[] arr = {1, 2, 4, 5};
        int n = 5;
        System.out.println(findMissingNumber(arr, n));  // Output: 3
    }
}

Explanation

  1. Expected Sum: Calculate the sum of numbers from 1 to n.

  2. Actual Sum: Sum the elements in the array.

  3. Difference: The missing number is the difference between the expected and actual sums.


12. Find the Largest and Smallest Elements (Array)

Problem

Find the largest and smallest elements in an array.

Solution Approach

  1. Traverse the array and track the largest and smallest elements.

Java Code

public class LargestAndSmallest {
    public static void findLargestAndSmallest(int[] arr) {
        int largest = arr[0];
        int smallest = arr[0];

        for (int num : arr) {
            if (num > largest) {
                largest = num;
            }
            if (num < smallest) {
                smallest = num;
            }
        }

        System.out.println("Largest: " + largest);
        System.out.println("Smallest: " + smallest);
    }

    public static void main(String[] args) {
        int[] arr = {3, 1, 4, 1, 5, 9, 2};
        findLargestAndSmallest(arr);
        // Output: Largest: 9, Smallest: 1
    }
}

Explanation

  1. Initialize largest and smallest with the first element.

  2. Traverse the array and update largest and smallest accordingly.


13. Rotate an Array (Array)

Problem

Rotate an array to the right by a given number of steps.

Solution Approach

  1. Use the modulo operator to handle rotations greater than the array length.

  2. Reverse parts of the array to achieve rotation.

Java Code

import java.util.Arrays;

public class RotateArray {
    public static void rotate(int[] arr, int steps) {
        int n = arr.length;
        steps %= n;  // Handle steps greater than array length

        reverse(arr, 0, n - 1);
        reverse(arr, 0, steps - 1);
        reverse(arr, steps, n - 1);
    }

    private static void reverse(int[] arr, int start, int end) {
        while (start < end) {
            int temp = arr[start];
            arr[start] = arr[end];
            arr[end] = temp;
            start++;
            end--;
        }
    }

    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4, 5};
        rotate(arr, 2);
        System.out.println(Arrays.toString(arr));  // Output: [4, 5, 1, 2, 3]
    }
}

Explanation

  1. Reverse the entire array.

  2. Reverse the first steps elements.

  3. Reverse the remaining elements.


14. Merge Two Sorted Arrays (Array)

Problem

Merge two sorted arrays into one sorted array.

Solution Approach

  1. Use two pointers to traverse both arrays.

  2. Compare elements and add the smaller one to the result array.

Java Code

import java.util.Arrays;

public class MergeSortedArrays {
    public static int[] merge(int[] arr1, int[] arr2) {
        int[] result = new int[arr1.length + arr2.length];
        int i = 0, j = 0, k = 0;

        while (i < arr1.length && j < arr2.length) {
            if (arr1[i] <= arr2[j]) {
                result[k++] = arr1[i++];
            } else {
                result[k++] = arr2[j++];
            }
        }

        while (i < arr1.length) {
            result[k++] = arr1[i++];
        }

        while (j < arr2.length) {
            result[k++] = arr2[j++];
        }

        return result;
    }

    public static void main(String[] args) {
        int[] arr1 = {1, 3, 5};
        int[] arr2 = {2, 4, 6};
        int[] merged = merge(arr1, arr2);
        System.out.println(Arrays.toString(merged));  // Output: [1, 2, 3, 4, 5, 6]
    }
}

Explanation

  1. Two Pointers: i for arr1, j for arr2, and k for result.

  2. Merge elements in sorted order.

  3. Copy remaining elements from either array.

0
Subscribe to my newsletter

Read articles from Jyotiprakash Mishra directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Jyotiprakash Mishra
Jyotiprakash Mishra

I am Jyotiprakash, a deeply driven computer systems engineer, software developer, teacher, and philosopher. With a decade of professional experience, I have contributed to various cutting-edge software products in network security, mobile apps, and healthcare software at renowned companies like Oracle, Yahoo, and Epic. My academic journey has taken me to prestigious institutions such as the University of Wisconsin-Madison and BITS Pilani in India, where I consistently ranked among the top of my class. At my core, I am a computer enthusiast with a profound interest in understanding the intricacies of computer programming. My skills are not limited to application programming in Java; I have also delved deeply into computer hardware, learning about various architectures, low-level assembly programming, Linux kernel implementation, and writing device drivers. The contributions of Linus Torvalds, Ken Thompson, and Dennis Ritchie—who revolutionized the computer industry—inspire me. I believe that real contributions to computer science are made by mastering all levels of abstraction and understanding systems inside out. In addition to my professional pursuits, I am passionate about teaching and sharing knowledge. I have spent two years as a teaching assistant at UW Madison, where I taught complex concepts in operating systems, computer graphics, and data structures to both graduate and undergraduate students. Currently, I am an assistant professor at KIIT, Bhubaneswar, where I continue to teach computer science to undergraduate and graduate students. I am also working on writing a few free books on systems programming, as I believe in freely sharing knowledge to empower others.