diff --git a/src/main/java/com/leetcode/graphs/GraphValidTree.java b/src/main/java/com/leetcode/graphs/GraphValidTree.java index 15950143..b9fb1e44 100644 --- a/src/main/java/com/leetcode/graphs/GraphValidTree.java +++ b/src/main/java/com/leetcode/graphs/GraphValidTree.java @@ -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> adjacencyList = new ArrayList<>(n); + List> adjacencyList = buildAdjacencyList(n, edges); + return isTreeBFS(n, adjacencyList); + } + /** + * Builds the adjacency list from the given edges. + */ + private static List> buildAdjacencyList(int n, int[][] edges) { + List> 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> 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> adjacencyList) { + Set visited = new HashSet<>(); + Queue 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) { diff --git a/src/main/java/com/leetcode/graphs/WordLadder.java b/src/main/java/com/leetcode/graphs/WordLadder.java index 61e706ce..c10b020f 100644 --- a/src/main/java/com/leetcode/graphs/WordLadder.java +++ b/src/main/java/com/leetcode/graphs/WordLadder.java @@ -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: - *

+ * * 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. - *

+ * * 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. - *

+ * * Example 1: * Input: * beginWord = "hit", * endWord = "cog", * wordList = ["hot","dot","dog","lot","log","cog"] - *

+ * * Output: 5 - *

+ * * Explanation: As one shortest transformation is "hit" -> "hot" -> "dot" -> "dog" -> "cog", * return its length 5. - *

+ * * Example 2: * Input: * beginWord = "hit" * endWord = "cog" * wordList = ["hot","dot","dog","lot","log"] - *

+ * * Output: 0 - *

- * 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: 79 ms. - * - * @param beginWord - * @param endWord - * @param wordList - * @return - */ + private static final char WILDCARD_CHAR = '*'; + public static int ladderLength(String beginWord, String endWord, List wordList) { - int L = beginWord.length(); - Map> transformedToOriginalWordMap = new HashMap<>(); - Queue> queue = new LinkedList<>(); + Map> allComboDict = preprocessWords(wordList); + return performBFS(beginWord, endWord, allComboDict); + } + private static Map> preprocessWords(List wordList) { + Map> 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 visited = new HashSet<>(); + private static int performBFS(String beginWord, String endWord, Map> allComboDict) { + Queue> queue = new LinkedList<>(); queue.add(new Pair<>(beginWord, 1)); + + Set visited = new HashSet<>(); visited.add(beginWord); while (!queue.isEmpty()) { - Pair 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 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 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"))); diff --git a/src/test/AnagramsInStringTest.java b/src/test/AnagramsInStringTest.java new file mode 100644 index 00000000..58caadb6 --- /dev/null +++ b/src/test/AnagramsInStringTest.java @@ -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 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 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 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 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 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 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 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 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."); + } +} diff --git a/src/test/BuySellStocksTest.java b/src/test/BuySellStocksTest.java new file mode 100644 index 00000000..48bbe2f9 --- /dev/null +++ b/src/test/BuySellStocksTest.java @@ -0,0 +1,43 @@ +package com.leetcode.arrays; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; + +public class BuySellStocksTest { + + @Test + public void testMaxProfitSimpleCase() { + assertEquals(5, BuySellStocks.maxProfit(new int[]{7, 1, 5, 3, 6, 4}), + "Phải trả về lợi nhuận tối đa là 5"); + } + + @Test + public void testMaxProfitNoTransaction() { + assertEquals(0, BuySellStocks.maxProfit(new int[]{7, 6, 4, 3, 1}), + "Phải trả về lợi nhuận tối đa là 0 khi không có giao dịch nào được thực hiện"); + } + + @Test + public void testMaxProfitWithZeroPrice() { + assertEquals(6, BuySellStocks.maxProfit(new int[]{7, 1, 5, 0, 6, 4}), + "Phải trả về lợi nhuận tối đa là 6 khi có giá là 0"); + } + + @Test + public void testMaxProfitDescendingOrder() { + assertEquals(0, BuySellStocks.maxProfit(new int[]{4, 3, 2, 1}), + "Phải trả về lợi nhuận tối đa là 0 với giá giảm dần"); + } + + @Test + public void testMaxProfitEmptyArray() { + assertEquals(0, BuySellStocks.maxProfit(new int[]{}), + "Phải trả về lợi nhuận tối đa là 0 với mảng trống"); + } + + @Test + public void testMaxProfitSingleElement() { + assertEquals(0, BuySellStocks.maxProfit(new int[]{1}), + "Phải trả về lợi nhuận tối đa là 0 với mảng chỉ một phần tử"); + } +} diff --git a/src/test/CountAndSayTest.java b/src/test/CountAndSayTest.java new file mode 100644 index 00000000..ddd9f241 --- /dev/null +++ b/src/test/CountAndSayTest.java @@ -0,0 +1,37 @@ +package com.leetcode.arrays; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; + +public class CountAndSayTest { + + @Test + public void testCountAndSayBaseCase() { + assertEquals("1", CountAndSay.countAndSay(1), + "Phải trả về '1' khi n = 1."); + } + + @Test + public void testCountAndSaySecond() { + assertEquals("11", CountAndSay.countAndSay(2), + "Phải trả về '11' khi n = 2."); + } + + @Test + public void testCountAndSayThird() { + assertEquals("21", CountAndSay.countAndSay(3), + "Phải trả về '21' khi n = 3."); + } + + @Test + public void testCountAndSayFourth() { + assertEquals("1211", CountAndSay.countAndSay(4), + "Phải trả về '1211' khi n = 4."); + } + + @Test + public void testCountAndSayFifth() { + assertEquals("111221", CountAndSay.countAndSay(5), + "Phải trả về '111221' khi n = 5."); + } +} diff --git a/src/test/MajorityElementTest.java b/src/test/MajorityElementTest.java new file mode 100644 index 00000000..c19bdaea --- /dev/null +++ b/src/test/MajorityElementTest.java @@ -0,0 +1,31 @@ +package com.leetcode.arrays; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; + +public class MajorityElementTest { + + @Test + public void testMajorityElementSimple() { + assertEquals(5, MajorityElement.majorityElement(new int[]{2, 2, 1, 1, 5, 5, 5}), + "Phải trả về phần tử đa số 5."); + } + + @Test + public void testMajorityElementAllSame() { + assertEquals(1, MajorityElement.majorityElement(new int[]{1, 1, 1, 1}), + "Phải trả về phần tử đa số 1 khi tất cả các phần tử giống nhau."); + } + + @Test + public void testMajorityElementNoInitialMajor() { + assertEquals(3, MajorityElement.majorityElement(new int[]{1, 1, 2, 3, 3, 3, 3}), + "Phải trả về phần tử đa số 3 khi phần tử đa số không phải là phần tử đầu tiên."); + } + + @Test + public void testMajorityElementChangeMajority() { + assertEquals(1, MajorityElement.majorityElement(new int[]{3, 1, 3, 1, 1}), + "Phải trả về phần tử đa số 1 khi phần tử đa số thay đổi trong quá trình duyệt."); + } +} diff --git a/src/test/SearchInsertPositionTest.java b/src/test/SearchInsertPositionTest.java new file mode 100644 index 00000000..aa4577b2 --- /dev/null +++ b/src/test/SearchInsertPositionTest.java @@ -0,0 +1,31 @@ +package com.leetcode.arrays; + +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; + +class SearchInsertPositionTest { + @Test + public void testTargetFound() { + assertEquals(2, SearchInsertPosition.searchInsert(new int[]{1,3,5,6}, 5)); + } + + @Test + public void testInsertPosition() { + assertEquals(1, SearchInsertPosition.searchInsert(new int[]{1,3,5,6}, 2)); + } + + @Test + public void testInsertAtEnd() { + assertEquals(4, SearchInsertPosition.searchInsert(new int[]{1,3,5,6}, 7)); + } + + @Test + public void testInsertAtBeginning() { + assertEquals(0, SearchInsertPosition.searchInsert(new int[]{1,3,5,6}, 0)); + } + + @Test + public void testEmptyArray() { + assertEquals(0, SearchInsertPosition.searchInsert(new int[]{}, 3)); + } +} \ No newline at end of file pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy