Java解leetcode,助力面试之中等10道题(五)
第216题 组合总和 III
找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
示例 1:
输入 | 输出 |
---|---|
k = 3, n = 7 | [[1,2,4]] |
示例 2:
输入 | 输出 |
---|---|
k = 3, n = 9 | [[1,2,6], [1,3,5], [2,3,4]] |
解题思路
利用dfs回溯搜索可行值,建立一个temp数组存值,利用回溯剪枝来完成遍历所有可行解。
代码
// 组合总和 III:dfs
class Solution {
List<Integer> temp = new ArrayList<Integer>();
List<List<Integer>> ans = new ArrayList<List<Integer>>();
public List<List<Integer>> combinationSum3(int k, int n) {
dfs(1, 9, k, n);
return ans;
}
public void dfs(int cur, int n, int k, int sum) {
if (temp.size() + (n - cur + 1) < k || temp.size() > k) {//如果当前数组大小加上后续的数无法达到k或者当前数组大小超过了k则结束
return;
}
if (temp.size() == k) {//判断尺寸大小是否达到规定数
int tempSum = 0;
for (int num : temp) {
tempSum += num;//将该集合的数累加
}
if (tempSum == sum) {//判断累加数是否等于目标值
ans.add(new ArrayList<Integer>(temp));//如果等于目标数,则将结果加入
return;
}
}
temp.add(cur);//如果当前数组大小加上后续的数可以达到k,则将当前遍历到的数加入当前数组中,然后遍历下一个数
dfs(cur + 1, n, k, sum);
temp.remove(temp.size() - 1);//剪枝,如果当前结果不满足要求,则移除最后一个数
dfs(cur + 1, n, k, sum);//遍历下一个数
}
}
时间复杂度为O(
(
k
M
)
×
k
(^M_k)×k
(kM)×k),M为集合大小,k为给定的数
空间复杂度为O(M)
第221题 最大正方形
在一个由 ‘0’ 和 ‘1’ 组成的二维矩阵内,找到只包含 ‘1’ 的最大正方形,并返回其面积。
示例 1:
输入 | 输出 |
---|---|
matrix = [[“1”,“0”,“1”,“0”,“0”],[“1”,“0”,“1”,“1”,“1”],[“1”,“1”,“1”,“1”,“1”],[“1”,“0”,“0”,“1”,“0”]] | 4 |
示例 2:
输入 | 输出 |
---|---|
matrix = [[“0”,“1”],[“1”,“0”]] | 1 |
示例 2:
输入 | 输出 |
---|---|
matrix = [[“0”]] | 0 |
解题思路
运动动态规划求解,首先将第一行和第一列为1的位置的面积标为1,其余为1的位置的面积取决于左边位置、上面位置、左上位置的最小值,然后加上1。最后将最大面积平方,即得到结果。
代码
// 最大正方形:动态规划
class Solution {
public int maximalSquare(char[][] matrix) {
int maxSide = 0;
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return maxSide;
}
int rows = matrix.length, columns = matrix[0].length;
int[][] dp = new int[rows][columns];
for (int i = 0; i < rows; i++) {
for (int j = 0; j < columns; j++) {//遍历二维矩阵
if (matrix[i][j] == '1') {
if (i == 0 || j == 0) {
dp[i][j] = 1;//如果第一行和第一列为1时,将面积设为1
} else {
dp[i][j] = Math.min(Math.min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;//如果其余位置时1,则它的面积取决于左边位置,上面位置,以及左上角一个位置的最小值然后加上1
}
maxSide = Math.max(maxSide, dp[i][j]);
}
}
}
int maxSquare = maxSide * maxSide;
return maxSquare;
}
}
时间复杂度为O(mn),m,n为二维数组行和列
空间复杂度为O(mn)
第230题 二叉搜索树中第K小的元素
给定一个二叉搜索树的根节点 root ,和一个整数 k ,请你设计一个算法查找其中第 k 个最小元素(从 1 开始计数)。
示例 1:
输入 | 输出 |
---|---|
root = [3,1,4,null,2], k = 1 | 1 |
示例 2:
输入 | 输出 |
---|---|
root = [5,3,6,2,4,null,null,1], k = 3 | 3 |
解题思路
利用栈来求解,先将所有左节点插入,然后判断能否找到第k小的元素,如果取出的左节点不符合,则令root指向当前节点的右节点,如果也不符合,则返回上一层节点,同样判断是否符合。
代码
// 二叉搜索树中第K小的元素:迭代
class Solution {
public int kthSmallest(TreeNode root, int k) {
LinkedList<TreeNode> stack = new LinkedList<TreeNode>();
while (true) {//如果遍历完树左边的所有节点,还是没有找到目标值,则依次遍历树右边的左节点
while (root != null) {
stack.add(root);
root = root.left;
}//先将所有左节点加入栈中
root = stack.removeLast();//取出栈顶元素
if (--k == 0) return root.val;//如果k不等于0,则让root等于当前节点的右子节点,再判断k是否等于0,如果没有右子节点,则从栈中继续取出节点,反复判断k是否小于0
root = root.right;
}
}
}
时间复杂度为O(H+k),H为树的高度,k为给定值
空间复杂度为O(H+k)
第236题 二叉树的最近公共祖先
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
示例 1:
输入 | 输出 |
---|---|
root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1 | 3 |
示例 2:
输入 | 输出 |
---|---|
root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4 | 5 |
示例 3:
输入 | 输出 |
---|---|
root = [1,2], p = 1, q = 2 | 1 |
解题思路
从小往上递归,如果有一个节点为节点p或节点q,则标记为true,然后找寻另一节点,找到之后,判断另一节点是否为最近公共祖先,如果不是则回到该节点的父节点,继续判断,直到找到满足的节点
代码
// 二叉树的最近公共祖先:递归
class Solution {
private TreeNode ans;
public Solution() {
this.ans = null;
}
private boolean dfs(TreeNode root, TreeNode p, TreeNode q) {
if (root == null) return false;
boolean lson = dfs(root.left, p, q);//dfs算法调用左子树
boolean rson = dfs(root.right, p, q);//dfs算法调用右子树
if ((lson && rson) || ((root.val == p.val || root.val == q.val) && (lson || rson))) {
ans = root;//如果左右子树都不为空或者根节点的值等于节点p或节点q且左左右子树至少有一个不为空
}
return lson || rson || (root.val == p.val || root.val == q.val);//如果不满足上述条件,则返回左右儿子是否都为空或者根节点的值是否等于节点p或者节点q
}
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
this.dfs(root, p, q);
return this.ans;
}
}
时间复杂度为O(n),n为树的节点数
空间复杂度为O(n)
第238题 除自身以外数组的乘积
给你一个长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。
示例 1:
输入 | 输出 |
---|---|
[1,2,3,4] | [24,12,8,6] |
解题思路
将数组分为了左右两个数组,求第一个数时,令answer[0]=1,然后乘上右边的数,求最后一个数时,令R=1,然后乘上左边的数,如果是中间数,则用左边数的乘积乘上右边数的乘积。
代码
// 除自身以外数组的乘积:
class Solution {
public int[] productExceptSelf(int[] nums) {
int length = nums.length;
int[] answer = new int[length];
// answer[i] 表示索引 i 左侧所有元素的乘积
// 因为索引为 '0' 的元素左侧没有元素, 所以 answer[0] = 1
answer[0] = 1;
for (int i = 1; i < length; i++) {
answer[i] = nums[i - 1] * answer[i - 1];
}
// R 为右侧所有元素的乘积
// 刚开始右边没有元素,所以 R = 1
int R = 1;
for (int i = length - 1; i >= 0; i--) {
// 对于索引 i,左边的乘积为 answer[i],右边的乘积为 R
answer[i] = answer[i] * R;
// R 需要包含右边所有的乘积,所以计算下一个结果时需要将当前值乘到 R 上
R *= nums[i];
}
return answer;
}
}
时间复杂度为O(n),n为数组的长度
空间复杂度为O(1)
第240题 搜索二维矩阵 II
编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性:
每行的元素从左到右升序排列。
每列的元素从上到下升序排列。
示例 1:
输入 | 输出 |
---|---|
matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 5 | true |
示例 2:
输入 | 输出 |
---|---|
matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 20 | false |
示例 3:
输入 | 输出 |
---|---|
nums = [0] | [0] |
示例 4:
输入 | 输出 |
---|---|
nums = [1] | [1] |
解题思路
定位到左下角那个值,因为上面的值比他小,右边的值比他大,所有比对当前位置的值与目标值,如果大于目标值,则往上走,如果小于目标值则往右走,直到找到目标值或者超出数组范围。
代码
// 搜索二维矩阵 II:数组
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
// start our "pointer" in the bottom-left
int row = matrix.length-1;
int col = 0;
while (row >= 0 && col < matrix[0].length) {//
if (matrix[row][col] > target) {//定位为左下角的数,如果值大于目标值,则向上移
row--;
} else if (matrix[row][col] < target) {//如果小于目标值,则向右移
col++;
} else { // 如果找到目标值,则返回ture
return true;
}
}
return false;//如果超出数组范围则返回false
}
}
时间复杂度为O(n+m),m,n为数组的行和列的长度
空间复杂度为O(1)
第241题 为运算表达式设计优先级
**给定一个含有数字和运算符的字符串,为表达式添加括号,改变其运算优先级以求出不同的结果。你需要给出所有可能的组合的结果。有效的运算符号包含 +, - 以及 ***
示例 1:
输入 | 输出 |
---|---|
“2-1-1” | [0, 2] |
解释:
((2-1)-1) = 0
(2-(1-1)) = 2
示例 2:
输入 | 输出 |
---|---|
“23-45” | [-34, -14, -10, -10, 10] |
解释:
(
2
∗
(
3
−
(
4
∗
5
)
)
)
(2*(3-(4*5)))
(2∗(3−(4∗5))) = -34
(
(
2
∗
3
)
−
(
4
∗
5
)
)
((2 * 3)-(4*5))
((2∗3)−(4∗5)) = -14
(
(
2
∗
(
3
−
4
)
)
∗
5
)
((2*(3-4))*5)
((2∗(3−4))∗5) = -10
(
2
∗
(
(
3
−
4
)
∗
5
)
)
(2*((3-4)*5))
(2∗((3−4)∗5)) = -10
(
(
(
2
∗
3
)
−
4
)
∗
5
)
(((2*3)-4)*5)
(((2∗3)−4)∗5) = 10
解题思路
采取分治解决,将字符串分为左右部分,遍历字符串,遍历到的位置以左设为left,按原来的操作执行,遍历到的位置以右分别进行+、-、*操作
代码
// 为运算表达式设计优先级:
class Solution {
public List<Integer> diffWaysToCompute(String input) {
return partition(input);
}
public List<Integer> partition(String input) {
List<Integer> result = new ArrayList<>();
if (!input.contains("+") && !input.contains("-") && !input.contains("*")) {//判断输入中是否有+、-、*
result.add(Integer.valueOf(input));//如果都没有,则直接将input加入到结果中
return result;
}
for(int i = 0; i < input.length(); i++) {//遍历数组长度
if (input.charAt(i) == '+' || input.charAt(i) == '-' || input.charAt(i) == '*') {//如果遍历到了字符+、-、*,则进行以下操作
for(Integer left : partition(input.substring(0, i))) {//left为当前遍历到的位置,不更改
for (Integer right : partition(input.substring(i + 1))) {//right为后续未遍历到的位置,对后面的位置执行+、-、*操作
if (input.charAt(i) == '+') {
result.add(left + right);
} else if (input.charAt(i) == '-') {
result.add(left - right);
} else if (input.charAt(i) == '*') {
result.add(left * right);
}
}
}
}
}
return result;
}
}
时间复杂度为O(),m,n为数组的长度
空间复杂度为O(n),字符串长度
第260题 只出现一次的数字 III
给定一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。你可以按 任意顺序 返回答案。
示例 1:
输入 | 输出 |
---|---|
nums = [1,2,1,3,2,5] | [3,5] |
示例 2:
输入 | 输出 |
---|---|
nums = [-1,0] | [-1,0] |
示例 3:
输入 | 输出 |
---|---|
nums = [0,1] | [0,1] |
解题思路
本题使用位运算中的异或操作求解,首先让0与数组中的所有数进行异或操作得到两个只出现一次的数的异或结果,然后让1与该结果进行&操作,如果等于0,则将1的二进制数右移一位,直到执行&操作后得到1,因为两个只出现一次的数的二进制结果中,如果有1,则代表两个数在这个位置不相等,取出这个位置为1的数,然后将这个数与数组执行异或操作,将操作结果为0的数与a进行异或操作,其余的数与b进行异或操作,就可以得到两个只出现一次的数。
代码
// 只出现一次的数字 III:位运算
class Solution {
public int[] singleNumber(int[] nums) {
int ret = 0;
for (int n : nums) {
ret ^= n;//让0与数组所有数字异或
}
int div = 1;
while ((div & ret) == 0) {
div <<= 1;
}//找到任意一位是1的数,因为ret为数组所有数异或的值,如果有一位的值为1,则代表两个只出现一次的数在该二进制位上的数值不同,则可以让div与其他数字中该位为1的数进行&操作,将其中执行操作后不为0的数与0进行异或操作得到一个只出现一次的数,然后将另一部分与0异或得到另一个只出现一次的数。
int a = 0, b = 0;
for (int n : nums) {
if ((div & n) != 0) {
a ^= n;
} else {
b ^= n;
}
}
return new int[]{a, b};
}
}
时间复杂度为O(n),n为数组的大小
空间复杂度为O(1)
第279题 完全平方数
给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
给你一个整数 n ,返回和为 n 的完全平方数的 最少数量 。
完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。
示例 1:
输入 | 输出 |
---|---|
n = 12 | 3 |
解释:12 = 4 + 4 + 4
示例 2:
输入 | 输出 |
---|---|
n = 13 | 2 |
解释:13 = 9 + 4
解题思路
运用动态调度,一次一次求出所有1-n之间的平方表示
代码
// 完全平方数:动态规划
class Solution {
public int numSquares(int n) {
int[] f = new int[n + 1];
for (int i = 1; i <= n; i++) {//遍历,求出1-n之间所有数的平方表示
int minn = Integer.MAX_VALUE;
for (int j = 1; j * j <= i; j++) {
minn = Math.min(minn, f[i - j * j]);
}
f[i] = minn + 1;//表示最小需要多少个平方来表示整数
}
return f[n];
}
}
时间复杂度为O(
n
n
n\sqrt n
nn),n为给定的数
空间复杂度为O(n)
第287题 寻找重复数
给定一个包含 n + 1 个整数的数组 nums ,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。
假设 nums 只有 一个重复的整数 ,找出 这个重复的数
示例 1:
输入 | 输出 |
---|---|
nums = [1,3,4,2,2] | 2 |
示例 2:
输入 | 输出 |
---|---|
nums = [3,1,3,4,2] | 3 |
示例 3:
输入 | 输出 |
---|---|
nums = [1,1]] | 1 |
示例 4:
输入 | 输出 |
---|---|
nums = [1,1,2] | 1 |
解题思路
利用双指针求解,因为数组中的数不会超过数组长度,因此可以看作时一个环,每次让慢指针走一步,快指针走两步,直到两个值相等,此时将慢指针放在数组首部,同时让两指针走一步,直到两个值相等就是结果。
代码
// 寻找重复数:双指针
class Solution {
public int findDuplicate(int[] nums) {
int slow = 0, fast = 0;
do {
slow = nums[slow];
fast = nums[nums[fast]];//慢指针走一步,快指针走两步
} while (slow != fast);
slow = 0;//当快慢指针值相等时,将慢指针放在数组首部
while (slow != fast) {//让快慢指针每次走一步,直到两个值相等就是答案
slow = nums[slow];
fast = nums[fast];
}
return slow;
}
}
时间复杂度为O(n),n为数组长度
空间复杂度为O(1)