LeetCode – 5 : Longest Palindromic Substring

Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.

Example 1:

Input: "babad"
Output: "bab"
Note: "aba" is also a valid answer.

Example 2:

Input: "cbbd"
Output: "bb"

class Solution {
    public String longestPalindrome(String s) {
        
    }
}

Problem Clarification

The longest palindromic substring must have characters that are adjacent in the original string.

Idea - 1

We can check each of the O(n^2) substrings and see if it is palindromic in O(n) time and keep track of the longest among them. Total time is O(n^3) and space is O(1).

class Solution {
    public String longestPalindrome(String s) {
        
        int n = s.length();
        if(n == 0)
        {
            return "";
        }
        
        int maxLength = 1;
        int longestStart = 0, longestEnd = 0;
        for(int i = 0; i < n-1; ++i)
        {
            for(int j = i+1; j < n; ++j)
            {
                if(palindromic(s, i, j) && j-i+1 > maxLength)
                {
                    maxLength = j-i+1;
                    longestStart = i;
                    longestEnd = j;
                }
            }
        }
        
        return s.substring(longestStart, longestEnd+1);
    }
    
    private boolean palindromic(String s, int begin, int end)
    {
        while(begin < end)
        {
            if(s.charAt(begin) != s.charAt(end))
            {
                return false;
            }
            
            ++begin;
            --end;
        }
        
        return true;
    }
}


Runtime: 1541 ms, faster than 5.00% of Java online submissions for Longest Palindromic Substring.
Memory Usage: 37.3 MB, less than 94.27% of Java online submissions for Longest Palindromic Substring.

Idea - 2

If c_0c_1\ \ldots\ c_{n-2}c_{n-1} is a palindrome, then it follows that c_0\ =\ c_{n-1} and c_1c_2\ldots c_{n-2} is a palindrome. So, if we find out which 2-length substrings are palindrome then finding if a 4-length substring is a palindrome take O(1) time. So, overall we need to find which of the O(n^2) substrings are palindrome and each can be checked in O(1) time making overall time complexity O(n^2) and space complexity O(n^2) as well.

class Solution {
    public String longestPalindrome(String s) {
        
        int n = s.length();
        if(n == 0)
        {
            return "";
        }
        
        boolean[][] dp = new boolean[n][n];
        
        // all 1-length substrings are palindromes
        for(int i = 0; i < n; ++i)
        {
            dp[i][i] = true;
        }
        
        int maxLength = 1;
        int longestBegin = 0, longestEnd = 0;
        for(int length = 2; length <= n; ++length)
        {
            int i = 0;
            while(i+length <= n)
            {
                int j = i+length-1;
                // i-th and j-th char match and these are the only chars or
                // the in between chars form palindrome
                if(s.charAt(i)==s.charAt(j) && ( i+1 > j-1 || dp[i+1][j-1] ))
                {
                    dp[i][j] = true;
                    
                    if(length > maxLength)
                    {
                        maxLength = length;
                        longestBegin = i;
                        longestEnd = j;
                    }
                }
                
                ++i;
            }
        }
        
        return s.substring(longestBegin, longestEnd+1);
    }
}


Runtime: 46 ms, faster than 40.03% of Java online submissions for Longest Palindromic Substring.
Memory Usage: 39.4 MB, less than 19.86% of Java online submissions for Longest Palindromic Substring.

Idea - 3

A palindrome can be checked starting at two ends and going inwards or starting at a center going outwards. The latter approach has the benefit that there are only O(n) centers, so we end up with an algorithm with time O(n^2) and space O(1). Centers are of two types: falls on a character, falls between two characters.
class Solution {
    
    class IndexPair
    {
        int i, j;
        public IndexPair(int i, int j)
        {
            this.i = i;
            this.j = j;
        }
        
        public int length()
        {
            return j-i+1;
        }
    }
    
    public String longestPalindrome(String s) {
        
        int n = s.length();
        if(n == 0)
        {
            return "";
        }
        
        int maxLength = 1;
        IndexPair sol = new IndexPair(0, 0);
        for(int center = 0; center < n; ++center)
        {
            IndexPair candidate = longestFromCenter(s, center);
            if(candidate.length() > sol.length())
            {
                sol = candidate;
            }
        }
        
        return s.substring(sol.i, sol.j+1);
    }
    
    private IndexPair longestFromCenter(String s, int center)
    {
        int n = s.length();
        
        // falls on a character
        int left1 = center-1;
        int right1 = center+1;
        while(left1 >= 0 && right1 < n && s.charAt(left1)==s.charAt(right1))
        {
            --left1;
            ++right1;
        }
        
        int length1 = right1-left1-1;
        
        // falls between two characters
        int left2 = center-1;
        int right2 = center;
        while(left2 >= 0 && right2 < n && s.charAt(left2)==s.charAt(right2))
        {
            --left2;
            ++right2;
        }
        
        int length2 = right2-left2-1;
        
        if(length1 > length2)
        {
            return new IndexPair(left1+1, right1-1);
        }
        else
        {
            return new IndexPair(left2+1, right2-1);
        }
    }
}

Runtime: 14 ms, faster than 64.48% of Java online submissions for Longest Palindromic Substring.
Memory Usage: 37.1 MB, less than 95.43% of Java online submissions for Longest Palindromic Substring.

LeetCode – 4 : Median of Two Sorted Arrays

There are two sorted arrays nums1 and nums2 of size m and n respectively.

Find the median of the two sorted arrays. The overall run time complexity should be O(log (m+n)).

You may assume nums1 and nums2 cannot be both empty.

Example 1:

nums1 = [1, 3]
nums2 = [2]

The median is 2.0

Example 2:

nums1 = [1, 2]
nums2 = [3, 4]

The median is (2 + 3)/2 = 2.5

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        
    }
}

Problem Clarification

Median of a single sorted array divides the array into two halves of equal length. If the number of elements in the array is even, the median is not an element of the original array, but falls between two consecutive elements.

Idea - 1

We can find median of two sorted array in O(m+n) time using the merge routine of mergesort. Suppose we have num1 = \langle 2,\ 5,\ 12 \rangle and num2 = \langle 1,\ 7\rangle. If we sorted the two arrays into one, we would have \langle 1,\ 2,\ 5,\ 7,\ 11 \rangle. Here, total number of elements is 5 and the middle element aka \frac{5}{2}+1\ =\ 3 or the 3rd element (5) in the sorted order is the median. On the other hand, if we had num1 = \langle 2,\ 5 \rangle and num2 = \langle 1,\ 7\rangle, then in the sorted array we would have \langle  1,\ 2,\ 5,\ 7 \rangle and the median would be the average of the middle element or \frac{4}{2}+1 = 3 rd element and the previous element. So, the median is \frac{2+5}{2}\ =\ 3.5. Clearly, if we take \frac{m+n}{2}+1 steps in the merge process and keep track of the currently selected element and the previous as well, we would have all information to compute median.

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int m = nums1.length;
        int n = nums2.length;
        
        int total = m+n;
        int nsteps = total/2+1;
        int prev = 0, curr = 0;
        int i = 0, j = 0;
        while(nsteps > 0)
        {
            prev = curr;
            if(i >= m)
            {
                curr = nums2[j++];    
            }
            else if(j >= n)
            {
                curr = nums1[i++];
            }
            else if(nums1[i] < nums2[j])
            {
                curr = nums1[i++];
            }
            else
            {
                curr = nums2[j++];
            }
            
            --nsteps;
        }
        
        if(total%2==1)
        {
            return curr;
        }
        else
        {
            return (prev+curr)/2.0;
        }
    }
}


Runtime: 2 ms, faster than 100.00% of Java online submissions for Median of Two Sorted Arrays.
Memory Usage: 47.2 MB, less than 91.63% of Java online submissions for Median of Two Sorted Arrays.

Idea - 2

Like the single array case, the median should divide the elements in the two sorted arrays into equal halves.
So, if we pick two indices i and j in the two sorted arrays such that i+j = \frac{m+n+1}{2} then we at least have the correct divide in terms of number of elements because i is the number of elements left of i-th index of the first array and j is the number of elements left of j-th index of second array, thus the left half of the divide has \frac{m+n+1}{2} elements. To make sure this is the median divide, we need to make sure \max(\textrm{num1}[i-1],\ \textrm{nums2}[j-1])\ \le \min(\textrm{nums1}[i],\ \textrm{nums2}[j]), therefore the left half contains smaller elements and the right half contains bigger elements. For example, below all three division are valid in terms of size of the two halves, but only 3rd is the median divide.
In general, if we have a divide like below (A[m] and B[n] are fictitious to allow divides that may not have right half):
We want max{A[i-1], B[j-1]} ≤ min{A[i], B[j]}. Note, if i==m, we only have A[i-1] no A[i]. Likewise for i==0 we only have A[i] no A[i-1], for j==0 we only have B[j] no B[j-1] and for j==n we only have B[j-1] no B[j]. If we have i > imin and A[i-1] > B[j] then it follows that max{A[i-1], B[j-1]}=A[j-1] > min{A[i], B[j]}=B[j]. So in this case i is too big, we can throw away the part [i, imax] and go left during binary search. On the other hand, if we have i < imax and A[i] < B[j-1] then it follows that max{A[i-1], B[j-1]} = B[i-1] > min{A[i], B[j]} = A[i]. So in this case i is too small, we can throw away [imin, i] and go right during binary search. Note, there is a reciprocal relationship between i and j, if i is big j gets smaller and if i is small j gets bigger. As a result, i < imax implies j > 0 which we need in the check A[i] < B[j-1]. Similarly i > imin implies j < n which we need in the check A[i-1] > B[j]. We can show more rigorously using the facts: (i) m ≤ n (ii) i is in [0, m] (iii) j = \frac{m+n+1}{2}-i. We can actually iterate on [imin, imax] (intially imin=0 and imax=m) in binary search fashion and check if the divide is the median divide. Choosing nums1 as the shorter array gives us the desired time complexity of O(\log(\min(m, n))).

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int m = nums1.length;
        int n = nums2.length;
        
        // We make sure nums1 is the shorter array
        // This ensures:
        // (1) i > imin => j < n
        // (2) i < imax => j > 0
        if(m > n)
        {
            int[] tmp = nums1;
            nums1 = nums2;
            nums2 = tmp;
            
            int bin = m;
            m = n;
            n = bin;
        }
        
        int imin = 0, imax = m;
        int halflen = (m+n+1)/2;
        
        // this is regular binary search on [imin, imax]
        while(imin <= imax)
        {
            int i = imin+(imax-imin)/2;
            int j = halflen-i;
            
            if(i > imin && nums1[i-1] > nums2[j]) // go left
            {
                imax = i-1;
            }
            else if(i < imax && nums1[i] < nums2[j-1]) // go right
            {
                imin = i+1;
            }
            else
            {
                int leftmax = 0;
                if(i==0)
                {
                    leftmax = nums2[j-1];
                }
                else if(j == 0)
                {
                    leftmax = nums1[i-1];
                }
                else
                {
                    leftmax = Math.max( nums1[i-1], nums2[j-1] );
                }
                
                if((m+n)%2==1)
                {
                    return leftmax;
                }
                
                int rightmin = 0;
                if(i==m)
                {
                    rightmin = nums2[j];
                }
                else if(j==n)
                {
                    rightmin = nums1[i];
                }
                else
                {
                    rightmin = Math.min(nums1[i], nums2[j]);
                }
                
                return (leftmax+rightmin)/2.0;
            }
        }
        
        return Double.NEGATIVE_INFINITY;
    }
}


Runtime: 3 ms, faster than 86.92% of Java online submissions for Median of Two Sorted Arrays.
Memory Usage: 48.1 MB, less than 90.12% of Java online submissions for Median of Two Sorted Arrays.

LeetCode – 200 : Number of Islands

Given a 2d grid map of '1's (land) and '0's (water), count the number of islands. An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically. You may assume all four edges of the grid are all surrounded by water.

Example 1:

Input: 
11110
11010
11000
00000
Output: 1

Example 2:

Input: 
11000
11000
00100
00011
Output: 3

Understand Problem

We need to traverse through adjacent 1’s which would form one island.

Idea – 1

The grid can be thought of as an undirected graph and the islands are connected components. A dfs from a vertex in a connected component stays within the component and traverses all nodes in the component. So, we would be doing dfs’s from each unvisited island node (grid cell with ‘1’) and keep count. We would be traversing each edge at most once and there are 4\ \cdot\ m\ \cdot\ n edges, so time complexity is O(m\cdot n). We use recursion, so worst case stack space is O(m\cdot n) as well (which could happen say if there is a single island).

class Solution {
    public int numIslands(char[][] grid) {
        int m = grid.length;
        if(m==0)
        {
            return 0;
        }
        
        int n = grid[0].length;
        
        boolean[][] visited = new boolean[m][n];
        int islandCount = 0;
        for(int row = 0; row < m; ++row)
        {
            for(int col = 0; col < n; ++col)
            {
                if(grid[row][col]=='1' && !visited[row][col])
                {
                    ++islandCount;
                    dfs(grid, row, col, visited);
                }
            }
        }
        
        return islandCount;
    }
    
    private void dfs(char[][] grid, int row, int col, boolean[][] visited)
    {
        int m = grid.length;
        int n = grid[0].length;
        
        if(row < 0 || row >= m || col < 0 || col >= n || grid[row][col] == '0' || visited[row][col])
        {
            return;
        }
        
        visited[row][col] = true;
        
        // visit upwards
        dfs(grid, row-1, col, visited);
        
        // visit rightwards
        dfs(grid, row, col+1, visited);
        
        // visit downwards
        dfs(grid, row+1, col, visited);
        
        // visit leftwards
        dfs(grid, row, col-1, visited);
    }
}

Runtime: 1 ms, faster than 100.00% of Java online submissions for Number of Islands.

Memory Usage: 40.8 MB, less than 40.82% of Java online submissions forNumber of Islands.

Idea - 2

If we are allowed to modify the grid, we could used it to keep track of visited and save some space. The worst case time and space are still O(m\cdot n).

class Solution {
    public int numIslands(char[][] grid) {
        int m = grid.length;
        if(m==0)
        {
            return 0;
        }
        
        int n = grid[0].length;
        
        int islandCount = 0;
        for(int row = 0; row < m; ++row)
        {
            for(int col = 0; col < n; ++col)
            {
                if(grid[row][col]=='1')
                {
                    ++islandCount;
                    dfs(grid, row, col);
                }
            }
        }
        
        return islandCount;
    }
    
    private void dfs(char[][] grid, int row, int col)
    {
        int m = grid.length;
        int n = grid[0].length;
        
        if(row < 0 || row >= m || col < 0 || col >= n || grid[row][col] != '1')
        {
            return;
        }
        
        grid[row][col] = 'X';
        
        // visit upwards
        dfs(grid, row-1, col);
        
        // visit rightwards
        dfs(grid, row, col+1);
        
        // visit downwards
        dfs(grid, row+1, col);
        
        // visit leftwards
        dfs(grid, row, col-1);
    }
}

Runtime: 1 ms, faster than 100.00% of Java online submissions for Number of Islands.

Memory Usage: 42.3 MB, less than 5.02% of Java online submissions forNumber of Islands.

Interestingly the memory usage has gone up, probably because I used 'X' to mark visited nodes. Using '2' to mark visited node the result is better:

Runtime: 1 ms, faster than 100.00% of Java online submissions for Number of Islands.

Memory Usage: 41 MB, less than 28.87% of Java online submissions forNumber of Islands.

Idea - 3

If stack space is limited, we can avoid recursion and use a stack object. The time and space complexity do not change.

class Solution {
    public int numIslands(char[][] grid) {
        int m = grid.length;
        if(m==0)
        {
            return 0;
        }
        
        int n = grid[0].length;
        
        boolean[][] visited = new boolean[m][n];
        int islandCount = 0;
        for(int row = 0; row < m; ++row)
        {
            for(int col = 0; col < n; ++col)
            {
                if(grid[row][col]=='1' && !visited[row][col])
                {
                    ++islandCount;
                    
                    Deque<int[]> stack = new ArrayDeque<>();
                    int[] root = {row, col};
                    stack.addLast(root);
                    while(!stack.isEmpty())
                    {
                        int[] node = stack.removeLast();
                        int x = node[0];
                        int y = node[1];
                        if(x >= 0 && x < m && y >= 0 && y < n && grid[x][y]=='1' && !visited[x][y])
                        {
                            visited[x][y] = true;
                            
                            int[] up = {x-1, y};
                            stack.addLast(up);
                            
                            int[] right = {x, y+1};
                            stack.addLast(right);
                            
                            int[] down = {x+1, y};
                            stack.addLast(down);
                            
                            int[] left = {x, y-1};
                            stack.addLast(left);
                        }
                    }
                }
            }
        }
        
        return islandCount;
    }
}

Runtime: 8 ms, faster than 9.89% of Java online submissions for Number of Islands.

Memory Usage: 42 MB, less than 5.02% of Java online submissions for Number of Islands.

Idea - 4

Using BFS we could reduce worst case space to O\left( \min\{m, n\} \right)

class Solution {
    public int numIslands(char[][] grid) {
        int m = grid.length;
        if(m==0)
        {
            return 0;
        }
        
        int n = grid[0].length;
        
        int islandCount = 0;
        for(int row = 0; row < m; ++row)
        {
            for(int col = 0; col < n; ++col)
            {
                if(grid[row][col]=='1')
                {
                    ++islandCount;
                    
                    Deque<int[]> queue = new ArrayDeque<>();
                    int[] root = {row, col};
                    queue.addLast(root);
                    
                    while(!queue.isEmpty())
                    {
                        int[] node = queue.removeFirst();
                        int x = node[0];
                        int y = node[1];
                        
                        if(x >=0 && x < m && y >= 0 && y < n && grid[x][y] == '1')
                        {
                            grid[x][y] = '2';
                            
                            int[] up = {x-1, y};
                            queue.addLast(up);
                            
                            int[] right = {x, y+1};
                            queue.addLast(right);
                            
                            int[] down = {x+1, y};
                            queue.addLast(down);
                            
                            int[] left = {x, y-1};
                            queue.addLast(left);
                        }
                    }
                }
            }
        }
        
        return islandCount;
    }
}

RuRuntime: 7 ms, faster than 13.47% of Java online submissions for Number of Islands.

Memory Usage: 41.9 MB, less than 5.02% of Java online submissions forNumber of Islands.

Idea - 5

Union-Find can be leveraged to find the number of connected components aka islands, however the graph is given as a whole in the beginning, so Union-Find is not better but may be a little worse than DFS/BFS. We would prefer Union-Find over DFS/BFS if the graph was not given as a whole, instead we get a stream of edges aka online.

LeetCode – 146 : LRU Cache

Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get and put.

get(key) – Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.
put(key, value) – Set or insert the value if the key is not already present. When the cache reached its capacity, it should invalidate the least recently used item before inserting a new item.

Follow up:
Could you do both operations in O(1) time complexity?

Example:

LRUCache cache = new LRUCache( 2 /* capacity */ ); 
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // returns 1
cache.put(3, 3); // evicts key 2
cache.get(2); // returns -1 (not found)
cache.put(4, 4); // evicts key 1
cache.get(1); // returns -1 (not found)
cache.get(3); // returns 3
cache.get(4); // returns 4

Understand Problem

LRUCache is like a hash table except we need to keep track of the least recently used key so that when we hit the capacity we can evict this item. Here used means update (put), get, or create (put).

Idea – 1

We use a HashMap and a LinkedList (doubly linked), the linkedlist would be used to keep track of the oldest key. Whenever we get a key we move the node for that key in the linkedlist to the front. Likewise when we update (put for existing key) a key, we move the corresponding node to the front. We insert to the front always. In this way, the tail node always is the oldest and ready to be evicted. All operations are O(1).
class LRUCache {
    
    class Node
    {
        int key;
        int val;
        
        public Node(int key, int val)
        {
            this.key = key;
            this.val = val;
        }
    }
    
    private int capacity;
    private HashMap<Integer, Node> map;
    private LinkedList<Node> list;

    public LRUCache(int capacity) {
        this.capacity = capacity;
        map = new HashMap<>();
        list = new LinkedList<>();
    }
    
    public int get(int key) {
        Node node = map.getOrDefault(key, null);
        
        if(node==null)
        {
            return -1;
        }
        
        list.remove(node);
        list.addFirst(node);
        
        return node.val;
    }
    
    public void put(int key, int value) {
        if(map.containsKey(key))
        {
            Node node = map.get(key);
            node.val = value;
            list.remove(node);
            list.addFirst(node);
        }
        else
        {
            if(map.size()==this.capacity)
            {
                Node last = list.removeLast();
                map.remove(last.key);
            }
            
            Node node = new Node(key, value);
            map.put(key, node);
            list.addFirst(node);
        }
    }
}

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache obj = new LRUCache(capacity);
 * int param_1 = obj.get(key);
 * obj.put(key,value);
 */
Runtime: 220 ms, faster than 9.21% of Java online submissions for LRU Cache. Memory Usage: 67.6 MB, less than 6.42% of Java online submissions for LRU Cache.

Idea – 2

We extend Java’s built-in LinkedHashMap. We need to override removeEldestEntry method. The default eviction is based on insertion order so we need to switch to access order through passing in true in the constructor.
class LRUCache extends LinkedHashMap<Integer, Integer> {
    
    private int capacity;
    
    public LRUCache(int capacity) {
        super(capacity, 0.75f, true);
        this.capacity = capacity;
    }
    
    public int get(int key) {
        Integer val = super.get(key);
        return val==null ? -1 : val;
    }
    
    public void put(int key, int value) {
        super.put(key, value);
    }
    
    public boolean removeEldestEntry(Map.Entry<Integer, Integer> entry)
    {
        return super.size() > this.capacity;
    }
}
Runtime: 56 ms, faster than 99.73% of Java online submissions for LRU Cache. Memory Usage: 51.4 MB, less than 95.53% of Java online submissions for LRU Cache.

LeetCode – 2 : Add Two Numbers

You are given two non-empty linked lists representing two non-negative integers. The digits are stored in reverse order and each of their nodes contain a single digit. Add the two numbers and return it as a linked list.

You may assume the two numbers do not contain any leading zero, except the number 0 itself.

Example:

Input: (2 -> 4 -> 3) + (5 -> 6 -> 4)
Output: 7 -> 0 -> 8
Explanation: 342 + 465 = 807.

Idea – 1

We add the lists digit by digit in O(n) time.
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode dummy = new ListNode(-1);
        ListNode t = dummy;
        int carry = 0;
        while(l1 ≠ null || l2 ≠ null || carry > 0)
        {
            int x = l1==null ? 0 : l1.val;
            int y = l2==null ? 0 : l2.val;
            int sum = x+y+carry;
            ListNode node = new ListNode(sum%10);
            t.next = node;
            carry = sum/10;
            
            t = t.next;
            if(l1 ≠ null)
            {
                l1 = l1.next;
            }
            
            if(l2 ≠ null)
            {
                l2 = l2.next;
            }
        }
        
        return dummy.next;
    }
}
Runtime: 2 ms, faster than 97.82% of Java online submissions for Add Two Numbers. Memory Usage: 40.7 MB, less than 73.62% of Java online submissions for Add Two Numbers.

LeetCode-1: Two Sum

Given an array of integers, return indices of the two numbers such that they add up to a specific target.

You may assume that each input would have exactly one solution, and you may not use the same element twice.

Example:

Given nums = [2, 7, 11, 15], target = 9,

Because nums[0] + nums[1] = 2 + 7 = 9,
return [0, 1].

Idea – 1

Try each of the O(n^2) pairs and see if the sum equals target.
class Solution {
    public int[] twoSum(int[] nums, int target) {
        int[] sol = new int[2];
        for(int i = 0; i < nums.length-1; ++i)
        {
            for(int j = i+1; j < nums.length; ++j)
            {
                int sum = nums[i]+nums[j];
                if(sum==target)
                {
                    sol[0] = i;
                    sol[1] = j;
                    
                    return sol;
                }
            }
        }
        
        return sol;
    }
}
Runtime: 22 ms, faster than 29.76% of Java online submissions for Two Sum. Memory Usage: 38.4 MB, less than 52.20% of Java online submissions for Two Sum.

Idea – 2

For i-th element x, find target-x and see if we have already seen it in [0, i-1]. For efficient lookup we use a HashMap that stores the last index where an element was seen. Time and space O(n). Overflow/underflow does not seem to create an issue in Java probably because the addition and subtraction behaves like being on a circle.
		int target = Integer.MIN_VALUE;
		int x = 1000;
		int y = target-x;
		int z = x+y;
		
		System.out.println( String.format( "x=%d, y=%d, z=%d", x, y, z ) );
		
		target = Integer.MAX_VALUE;
		x = -1000;
		y = target-x;
		z = x+y;
		
		System.out.println( String.format( "x=%d, y=%d, z=%d", x, y, z ) );

x=1000, y=2147482648, z=-2147483648

x=-1000, y=-2147482649, z=2147483647

class Solution {
    public int[] twoSum(int[] nums, int target) {
        int[] sol = new int[2];
        HashMap<Integer, Integer> map = new HashMap();
        for(int i = 0; i < nums.length; ++i)
        {
            int delta = target-nums[i];
            if(map.containsKey(delta))
            {
                sol[0] = map.get(delta);
                sol[1] = i;
                break;
            }
            
            map.put(nums[i], i);
        }
        
        return sol;
    }
}
Runtime: 2 ms, faster than 99.73% of Java online submissions for Two Sum. Memory Usage: 38 MB, less than 84.34% of Java online submissions for Two Sum.