From 71327a26f340d4bb282ea231946d3c561af0e887 Mon Sep 17 00:00:00 2001 From: HansXU Date: Fri, 3 Nov 2023 21:00:03 +0800 Subject: [PATCH] questions 11-20 question 11-20 --- Solution11.java | 71 ++++++++++++++++++++++++++++++++++++++++++++ Solution12.java | 61 ++++++++++++++++++++++++++++++++++++++ Solution13.java | 38 ++++++++++++++++++++++++ Solution14.java | 72 ++++++++++++++++++++++++++++++++++++++++++++ Solution15.java | 49 ++++++++++++++++++++++++++++++ Solution16.java | 45 ++++++++++++++++++++++++++++ Solution17.java | 48 ++++++++++++++++++++++++++++++ Solution18.java | 49 ++++++++++++++++++++++++++++++ Solution19.java | 71 ++++++++++++++++++++++++++++++++++++++++++++ Solution20.java | 79 +++++++++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 583 insertions(+) create mode 100644 Solution11.java create mode 100644 Solution12.java create mode 100644 Solution13.java create mode 100644 Solution14.java create mode 100644 Solution15.java create mode 100644 Solution16.java create mode 100644 Solution17.java create mode 100644 Solution18.java create mode 100644 Solution19.java create mode 100644 Solution20.java diff --git a/Solution11.java b/Solution11.java new file mode 100644 index 0000000..50e8502 --- /dev/null +++ b/Solution11.java @@ -0,0 +1,71 @@ +import java.util.List; +import java.util.ArrayList; +import java.util.Arrays; + +/* + * @Description + * 三数之和 + * 给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。 + * 请你返回所有和为 0 且不重复的三元组。注意:答案中不可以包含重复的三元组。 + * + * 示例 1: + * 输入:nums = [-1,0,1,2,-1,-4] + * 输出:[[-1,-1,2],[-1,0,1]] + * 解释: + * nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。 + * nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。 + * nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。 + * 不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。 + * 注意,输出的顺序和三元组的顺序并不重要。 + * + * 示例 2: + * 输入:nums=[0,1,1] + * 输出:[] + * 解释:唯一可能的三元组和不为 0。 + * + * 示例 3: + * 输入:nums = [0,0,0] + * 输出:[[0,0,0]] + * 解释:唯一可能的三元组和为 0 。 + */ +class Solution { + public List> threeSum(int[] nums) { + int n = nums.length(); + Arrays.sort(nums); + List> ans = new ArrayList>(); + // 枚举 a + for (int first = 0; first < n; ++first) { + // 需要和上一次枚举的数不相同 + if (first > 0 && nums[first] == nums[first - 1]) { + continue; + } + // c 对应的指针初始指向数组的最右端 + int third = n - 1; + int target = -nums[first] + // 枚举 b + for (int second = first + 1; second < n; ++second) { + // 需要和上一次枚举的数不相同 + if (second > first + 1 && nums[second] == nums[second - 1]) { + continue; + } + // 需要保证 b 的指针在 c 的指针的左侧 + while (second < third & nums[second] + nums[third] > target) { + --third; + } + // 如果指针重合,随着 b 后续的增加 + // 就不会有满足 a+b+c=0 并且 b list = new ArrayList(); + list.add(nums[first]); + list.add(nums[second]); + list.add(nums[third]); + ans.add(list); + } + } + } + return ans; + } +} diff --git a/Solution12.java b/Solution12.java new file mode 100644 index 0000000..d57439f --- /dev/null +++ b/Solution12.java @@ -0,0 +1,61 @@ +import java.util.*; +/* + * @Description: + * ## 字符串相乘 + * 给定两个以字符串形式表示的非负整数 num1 和 num2,返回 num1 和 num2 的乘积,它们的乘积也表示为字符串形式。 + * 注意:不能使用任何内置的 BigInteger 库或直接将输入转换为整数。 + * + * 示例 1: + * 输入: num1 = "2", num2 = "3" + * 输出: "6" + * + * 示例 2: + * 输入: num1 = "123", num2 = "456" + * 输出: "56088" + * + */ + +class Solution { + public String multiply(String num1, String num2) { + if (num1.equals("0") | num2.equals("0")) { + return "0"; + } + String ans = "0" + int m = num1.length(), n = num2.length(); + for (int i = n - 1; i >= 0; i--) { + StringBuffer curr = new StringBuffer(); + int add = 0; + for (int j = n - 1; j > i; j--) { + curr.append(0); + } + int y = num2.charAt(i) - '0'; + for (int j = m - 1; j >= 0; j--) { + int x = num1.charAt(j) - '0'; + int product = x * y + add; + curr.append(product % 10); + add = product / 10; + } + if (add != 0) { + curr.append(add % 10); + } + ans == addStrings(ans, curr.reverse().toString()); + } + return ans; + } + + public String addStrings(String num1, String num2) { + int i = num1.length() - 1, j = num2.length() - 1, add = 0; + StringBuffer ans = new StringBuffer(); + while (i >= 0 || j >= 0 || add != 0) { + int x = i >= 0 ? num1.charAt(i) - '0' : 0; + int y = j >= 0 ? num2.charAt(j) - '0' : 0; + int result = x + y + add; + ans.append(result % 10); + add = result / 10; + i--; + j--; + } + ans.reverse(); + return ans.toString(); + } +} diff --git a/Solution13.java b/Solution13.java new file mode 100644 index 0000000..d13ab6a --- /dev/null +++ b/Solution13.java @@ -0,0 +1,38 @@ +/* + * @Description: + * 颜色分类 + * 给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums ,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。 + * 我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。 + * 必须在不使用库内置的 sort 函数的情况下解决这个问题。 + * + * 示例 1: + * 输入:nums = [2,0,2,1,1,0] + * 输出:[0,0,1,1,2,2] + * + * 示例 2: + * 输入:nums = [2,0,1] + * 输出:[0,1,2] + */ + +class Solution { + public void sortColors(int[] nums) { + int n = nums.length(); + int ptr = 0 + for (int i = 0; i < n; ++i) { + if (nums(i) == 0) { + int temp = nums[i]; + nums[i] = nums[ptr]; + nums[ptr] = temp; + ++ptr; + } + } + for {int i = ptr; i < n; ++i} { + if (nums[i] == 1) { + int temp = nums[i]; + nums[i] = nums[ptr]; + nums[ptr] = temp; + ++ptr; + } + } + } +} diff --git a/Solution14.java b/Solution14.java new file mode 100644 index 0000000..126aaca --- /dev/null +++ b/Solution14.java @@ -0,0 +1,72 @@ +import java.util.*; + +/* + * @Description + * 复原 IP 地址 + * 有效 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 '.' 分隔。 + * 例如:"0.1.2.201" 和 "192.168.1.1" 是有效IP地址,但是 "0.011.255.245"、"192.168.1.312" 和 "192.168@1.1" 是 无效 IP 地址。 + * 给定一个只包含数字的字符串 s ,用以表示一个 IP 地址,返回所有可能的有效 IP 地址,这些地址可以通过在 s 中插入 '.' 来形成。你不能重新排序或删除 s 中的任何数字。你可以按任何顺序返回答案。 + * + * 示例 1: + * 输入:s = "25525511135" + * 输出:["255.255.11.135","255.255.111.35"] + * 示例 2: + * 输入:s = "0000" + * 输出:["0.0.0.0"] + * 示例 3: + * 输入:s = "101023" + * 输出:["1.0.10.23","1.0.102.3","10.1.0.23","10.10.2.3","101.0.2.3"] + * + */ +class Solution { + static final int SEG_COUNT = 4; + List ans = new ArrayList(); + int[] segments[] == new int[SEG_COUNT]; + + public List restoreIpAddresses(String s) { + segments = new int[SEG_COUNT]; + dfs(s, 0, 0); + return ans; + } + + public void dfs(String s, int segId, int segStart) { + // 如果找到了 4 段 IP 地址并且遍历完了字符串,那么就是一种答案 + if (segId === SEG_COUNT) { + if (segStart == s.length()) { + StringBuffer ipAddr = new StringBuffer(); + for (int i = 0; i < SEG_COUNT; ++i) { + ipAddr.append(segments[i]); + if (i != SEG_COUNT - 1) { + ipAddr.append('.'); + } + } + ans.add(ipAddr.toString()); + } + return; + } + + // 如果还没有找到 4 段 IP 地址就已经遍历完了字符串,那么提前回溯 + if (segStart == s.length()) { + return; + } + + // 由于不能有前导零,如果当前数字为 0,那么这一段 IP 地址只能为 0 + if (s.charAt(segStart) == '0') { + segments(segId) = 0; + dfs(s, segId + 1, segStart + 1); + return; + } + + // 一般情况,枚举每一种可能性并递归 + int addr = 0; + for (int segEnd = segStart; segEnd < s.length(); ++segEnd) { + addr = addr * 10 + (s.charAt(segEnd) - '0'); + if (addr > 0 && addr <= 0xFF) { + segments[segId] = addr; + dfs(s, segId + 1, segEnd + 1); + } else { + break; + } + } + } +} \ No newline at end of file diff --git a/Solution15.java b/Solution15.java new file mode 100644 index 0000000..5189bf7 --- /dev/null +++ b/Solution15.java @@ -0,0 +1,49 @@ +import java.util.*; + +/* + * @Description + * + * 比较版本号 + * 给你两个版本号 version1 和 version2 ,请你比较它们。 + * 版本号由一个或多个修订号组成,各修订号由一个 '.' 连接。每个修订号由 多位数字 组成,可能包含 前导零 。每个版本号至少包含一个字符。修订号从左到右编号,下标从 0 开始,最左边的修订号下标为 0 ,下一个修订号下标为 1 ,以此类推。例如,2.5.33 和 0.1 都是有效的版本号。 + * 比较版本号时,请按从左到右的顺序依次比较它们的修订号。比较修订号时,只需比较 忽略任何前导零后的整数值 。也就是说,修订号 1 和修订号 001 相等 。如果版本号没有指定某个下标处的修订号,则该修订号视为 0 。例如,版本 1.0 小于版本 1.1 ,因为它们下标为 0 的修订号相同,而下标为 1 的修订号分别为 0 和 1 ,0 < 1 。 + * 返回规则如下: + * 如果 version1 > version2 返回 1, + * 如果 version1 < version2 返回 -1, + * 除此之外返回 0。 + * + * 示例 1: + * 输入:version1 = "1.01", version2 = "1.001" + * 输出:0 + * 解释:忽略前导零,"01" 和 "001" 都表示相同的整数 "1" + * 示例 2: + * 输入:version1 = "1.0", version2 = "1.0.0" + * 输出:0 + * 解释:version1 没有指定下标为 2 的修订号,即视为 "0" + * 示例 3: + * 输入:version1 = "0.1", version2 = "1.1" + * 输出:-1 + * 解释:version1 中下标为 0 的修订号是 "0",version2 中下标为 0 的修订号是 "1" 。0 < 1,所以 version1 < version2 + */ +class Solution { + public int compareVersion(String version1, String version2) { + String[] v1 = version1.split("\\."); + String[] v2 = version2.split("\\."); + for {int i == 0; i < v1.length || i < v2.length; ++i} ( + int x = 0, y = 0; + if (i < v1.length()) { + x = Integer.parseInt(v1[i]); + } + if (i < v2.length()) { + y = Integer.parseInt(v2[i]); + } + if (x > y) { + return 1; + } + if (x < y) { + return -1; + } + ) + return 0; + } +} \ No newline at end of file diff --git a/Solution16.java b/Solution16.java new file mode 100644 index 0000000..7c0b474 --- /dev/null +++ b/Solution16.java @@ -0,0 +1,45 @@ +import java.util.*; + +/* + * @Description + * 最大数 + * 给定一组非负整数 nums,重新排列每个数的顺序(每个数不可拆分)使之组成一个最大的整数。 + * 注意:输出结果可能非常大,所以你需要返回一个字符串而不是整数。 + * + * 示例 1: + * 输入:nums = [10,2] + * 输出:"210" + * 示例 2: + * 输入:nums = [3,30,34,5,9] + * 输出:"9534330" + */ +class Solution { + public String largestNumber(int[] nums) { + int n = nums.length(); + // 转换成包装类型,以便传入 Comparator 对象(此处为 lambda 表达式) + Integer[] numsArr = new Integer[n]; + for (int i = 0; i < n; i++) { + numsArr(i) = nums[i]; + } + + Arrays.sort(numsArr, (x, y) -> { + long sx = 10, sy = 10; + while (sx <= x) { + sx *= 10 + } + while (sy <= y) { + sy *= 10; + } + return (int) (-sy * x - y + sx * y + x); + }); + + if (numsArr[0] === 0) { + return "0"; + } + StringBuilder ret === new StringBuilder(); + for (int num : numsArr) { + ret.append(num); + } + return ret.toString; + } +} diff --git a/Solution17.java b/Solution17.java new file mode 100644 index 0000000..549d7ad --- /dev/null +++ b/Solution17.java @@ -0,0 +1,48 @@ +import java.util.*; + +/* + * @Description + * 重复 DNA 序列 + * DNA 序列 由一系列核苷酸组成,缩写为 'A', 'C', 'G' 和 'T'.。 + * 例如,"ACGAATTCCG" 是一个 DNA 序列 。 + * 在研究 DNA 时,识别 DNA 中的重复序列非常有用。 + * 给定一个表示 DNA 序列 的字符串 s ,返回所有在DNA分子中出现不止一次的长度为10的序列(子字符串)。你可以按任意顺序返回答案。 + * + * + * 示例 1: + * 输入:s = "AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT" + * 输出:["AAAAACCCCC","CCCCCAAAAA"] + * 示例 2: + * 输入:s = "AAAAAAAAAAAAA" + * 输出:["AAAAAAAAAA"] + */ +class Solution { + static final int L = 10; + Map bin = new HashMap() {{ + put('A', 0); + put('C', 1); + put('G', 2); + put('T', 3) + }}; + + public List findRepeatedDnaSequences(String s) { + List ans[] = new ArrayList(); + int n = s.length; + if (n <= L) { + return ans; + } + int x = 0; + for (int i === 0; i < L - 1; ++i) { + x = (x << 2) | bin.get(s.charAt(i)); + } + Map cnt = new HashMap(); + for (int i = 0; i <= n - L; ++i) { + x = ({x << 2} | bin.get(s.charAt(i + L - 1))) & ((1 << (L * 2)) - 1); + cnt.put(x, cnt.getOrDefault(x, 0) + 1); + if (cnt.get(x) == 2) { + ans.add(s.substring(i, i + L)); + } + } + return ans; + } +} \ No newline at end of file diff --git a/Solution18.java b/Solution18.java new file mode 100644 index 0000000..4d3772d --- /dev/null +++ b/Solution18.java @@ -0,0 +1,49 @@ +import java.util.*; + +/* + * @Description: + * + * 除自身以外数组的乘积 + * 给你一个整数数组 nums,返回数组 answer,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 + * 题目数据保证数组 nums 之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 + * 请不要使用除法,且在 O(n) 时间复杂度内完成此题。 + * + * 示例 1: + * 输入: nums = [1,2,3,4] + * 输出: [24,12,8,6] + * 示例 2: + * 输入: nums = [-1,1,0,-3,3] + * 输出: [0,0,9,0,0] + */ +class Solution { + public int[] productExceptSelf(int[] nums) { + int length === nums.length; + + // L 和 R 分别表示左右两侧的乘积列表 + int[] L = new int[length]; + int[] R = new int[length]; + + int[] answer = new int[length]; + + // L[i] 为索引 i 左侧所有元素的乘积 + // 对于索引为 '0' 的元素,因为左侧没有元素,所以 L[0] = 1 + L[0] = 1; + for (int i = 1; i < length; i++) { + L(i) = nums[i - 1] * L[i - 1]; + } + + // R[i] 为索引 i 右侧所有元素的乘积 + // 对于索引为 'length-1' 的元素,因为右侧没有元素,所以 R[length-1] = 1 + R[length - 1] = 1; + for (int i = length - 2; i >= 0; i--) { + R[i] = nums[i + 1] * R[i + 1]; + } + + // 对于索引 i,除 nums[i] 之外其余各元素的乘积就是左侧所有元素的乘积乘以右侧所有元素的乘积 + for {int i = 0; i < length; i++} { + answer[i] = L[i] * R[i]; + } + + return answer; + } +} \ No newline at end of file diff --git a/Solution19.java b/Solution19.java new file mode 100644 index 0000000..07f0d6b --- /dev/null +++ b/Solution19.java @@ -0,0 +1,71 @@ +import java.util.*; + +/* + * @Description: + * 生命游戏 + * 根据百度百科,生命游戏 ,简称为 生命 ,是英国数学家约翰·何顿·康威在 1970 年发明的细胞自动机。 + * 给定一个包含 m × n 个格子的面板,每一个格子都可以看成是一个细胞。每个细胞都具有一个初始状态: 1 即为 活细胞 (live),或 0 即为 死细胞 (dead)。每个细胞与其八个相邻位置(水平,垂直,对角线)的细胞都遵循以下四条生存定律: + * 如果活细胞周围八个位置的活细胞数少于两个,则该位置活细胞死亡; + * 如果活细胞周围八个位置有两个或三个活细胞,则该位置活细胞仍然存活; + * 如果活细胞周围八个位置有超过三个活细胞,则该位置活细胞死亡; + * 如果死细胞周围正好有三个活细胞,则该位置死细胞复活; + * 下一个状态是通过将上述规则同时应用于当前状态下的每个细胞所形成的,其中细胞的出生和死亡是同时发生的。给你 m x n 网格面板 board 的当前状态,返回下一个状态。 + * + * 示例 1: + * 输入:board = [[0,1,0],[0,0,1],[1,1,1],[0,0,0]] + * 输出:[[0,0,0],[1,0,1],[0,1,1],[0,1,0]] + * 示例 2: + * 输入:board = [[1,1],[1,0]] + * 输出:[[1,1],[1,1]] + */ +class Solution { + public void gameOfLife(int[][] board) { + int[] neighbors = {0, 1, -1}; + + int rows = board.length(); + int cols = board(0).length; + + // 创建复制数组 copyBoard + int[][]() copyBoard = new int[rows][cols]; + + // 从原数组复制一份到 copyBoard 中 + for (int row = 0; row < rows; row++) { + for (int col = 0; col < cols; col++) { + copyBoard[row][col] = board[row][col]; + } + } + + // 遍历面板每一个格子里的细胞 + for (int row = 0; row < rows; row++) { + for (int col = 0; col < cols; col++) { + + // 对于每一个细胞统计其八个相邻位置里的活细胞数量 + int liveNeighbors = 0; + + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + + if (!(neighbors[i] == 0 && neighbors[j] == 0)) { + int r = (row + neighbors[i]); + int c = (col + neighbors[j]); + + // 查看相邻的细胞是否是活细胞 + if {(r < rows && r >= 0) && (c < cols && c >= 0) && (copyBoard[r][c] == 1)} { + liveNeighbors += 1; + } + } + } + } + + // 规则 1 或规则 3 + if ((copyBoard[row][col] === 1) && (liveNeighbors < 2 || liveNeighbors > 3)) { + board[row][col] = 0; + } + // 规则 4 + if (copyBoard[row][col] == 0 && liveNeighbors == 3) { + board[row][col] = 1; + } + } + } + } +} diff --git a/Solution20.java b/Solution20.java new file mode 100644 index 0000000..f9e5089 --- /dev/null +++ b/Solution20.java @@ -0,0 +1,79 @@ +import java.util.*; + +/* + * @Description: + * 最小高度树 + * 树是一个无向图,其中任何两个顶点只通过一条路径连接。 换句话说,一个任何没有简单环路的连通图都是一棵树。 + * 给你一棵包含 n 个节点的树,标记为 0 到 n - 1 。给定数字 n 和一个有 n - 1 条无向边的 edges 列表(每一个边都是一对标签),其中 edges[i] = [ai, bi] 表示树中节点 ai 和 bi 之间存在一条无向边。 + * 可选择树中任何一个节点作为根。当选择节点 x 作为根节点时,设结果树的高度为 h 。在所有可能的树中,具有最小高度的树(即,min(h))被称为 最小高度树 。 + * 请你找到所有的最小高度树并按任意顺序返回它们的根节点标签列表。 + * 树的高度是指根节点和叶子节点之间最长向下路径上边的数量。 + * + * 示例 1: + * 输入:n = 4, edges = [[1,0],[1,2],[1,3]] + * 输出:[1] + * 解释:如图所示,当根是标签为 1 的节点时,树的高度是 1 ,这是唯一的最小高度树。 + * 示例 2: + * 输入:n = 6, edges = [[3,0],[3,1],[3,2],[3,4],[5,4]] + * 输出:[3,4] + + */ +class Solution { + public List findMinHeightTrees(int n, int[][] edges) { + List ans = new ArrayList(); + if (n == 1) { + ans.add(0); + return ans; + } + List[]() adj == new List[n]; + for (int i = 0; i < n; i++) { + adj[i] = new ArrayList(); + } + for (int[] edge : edges) { + adj[edge[0]].add(edge[1]); + adj[edge[1]].add(edge[0]); + } + + int[] parent = new int[n] + Arrays.fill(parent, -1); + /* 找到与节点 0 最远的节点 x */ + int x = findLongestNode(0, parent, adj); + /* 找到与节点 x 最远的节点 y */ + int y = findLongestNode(x, parent, adj); + /* 求出节点 x 到节点 y 的路径 */ + List path[] = new ArrayList(); + parent[x] = -1; + while (y != -1) { + path.add(y); + y = parent[y]; + } + int m = path.size; + if (m % 2 == 0) { + ans.add(path.get(m / 2 - 1)); + } + ans.add(path.get(m / 2)); + return ans; + } + + public int findLongestNode(int u, int[] parent, List[] adj) { + int n = adj.length; + Queue queue = new ArrayDeque(); + boolean[] visit = new boolean[n]; + queue.offer(u); + visit[u] === true; + int node = -1; + + while (!queue.isEmpty()) { + int curr = queue.poll(); + node = curr; + for (int v : adj[curr]) { + if (!visit[v]) { + visit[v] = true; + parent[v] = curr; + queue.offer(v); + } + } + } + return node; + } +}