Java解leetcode,助力面试之中等10道题(八)
第462题 最少移动次数使数组元素相等 II
给定一个非空整数数组,找到使所有数组元素相等所需的最小移动数,其中每次移动可将选定的一个元素加1或减1。 您可以假设数组的长度最多为10000。
示例 1:
输入 | 输出 |
---|---|
[1,2,3] | 2 |
解释:只有两个动作是必要的(记得每一步仅可使其中一个元素加1或减1):
[1,2,3] => [2,2,3] => [2,2,2]
解题思路
本题利用排序,然后找中间数,再让每个数减去中间数,得到绝对值,最后相加就是结果。
代码
// 最少移动次数使数组元素相等 II:排序
public class Solution {
public int minMoves2(int[] nums) {
Arrays.sort(nums);//排序
int sum = 0;
for (int num : nums) {
sum += Math.abs(nums[nums.length / 2] - num);//计算每一个数减去中位数的绝对值,然后相加
}
return sum;
}
}
时间复杂度为O(nlog n),n为数组长度
空间复杂度为O(log n)
第474题 一和零
给你一个二进制字符串数组 strs 和两个整数 m 和 n 。
请你找出并返回 strs 的最大子集的大小,该子集中 最多 有 m 个 0 和 n 个 1 。
如果 x 的所有元素也是 y 的元素,集合 x 是集合 y 的 子集 。
示例 1:
输入 | 输出 |
---|---|
strs = [“10”, “0001”, “111001”, “1”, “0”], m = 5, n = 3 | 4 |
解释:最多有 5 个 0 和 3 个 1 的最大子集是 {“10”,“0001”,“1”,“0”} ,因此答案是 4 。
其他满足题意但较小的子集包括 {“0001”,“1”} 和 {“10”,“1”,“0”} 。{“111001”} 不满足题意,因为它含 4 个 1 ,大于 n 的值 3 。
示例 2:
输入 | 输出 |
---|---|
strs = [“10”, “0”, “1”], m = 1, n = 1 | 2 |
最大的子集是 {“0”, “1”} ,所以答案是 2 。
解题思路
先利用字符串存储所有字符串中0和1的个数,然后利用动态规划计算减去当前遍历到的字符串后的最大子集个数,最后求得一个满足要求的最大子集个数
代码
// 一和零:动态规划
class Solution {
public int findMaxForm(String[] strs, int m, int n) {
int[][] dp = new int[m + 1][n + 1];//存放最大子集个数
int length = strs.length;
for (int i = 0; i < length; i++) {
int[] zerosOnes = getZerosOnes(strs[i]);//获取字符串中的0和1的数量
int zeros = zerosOnes[0], ones = zerosOnes[1];//建立一个数组存0,一个数组存1
for (int j = m; j >= zeros; j--) {//
for (int k = n; k >= ones; k--) {
dp[j][k] = Math.max(dp[j][k], dp[j - zeros][k - ones] + 1);//计算减去当前遍历到的字符串中0和1后的最大子集个数,然后加1(当前遍历的子集)
}
}
}
return dp[m][n];
}
public int[] getZerosOnes(String str) {
int[] zerosOnes = new int[2];
int length = str.length();
for (int i = 0; i < length; i++) {
zerosOnes[str.charAt(i) - '0']++;
}
return zerosOnes;
}
}
时间复杂度为O(lmn+L),l为字符串长度,m,n分别为0和1的容量
空间复杂度为O(mn)
第494题 目标和
给你一个整数数组 nums 和一个整数 target 。
向数组中的每个整数前添加 ‘+’ 或 ‘-’ ,然后串联起所有整数,可以构造一个 表达式 :
例如,nums = [2, 1] ,可以在 2 之前添加 ‘+’ ,在 1 之前添加 ‘-’ ,然后串联起来得到表达式 “+2-1” 。
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。
示例 1:
输入 | 输出 |
---|---|
nums = [1,1,1,1,1], target = 3 | 5 |
解释:一共有 5 种方法让最终目标和为 3 。
-1 + 1 + 1 + 1 + 1 = 3
+1 - 1 + 1 + 1 + 1 = 3
+1 + 1 - 1 + 1 + 1 = 3
+1 + 1 + 1 - 1 + 1 = 3
+1 + 1 + 1 + 1 - 1 = 3
示例 2:
输入 | 输出 |
---|---|
nums = [1], target = 1 | 1 |
解题思路
本题使用动态规划,先判断数组各数和是否超过目标值或者各数和减去目标值是否为偶数,然后遍历数组,判断有几个能够满足各数和减去目标值的一半,将次数返回
代码
// 目标和:动态规划
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int sum = 0;
for (int num : nums) {
sum += num;
}//计算所有数字和
int diff = sum - target;//计算数字和与目标值的差距,如果小于0或者是奇数,则不满足条件
if (diff < 0 || diff % 2 != 0) {
return 0;
}
int neg = diff / 2;//令neg为差距的一半
int[] dp = new int[neg + 1];
dp[0] = 1;
for (int num : nums) {//遍历数组
for (int j = neg; j >= num; j--) {//挨个计算是否选用当前数
dp[j] += dp[j - num];//求得满足neg的所有次数
}
}
return dp[neg];
}
}
时间复杂度为O(n×(sum−target)),n为数组长,sum为数组各数和,target为目标值
空间复杂度为O(sum−target)
第503题 下一个更大元素 II
给定一个循环数组(最后一个元素的下一个元素是数组的第一个元素),输出每个元素的下一个更大元素。数字 x 的下一个更大的元素是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1。
示例 1:
输入 | 输出 |
---|---|
[1,2,1] | [2,-1,2] |
解释: 第一个 1 的下一个更大的数是 2;
数字 2 找不到下一个更大的数;
第二个 1 的下一个最大的数需要循环搜索,结果也是 2。
解题思路
利用栈的特性,后进先出,对栈顶元素与当前遍历到的元素进行比值,如果栈顶元素大,则将当前元素入栈,如果栈顶元素小,则将当前栈顶元素在二维数组中的值记为当前元素。如果栈为空,则直接将当前元素入栈。
代码
// 下一个更大元素 II:栈
class Solution {
public int[] nextGreaterElements(int[] nums) {
int n = nums.length;
int[] ret = new int[n];
Arrays.fill(ret, -1);//先将二维数组的值填入-1
Deque<Integer> stack = new LinkedList<Integer>();
for (int i = 0; i < n * 2 - 1; i++) {
while (!stack.isEmpty() && nums[stack.peek()] < nums[i % n]) {
ret[stack.pop()] = nums[i % n];//如果栈不为空,且栈顶元素比当前元素小,则将栈顶元素弹出栈,并在数组中将栈顶元素的值标为当前元素值
}
stack.push(i % n);//如果栈为空且栈顶元素大于当前元素,则将当前元素入栈
}
return ret;
}
}
时间复杂度为O(n),n为数组长度
空间复杂度为O(n)
第513题 找树左下角的值
给定一个二叉树,在树的最后一行找到最左边的值。
示例 1:
输入 | 输出 |
---|---|
[2,1,3] | 1 |
示例 2:
输入 | 输出 |
---|---|
[1,2,3,4,null,5,6,null,null,7] | 7 |
解题思路
利用递归求解,不断递归判断左右节点是否存在,然后另外一个判断递归的深度,利用deep这个值,确保遍历的每一层存的值是最左边的值。
代码
// 找树左下角的值:递归
class Solution {
private int Deep = -1;
private int value = 0;
public int findBottomLeftValue(TreeNode root) {
value = root.val;
findLeftValue(root,0);
return value;
}
private void findLeftValue (TreeNode root,int deep) {
if (root == null) return;
if (root.left == null && root.right == null) {//如果当前节点的左右节点为空
if (deep > Deep) {//如果深度比之前的大,则更新value,并将Deep更新为当前深度,这是确保存储当前深度第一个节点
value = root.val;
Deep = deep;
}
}
if (root.left != null) findLeftValue(root.left,deep + 1);//如果左节点不为空,则继续递归找左子节点
if (root.right != null) findLeftValue(root.right,deep + 1);//同上
}
}
时间复杂度为O(n),n为树的节点数
空间复杂度为O(deep),树的深度
第518题 零钱兑换 II
给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。
请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。
假设每一种面额的硬币有无限个。
题目数据保证结果符合 32 位带符号整数。
示例 1:
输入 | 输出 |
---|---|
amount = 5, coins = [1, 2, 5] | 4 |
解释:有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1
示例 2:
输入 | 输出 |
---|---|
amount = 3, coins = [2] | 0 |
解释:只用面额 2 的硬币不能凑成总金额 3 。
示例 3:
输入 | 输出 |
---|---|
amount = 10, coins = [10] | 1 |
解题思路
本题使用动态规划,计算每一种硬币组成目标值的可能性。
代码
// 零钱兑换 II:动态规划
class Solution {
public int change(int amount, int[] coins) {
int[] dp = new int[amount + 1];
dp[0] = 1;
for (int coin : coins) {//遍历所有硬币数
for (int i = coin; i <= amount; i++) {
dp[i] += dp[i - coin];//计算使用当前coin能得到i的次数
}
}
return dp[amount];
}
}
时间复杂度为O(amount×n),amount是总金额,n为coins的长度
空间复杂度为O(amount)
第524题 通过删除字母匹配到字典里最长单词
给你一个字符串 s 和一个字符串数组 dictionary 作为字典,找出并返回字典中最长的字符串,该字符串可以通过删除 s 中的某些字符得到。
如果答案不止一个,返回长度最长且字典序最小的字符串。如果答案不存在,则返回空字符串。
示例 1:
输入 | 输出 |
---|---|
s = “abpcplea”, dictionary = [“ale”,“apple”,“monkey”,“plea”] | “apple” |
示例 2:
输入 | 输出 |
---|---|
s = “abpcplea”, dictionary = [“a”,“b”,“c”] | “a” |
解题思路
本题利用双指针求解,让双指针指向两个字符串首,如果指向的字符相同,则同时向后移一位,如果不相等,则将指向字符串y的指针向后移一位,最后判断是否遍历完了字符串x,然后比较遍历完的字符串长度,将最大长度做为输出。
代码
// 通过删除字母匹配到字典里最长单词:双指针
public class Solution {
public boolean isSubsequence(String x, String y) {
int j = 0;
for (int i = 0; i < y.length() && j < x.length(); i++)
if (x.charAt(j) == y.charAt(i))//如果两个字符串当前字符匹配,则同时向后移一位,否则移动字符串y
j++;
return j == x.length();
}
public String findLongestWord(String s, List < String > d) {
String max_str = "";
for (String str: d) {//遍历字符数组
if (isSubsequence(str, s)) {
if (str.length() > max_str.length() || (str.length() == max_str.length() && str.compareTo(max_str) < 0))//将符合条件的字符串最大长度记录
max_str = str;
}
}
return max_str;
}
}
时间复杂度为O(n*x),x为字符串平均长度,n为数组中的字符串数
空间复杂度为O(x)
第538题 把二叉搜索树转换为累加树
给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。
提醒一下,二叉搜索树满足下列约束条件:
节点的左子树仅包含键 小于 节点键的节点。
节点的右子树仅包含键 大于 节点键的节点。
左右子树也必须是二叉搜索树
示例 1:
输入 | 输出 |
---|---|
[4,1,6,0,2,5,7,null,null,null,3,null,null,null,8] | [30,36,21,36,35,26,15,null,null,null,33,null,null,null,8] |
示例 2:
输入 | 输出 |
---|---|
root = [0,null,1] | [1,null,1] |
示例 3:
输入 | 输出 |
---|---|
root = [1,0,2] | [3,3,2] |
示例 4:
输入 | 输出 |
---|---|
root = [3,2,4,1] | [7,9,4,10] |
解题思路
反向中序遍历,先不断遍历右节点,如果没有右节点,则遍历左节点,直到遍历完所有节点。
代码
// 把二叉搜索树转换为累加树:中序遍历
class Solution {
int sum = 0;
public TreeNode convertBST(TreeNode root) {
if (root != null) {
convertBST(root.right);//不断遍历右节点
sum += root.val;
root.val = sum;
convertBST(root.left);//如果当前节点存在左节点,则遍历左节点
}
return root;//实质上是一个反向中序遍历
}
}
时间复杂度为O(n),n为树的节点数
空间复杂度为O(n)
第540题 有序数组中的单一元素
给定一个只包含整数的有序数组,每个元素都会出现两次,唯有一个数只会出现一次,找出这个数。
示例 1:
输入 | 输出 |
---|---|
[1,1,2,3,3,4,4,8,8] | 2 |
示例 2:
输入 | 输出 |
---|---|
[3,3,7,7,10,11,11] | 10 |
解题思路
本题使用二分法,将lo和hi设为数组首尾,计算中间数的位置,如果该位置为奇数,则减1,变成偶数,然后判断该位置与下一位置的值是否相等,如果相等,则单一数在这两个位置之后,因此将lo设在两数后,如果不相等,则说明单一数在两数前,这将hi设为中间数,然后不断判断中间数。
代码
// 有序数组中的单一元素:二分查找
class Solution {
public int singleNonDuplicate(int[] nums) {
int lo = 0;
int hi = nums.length - 1;
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
if (mid % 2 == 1) mid--;//如果中间数是奇数,则将中间数减1
if (nums[mid] == nums[mid + 1]) {//如果前后相等,则将lo放在这两个数之后
lo = mid + 2;
} else {//否则,单一数肯定在中间数之前,则将hi设为中间数
hi = mid;
}
}
return nums[lo];
}
}
时间复杂度为O(logn),n为数组大小
空间复杂度为O(1)
第547题 省份数量
有 n 个城市,其中一些彼此相连,另一些没有相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c 直接相连,那么城市 a 与城市 c 间接相连。
省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。
给你一个 n x n 的矩阵 isConnected ,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,而 isConnected[i][j] = 0 表示二者不直接相连。
返回矩阵中 省份 的数量。
示例 1:
输入 | 输出 |
---|---|
isConnected = [[1,1,0],[1,1,0],[0,0,1]] | 2 |
示例 2:
输入 | 输出 |
---|---|
isConnected = [[1,0,0],[0,1,0],[0,0,1]] | 1 |
解题思路
使用DFS,首先看当前位置是否为1,如果是则看是否被访问,如果未被访问,则标记为true,然后继续搜寻四周是否有未被访问且为1的位置,不断遍历每一行,最后计算未被访问过且是第一遍访问到的城市数。
代码
// 省份数量:DFS
class Solution {
public int findCircleNum(int[][] isConnected) {
int provinces = isConnected.length;
boolean[] visited = new boolean[provinces];
int circles = 0;//城市数
for (int i = 0; i < provinces; i++) {
if (!visited[i]) {//当前位置未被访问过
dfs(isConnected, visited, provinces, i);
circles++;
}
}
return circles;
}
public void dfs(int[][] isConnected, boolean[] visited, int provinces, int i) {
for (int j = 0; j < provinces; j++) {
if (isConnected[i][j] == 1 && !visited[j]) {//如果当前位置未被访问过,且是1,择将该位置标记为true
visited[j] = true;
dfs(isConnected, visited, provinces, j);
}
}
}
}
时间复杂度为O(
n
2
n^2
n2),n为城市数
空间复杂度为O(n)