Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add JUnit for AnagramsInString, BuySellStocks, CountAndSay, MajorityE… #55

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 48 additions & 66 deletions src/main/java/com/leetcode/graphs/GraphValidTree.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,95 +28,77 @@
* @since 2019-08-05
*/
public class GraphValidTree {

/**
*
* @param n
* @param edges
* @return
* Checks if the given edges form a valid tree using BFS.
*/
public static boolean isValidTree(int n, int[][] edges) {
List<List<Integer>> adjacencyList = new ArrayList<>(n);
List<List<Integer>> adjacencyList = buildAdjacencyList(n, edges);
return isTreeBFS(n, adjacencyList);
}

/**
* Builds the adjacency list from the given edges.
*/
private static List<List<Integer>> buildAdjacencyList(int n, int[][] edges) {
List<List<Integer>> adjacencyList = new ArrayList<>(n);
for (int i = 0; i < n; i++) {
adjacencyList.add(new ArrayList<>());
}

for (int i = 0; i < edges.length; i++) {
adjacencyList.get(edges[i][0]).add(edges[i][1]);
}

boolean[] visited = new boolean[n];

if (hasCycle(adjacencyList, 0, -1, visited)) {
return false;
}

for (int i = 0; i < n; i++) {
if (!visited[i]) {
return false;
}
for (int[] edge : edges) {
adjacencyList.get(edge[0]).add(edge[1]);
adjacencyList.get(edge[1]).add(edge[0]); // Since the graph is undirected
}

return true;
return adjacencyList;
}

private static boolean hasCycle(List<List<Integer>> adjacencyList, int node1, int exclude, boolean[] visited) {
visited[node1] = true;

for (int i = 0; i < adjacencyList.get(node1).size(); i++) {
int node2 = adjacencyList.get(node1).get(i);

if ((visited[node2] && exclude != node2) || (!visited[node2] && hasCycle(adjacencyList, node2, node1, visited))) {
return true;
/**
* Uses BFS to check for cycles and disconnected components.
*/
private static boolean isTreeBFS(int n, List<List<Integer>> adjacencyList) {
Set<Integer> visited = new HashSet<>();
Queue<Integer> queue = new LinkedList<>();
queue.offer(0);
visited.add(0);

while (!queue.isEmpty()) {
int node = queue.poll();
for (int neighbor : adjacencyList.get(node)) {
if (!visited.add(neighbor)) { // if 'add' returns false, 'neighbor' is already visited
return false;
}
queue.offer(neighbor);
}
}

return false;
return visited.size() == n;
}


/**
* Union-find algorithm: We keep all connected nodes in one set in the union operation and in find operation we
* check whether two nodes belong to the same set. If yes then there's a cycle and if not then no cycle.
*
* Good articles on union-find:
* - https://www.hackerearth.com/practice/notes/disjoint-set-union-union-find/
* - https://www.youtube.com/watch?v=wU6udHRIkcc
*
* @param n
* @param edges
* @return
* Checks if the given edges form a valid tree using the Union-Find algorithm.
*/
public static boolean isValidTreeUsingUnionFind(int n, int[][] edges) {
int[] roots = new int[n];
int[] parent = new int[n];
Arrays.fill(parent, -1);

for (int i = 0; i < n; i++) {
roots[i] = i;
}
for (int[] edge : edges) {
int x = find(parent, edge[0]);
int y = find(parent, edge[1]);

for (int i = 0; i < edges.length; i++) {
// find operation
if (roots[edges[i][0]] == roots[edges[i][1]]) {
return false;
}
// union operation
roots[edges[i][1]] = findRoot(roots, roots[edges[i][0]]); // note: we can optimize this even further by
// considering size of each side and then join the side with smaller size to the one with a larger size (weighted union).
// We can use another array called size to keep count of the size or we can use the same root array with
// negative values, i.e, negative resembles that the node is pointing to itself and the number will represent
// the size. For example, roots = [-2, -1, -1, 0] means that node 3 is pointing to node 0 and node 0 is pointing
// to itself and is has 2 nodes under it including itself.
if (x == y) return false; // x and y are in the same set

// Union operation
parent[y] = x;
}

return edges.length == n - 1;
return edges.length == n - 1; // Tree should have exactly n-1 edges
}

private static int findRoot(int[] roots, int node) {
while (roots[node] != node) {
node = roots[node];
}
return node;
/**
* Finds the root of the node 'i' using path compression.
*/
private static int find(int[] parent, int i) {
if (parent[i] == -1) return i;
return find(parent, parent[i]);
}

public static void main(String[] args) {
Expand Down
103 changes: 39 additions & 64 deletions src/main/java/com/leetcode/graphs/WordLadder.java
Original file line number Diff line number Diff line change
@@ -1,119 +1,94 @@
package com.leetcode.graphs;


import javafx.util.Pair;

import java.util.*;

import static org.junit.jupiter.api.Assertions.assertEquals;

/**
* Level: Medium
* Link: https://leetcode.com/problems/word-ladder/
* Description:
* Given two words (beginWord and endWord), and a dictionary's word list, find the length of shortest transformation
* sequence from beginWord to endWord, such that:
* <p>
*
* Only one letter can be changed at a time. Each transformed word must exist in the word list. Note that beginWord
* is not a transformed word.
* <p>
*
* Note:
* - Return 0 if there is no such transformation sequence.
* - All words have the same length.
* - All words contain only lowercase alphabetic characters.
* - You may assume no duplicates in the word list.
* - You may assume beginWord and endWord are non-empty and are not the same.
* <p>
*
* Example 1:
* Input:
* beginWord = "hit",
* endWord = "cog",
* wordList = ["hot","dot","dog","lot","log","cog"]
* <p>
*
* Output: 5
* <p>
*
* Explanation: As one shortest transformation is "hit" -> "hot" -> "dot" -> "dog" -> "cog",
* return its length 5.
* <p>
*
* Example 2:
* Input:
* beginWord = "hit"
* endWord = "cog"
* wordList = ["hot","dot","dog","lot","log"]
* <p>
*
* Output: 0
* <p>
* Explanation: The endWord "cog" is not in wordList, therefore no possible transformation.
*
* @author rampatra
* @since 2019-08-15
* Explanation: The endWord "cog" is not in wordList, therefore no possible transformation.
*/
public class WordLadder {

/**
* Runtime: <a href="https://leetcode.com/submissions/detail/251960230/">79 ms</a>.
*
* @param beginWord
* @param endWord
* @param wordList
* @return
*/
private static final char WILDCARD_CHAR = '*';

public static int ladderLength(String beginWord, String endWord, List<String> wordList) {
int L = beginWord.length();
Map<String, Set<String>> transformedToOriginalWordMap = new HashMap<>();
Queue<Pair<String, Integer>> queue = new LinkedList<>();
Map<String, Set<String>> allComboDict = preprocessWords(wordList);
return performBFS(beginWord, endWord, allComboDict);
}

private static Map<String, Set<String>> preprocessWords(List<String> wordList) {
Map<String, Set<String>> allComboDict = new HashMap<>();
int L = wordList.get(0).length();
wordList.forEach(word -> {
String transformedWord;
for (int i = 0; i < L; i++) {
transformedWord = word.substring(0, i) + "*" + word.substring(i + 1, L);
transformedToOriginalWordMap.putIfAbsent(transformedWord, new HashSet<>());
transformedToOriginalWordMap.get(transformedWord).add(word);
}
}
);
for (int i = 0; i < L; i++) {
String newWord = word.substring(0, i) + WILDCARD_CHAR + word.substring(i + 1, L);
allComboDict.computeIfAbsent(newWord, k -> new HashSet<>()).add(word);
}
});
return allComboDict;
}

Set<String> visited = new HashSet<>();
private static int performBFS(String beginWord, String endWord, Map<String, Set<String>> allComboDict) {
Queue<Pair<String, Integer>> queue = new LinkedList<>();
queue.add(new Pair<>(beginWord, 1));

Set<String> visited = new HashSet<>();
visited.add(beginWord);

while (!queue.isEmpty()) {
Pair<String, Integer> currPair = queue.poll();
String word = currPair.getKey();
Integer level = currPair.getValue();

if (word.equals(endWord)) {
return level;
}

String transformedWord;
for (int i = 0; i < L; i++) {
transformedWord = word.substring(0, i) + "*" + word.substring(i + 1, L);

for (String originalWord : transformedToOriginalWordMap.getOrDefault(transformedWord, Collections.emptySet())) {
if (!visited.contains(originalWord)) {
queue.add(new Pair<>(originalWord, level + 1));
visited.add(originalWord);
Pair<String, Integer> node = queue.remove();
String word = node.getKey();
int level = node.getValue();
for (int i = 0; i < word.length(); i++) {
String newWord = word.substring(0, i) + WILDCARD_CHAR + word.substring(i + 1);
for (String adjacentWord : allComboDict.getOrDefault(newWord, new HashSet<>())) {
if (adjacentWord.equals(endWord)) {
return level + 1;
}
if (!visited.contains(adjacentWord)) {
visited.add(adjacentWord);
queue.add(new Pair<>(adjacentWord, level + 1));
}
}
}
}

return 0;
}

/**
* TODO: Optimized both end BFS solution
*
* @param beginWord
* @param endWord
* @param wordList
* @return
*/
public static int ladderLengthOptimized(String beginWord, String endWord, List<String> wordList) {
return -1;
}

public static void main(String[] args) {
assertEquals(5, ladderLength("hit", "cog", Arrays.asList("hot", "dot", "dog", "lot", "log", "cog")));
assertEquals(0, ladderLength("hit", "cog", Arrays.asList("hot", "dot", "dog", "lot", "log")));
Expand Down
81 changes: 81 additions & 0 deletions src/test/AnagramsInStringTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package com.leetcode.arrays;

import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.List;

public class AnagramsInStringTest {

@Test
public void testFindAllAnagramsInTextWithMultipleAnagrams() {
String text = "cbaebabacd";
String pattern = "abc";
List<Integer> expected = Arrays.asList(0, 6);
assertEquals(expected, AnagramsInString.findAllAnagramsInText(text, pattern),
"Phải trả về các vị trí bắt đầu của tất cả các anagram của chuỗi mẫu trong văn bản.");
}

@Test
public void testFindAllAnagramsInTextWithNoAnagrams() {
String text = "abcdefg";
String pattern = "xyz";
List<Integer> expected = Arrays.asList();
assertEquals(expected, AnagramsInString.findAllAnagramsInText(text, pattern),
"Phải trả về danh sách trống khi không tìm thấy anagram nào.");
}

@Test
public void testFindAllAnagramsInTextWhenPatternLargerThanText() {
String text = "ab";
String pattern = "abc";
List<Integer> expected = Arrays.asList();
assertEquals(expected, AnagramsInString.findAllAnagramsInText(text, pattern),
"Phải trả về danh sách trống khi chuỗi mẫu lớn hơn văn bản.");
}

@Test
public void testFindAllAnagramsInTextWithExactMatch() {
String text = "abab";
String pattern = "abab";
List<Integer> expected = Arrays.asList(0);
assertEquals(expected, AnagramsInString.findAllAnagramsInText(text, pattern),
"Phải trả về vị trí bắt đầu khi chuỗi mẫu khớp hoàn hảo với văn bản.");
}

@Test
public void testFindAllAnagramsInTextNaiveWithMultipleAnagrams() {
String text = "cbaebabacd";
String pattern = "abc";
List<Integer> expected = Arrays.asList(0, 6);
assertEquals(expected, AnagramsInString.findAllAnagramsInTextNaive(text, pattern),
"Phải trả về các vị trí bắt đầu của tất cả các anagram của chuỗi mẫu trong văn bản.");
}

@Test
public void testFindAllAnagramsInTextNaiveWithNoAnagrams() {
String text = "abcdefg";
String pattern = "xyz";
List<Integer> expected = Arrays.asList();
assertEquals(expected, AnagramsInString.findAllAnagramsInTextNaive(text, pattern),
"Phải trả về danh sách trống khi không tìm thấy anagram nào.");
}

@Test
public void testFindAllAnagramsInTextNaiveWhenPatternLargerThanText() {
String text = "ab";
String pattern = "abc";
List<Integer> expected = Arrays.asList();
assertEquals(expected, AnagramsInString.findAllAnagramsInTextNaive(text, pattern),
"Phải trả về danh sách trống khi chuỗi mẫu lớn hơn văn bản.");
}

@Test
public void testFindAllAnagramsInTextNaiveWithExactMatch() {
String text = "abab";
String pattern = "abab";
List<Integer> expected = Arrays.asList(0);
assertEquals(expected, AnagramsInString.findAllAnagramsInTextNaive(text, pattern),
"Phải trả về vị trí bắt đầu khi chuỗi mẫu khớp hoàn hảo với văn bản.");
}
}
Loading