Java解leetcode,助力面试之中等10道题(二)
第56题 合并区间
以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间。
示例 1:
输入 | 输出 |
---|---|
intervals = [[1,3],[2,6],[8,10],[15,18]] | [[1,6],[8,10],[15,18]] |
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
示例 2:
输入 | 输出 |
---|---|
intervals = [[1,4],[4,5]] | [[1,5]] |
区间 [1,4] 和 [4,5] 可被视为重叠区间。
解题思路
排序,依次比较,如果当前区间的左端点大于集合的右端点,直接加入当前区间,否则将集合最右边区间的右端点更新为两数中的较大值。
代码
// 合并区间:排序
class Solution {
public int[][] merge(int[][] intervals) {
if (intervals.length == 0) {
return new int[0][2];
}//如果集合长度为0,直接返回空集合
Arrays.sort(intervals, new Comparator<int[]>() {
public int compare(int[] interval1, int[] interval2) {
return interval1[0] - interval2[0];//从小到大排序
}
});
List<int[]> merged = new ArrayList<int[]>();
for (int i = 0; i < intervals.length; ++i) {
int L = intervals[i][0], R = intervals[i][1];
if (merged.size() == 0 || merged.get(merged.size() - 1)[1] < L) {
merged.add(new int[]{L, R});//新集合为空或者当前区间的左端点在最后一个区间的右端点之后,则直接将区间加入数组末尾
} else {
merged.get(merged.size() - 1)[1] = Math.max(merged.get(merged.size() - 1)[1], R);
}//否则右区间更新为两个数之间的较大值
}
return merged.toArray(new int[merged.size()][]);
}
}
时间复杂度为O(nlog n),n为区间数
空间复杂度为O(log n)
第57题 插入区间
给你一个 无重叠的 ,按照区间起始端点排序的区间列表。
在列表中插入一个新的区间,你需要确保列表中的区间仍然有序且不重叠(如果有必要的话,可以合并区间)。
示例 1:
输入 | 输出 |
---|---|
intervals = [[1,3],[6,9]], newInterval = [2,5] | [[1,5],[6,9]] |
示例 2:
输入 | 输出 |
---|---|
intervals = [[1,2],[3,5],[6,7],[8,10],[12,16]], newInterval = [4,8] | [[1,2],[3,10],[12,16]] |
这是因为新的区间 [4,8] 与 [3,5],[6,7],[8,10] 重叠。
示例 3:
输入 | 输出 |
---|---|
intervals = [], newInterval = [5,7] | [[5,7]] |
示例 4:
输入 | 输出 |
---|---|
intervals = [[1,5]], newInterval = [2,3] | [[1,5]] |
示例 5:
输入 | 输出 |
---|---|
intervals = [[1,5]], newInterval = [2,7] | [[1,7]] |
解题思路
判断插入区间是在最左侧,还是最右侧,还是与原区间有交集,如果有交集,将左区间小的值设为新的左区间,将右区间大的值设为右区间。
代码
// 插入区间:
class Solution {
public int[][] insert(int[][] intervals, int[] newInterval) {
int left = newInterval[0];
int right = newInterval[1];
boolean placed = false;//替换
List<int[]> ansList = new ArrayList<int[]>();
for (int[] interval : intervals) {
if (interval[0] > right) {//如果原区间的左区间比新区间的右区间大
// 在插入区间的右侧且无交集
if (!placed) {
ansList.add(new int[]{left, right});
placed = true;
}//则将数组插入到最左边
ansList.add(interval);
} else if (interval[1] < left) {
// 在插入区间的左侧且无交集
ansList.add(interval);//将新区间加入最右边
} else {
// 与插入区间有交集,计算它们的并集
left = Math.min(left, interval[0]);
right = Math.max(right, interval[1]);
}
}
if (!placed) {//如果place等于false,代表有交集,则将交集插入
ansList.add(new int[]{left, right});
}
int[][] ans = new int[ansList.size()][2];
for (int i = 0; i < ansList.size(); ++i) {
ans[i] = ansList.get(i);
}
return ans;
}
}
时间复杂度为O(n),n为数组长度
空间复杂度为O(1)
第62题 不同路径
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
示例 1:
输入 | 输出 |
---|---|
m = 3, n = 7 | 28 |
示例 2:
输入 | 输出 |
---|---|
m = 3, n = 2 | 3 |
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
- 向右 -> 向下 -> 向下
- 向下 -> 向下 -> 向右
- 向下 -> 向右 -> 向下
示例 3:
输入 | 输出 |
---|---|
m = 7, n = 3 | 28 |
示例 4:
输入 | 输出 |
---|---|
m = 3, n = 3 | 6 |
解题思路
F[i][j]=F[i-1][j]+F[i][j-1],而第一行和第一列的路径数全为1,因此可以求出所有的路径数。
代码
// 不同路径:动态规划
class Solution {
public int uniquePaths(int m, int n) {
int[][] f = new int[m][n];
for (int i = 0; i < m; ++i) {
f[i][0] = 1;
}//将第一列全设为1,因为只有这一条路径
for (int j = 0; j < n; ++j) {
f[0][j] = 1;
}//将第一行全设为1,因为只有这一条路径
for (int i = 1; i < m; ++i) {
for (int j = 1; j < n; ++j) {
f[i][j] = f[i - 1][j] + f[i][j - 1];
}//当前位置的路径数,取决于左边位置的路径数加上上面位置的路径数
}
return f[m - 1][n - 1];
}
}
时间复杂度为O(mn),m,n分别为矩阵的长和宽
空间复杂度为O(mn)
第64题 最小路径和
给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
示例 1:
输入 | 输出 |
---|---|
grid = [[1,3,1],[1,5,1],[4,2,1]] | 7 |
解释:因为路径 1→3→1→1→1 的总和最小。
示例 2:
输入 | 输出 |
---|---|
grid = [[1,2,3],[4,5,6]] | 12 |
解题思路
类似62题,只是在每条路径上求一个最小的路径和,相当于加了一个权重一样。
代码
// 最小路径和:动态规划
class Solution {
public int minPathSum(int[][] grid) {
if (grid == null || grid.length == 0 || grid[0].length == 0) {
return 0;
}//行、列以及长度为0,则返回0
int rows = grid.length, columns = grid[0].length;
int[][] dp = new int[rows][columns];
dp[0][0] = grid[0][0];
for (int i = 1; i < rows; i++) {
dp[i][0] = dp[i - 1][0] + grid[i][0];
}//第一列的路径和
for (int j = 1; j < columns; j++) {
dp[0][j] = dp[0][j - 1] + grid[0][j];
}//第一行的路径和
for (int i = 1; i < rows; i++) {
for (int j = 1; j < columns; j++) {
dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
}//每一个位置的最小路径和等于一个最小值,最小值由上面位置的路径和以及左边位置的路径和加上当前位置的路径产生
}
return dp[rows - 1][columns - 1];
}
}
时间复杂度为O(mn),m,n分别为矩阵的行和列
空间复杂度为O(mn)
第74题 搜索二维矩阵
编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性:
每行中的整数从左到右按升序排列。
每行的第一个整数大于前一行的最后一个整数。
示例 1:
输入 | 输出 |
---|---|
matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 3 | true |
示例 2:
输入 | 输出 |
---|---|
matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 13 | false |
解题思路
利用二分法,因为数据是一个升序的,所以直接直接查找中间值,不断找中间值,直到得到最终答案是否存在。
代码
// 搜索二维矩阵:二分查找
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int m = matrix.length, n = matrix[0].length;
int low = 0, high = m * n - 1;//矩阵的第一个位置和最后一个位置
while (low <= high) {
int mid = (high - low) / 2 + low;//矩阵的中间位置
int x = matrix[mid / n][mid % n];//得到中间位置所在行和列
if (x < target) {
low = mid + 1;
} else if (x > target) {
high = mid - 1;
} else {
return true;
}
}
return false;
}
}
时间复杂度为O(log mn),m,n为矩阵得行和列
空间复杂度为O(1)
第75题 颜色分类
给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
示例 1:
输入 | 输出 |
---|---|
nums = [2,0,2,1,1,0] | [0,0,1,1,2,2] |
示例 2:
输入 | 输出 |
---|---|
nums = [2,0,1] | [0,1,2] |
示例 3:
输入 | 输出 |
---|---|
nums = [0] | [0] |
示例 4:
输入 | 输出 |
---|---|
nums = [1] | [1] |
解题思路
设立双指针,左指针与遍历到的0交换,右指针与遍历到的2交换。
代码
// 颜色分类:双指针
class Solution {
public void sortColors(int[] nums) {
int n = nums.length;
int p0 = 0, p2 = n - 1;//双指针,一个指向数组首部,一个指向数组尾部
for (int i = 0; i <= p2; ++i) {
while (i <= p2 && nums[i] == 2) {//遍历数组,当遍历到的数值为2时
int temp = nums[i];
nums[i] = nums[p2];
nums[p2] = temp;
--p2;
}//将右指针与当前位置交换,将右指针左移
if (nums[i] == 0) {//当遍历到的数组为0时
int temp = nums[i];
nums[i] = nums[p0];
nums[p0] = temp;
++p0;
}//将左指针与当前位置交换,将左指针右移
}
}
}
时间复杂度为O(n),n为数组长度
空间复杂度为O(1)
第77题 组合
给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。
示例 1:
输入 | 输出 |
---|---|
n = 4, k = 2 | [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4],] |
解题思路
采用dfs配合递归找寻解,如果加入的数使temp的长度等于k,则加入结果,否则继续dfs调用下一个数。
代码
// 组合:递归
class Solution {
List<Integer> temp = new ArrayList<Integer>();
List<List<Integer>> ans = new ArrayList<List<Integer>>();
public List<List<Integer>> combine(int n, int k) {
dfs(1, n, k);
return ans;
}//深度搜索
public void dfs(int cur, int n, int k) {
// 剪枝:temp 长度加上区间 [cur, n] 的长度小于 k,不可能构造出长度为 k 的 temp
if (temp.size() + (n - cur + 1) < k) {
return;
}
// 记录合法的答案
if (temp.size() == k) {
ans.add(new ArrayList<Integer>(temp));
return;
}
// 考虑选择当前位置
temp.add(cur);//将当前值加入temp数组
dfs(cur + 1, n, k);//然后用dfs遍历下一个数
temp.remove(temp.size() - 1);//该位置不做为选择位置,则不加入当前位置的值
// 考虑不选择当前位置
dfs(cur + 1, n, k);
}
}
时间复杂度为O((n k)×k),m,n为数组的长度
空间复杂度为O(n)
第78题 子集
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:
输入 | 输出 |
---|---|
nums = [1,2,3] | [[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]] |
示例 2:
输入 | 输出 |
---|---|
nums = [0] | [[],[0]] |
解题思路
遍历数组,首先输出空数组,然后当子集中的数为1个时,最后到数组长度大小,将他们全部输出。
代码
// 子集:迭代
class Solution {
List<Integer> t = new ArrayList<Integer>();
List<List<Integer>> ans = new ArrayList<List<Integer>>();
public List<List<Integer>> subsets(int[] nums) {//建立哈希集合
int n = nums.length;
for (int mask = 0; mask < (1 << n); ++mask) {//考虑每个长度的输出
t.clear();//清除之前的结果
for (int i = 0; i < n; ++i) {
if ((mask & (1 << i)) != 0) {
t.add(nums[i]);//将数组合输出
}
}
ans.add(new ArrayList<Integer>(t));//将遍历的结果存入ans中
}
return ans;
}
}
时间复杂度为O(n×2^n),n为数组的大小
空间复杂度为O(n)
第79题 单词搜索
给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
示例 1:
输入 | 输出 |
---|---|
board = [[“A”,“B”,“C”,“E”],[“S”,“F”,“C”,“S”],[“A”,“D”,“E”,“E”]], word = “ABCCED” | true |
示例 2:
输入 | 输出 |
---|---|
board = [[“A”,“B”,“C”,“E”],[“S”,“F”,“C”,“S”],[“A”,“D”,“E”,“E”]], word = “SEE” | true |
示例 3:
输入 | 输出 |
---|---|
board = [[“A”,“B”,“C”,“E”],[“S”,“F”,“C”,“S”],[“A”,“D”,“E”,“E”]], word = “ABCB” | false |
解题思路
设一个check函数查找当前字母是否对应上word里的字母,设一个visit代表该字母是否已被选用,初始化为false,从头开始遍历,如果遇见当前字母等于word首字母,则将visit变为true,直到遍历到最后一个字母都相等,则代表单词搜索成功,如果从第一个字母一直遍历到最后一个字母仍然不能形成word,则代表单词搜索失败。
代码
// 单词搜索:
class Solution {
public boolean exist(char[][] board, String word) {
int h = board.length, w = board[0].length;
boolean[][] visited = new boolean[h][w];
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
boolean flag = check(board, visited, i, j, word, 0);//搜索每一个位置是否存在解
if (flag) {
return true;
}
}
}
return false;
}
public boolean check(char[][] board, boolean[][] visited, int i, int j, String s, int k) { //判断从该位置是否能找到解
if (board[i][j] != s.charAt(k)) {
return false;//如果当前位置不等于字符串首字母,则返回false
} else if (k == s.length() - 1) {
return true;
}//如果遍历的长度等于单词长度,则代表查找成功
visited[i][j] = true;//将当前搜索字母设为true,然后搜索四周字母
int[][] directions = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};//四个方向
boolean result = false;//将result先置为false
for (int[] dir : directions) {
int newi = i + dir[0], newj = j + dir[1];//新方向
if (newi >= 0 && newi < board.length && newj >= 0 && newj < board[0].length) {//在board内
if (!visited[newi][newj]) {//如果新位置未被使用过,就递归调用check函数判断是否是对应字母,如果不是则换一个方向找,直到4个方向都不是,则进入下一位置
boolean flag = check(board, visited, newi, newj, s, k + 1);
if (flag) {
result = true;
break;
}
}
}
}
visited[i][j] = false;
return result;
}
}
时间复杂度为O(MN⋅3^L),m,n为行列,L为字符串长度
空间复杂度为O(mn)
第90题 子集 II
给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
示例 1:
输入 | 输出 |
---|---|
nums = [1,2,2] | [[],[1],[1,2],[1,2,2],[2],[2,2]] |
示例 2:
输入 | 输出 |
---|---|
nums = [0] | [[],[0]] |
解题思路
先排序数组,然后将数组大小为0,1一直到数组长度的集合依次加入到结果中,如果出现重复数字,则跳过,将遍历到的数字设为true,遍历下一数字,如果长度达到要求,则将当前长度的全部加入结果,再访问当前长度加1的数字组合。
代码
// 子集 II:递归
class Solution {
List<Integer> t = new ArrayList<Integer>();
List<List<Integer>> ans = new ArrayList<List<Integer>>();
public List<List<Integer>> subsetsWithDup(int[] nums) {
Arrays.sort(nums);//排序
dfs(false, 0, nums);
return ans;
}
public void dfs(boolean choosePre, int cur, int[] nums) {
if (cur == nums.length) {//将数组长度从0到最大长度中所有可能加入结果
ans.add(new ArrayList<Integer>(t));
return;
}
dfs(false, cur + 1, nums);//搜索下一长度的可能
if (!choosePre && cur > 0 && nums[cur - 1] == nums[cur]) {//重复数字
return;
}
t.add(nums[cur]);//将当前数字加入t中
dfs(true, cur + 1, nums);//将当前数字设为true,遍历下一数字
t.remove(t.size() - 1);//剪枝
}
}
时间复杂度为O(n·2^n),n为数组长度
空间复杂度为O(n)