【LeetCode】 41 - 50题解
41. 缺失的第一个正数
给你一个未排序的整数数组,请你找出其中没有出现的最小的正整数。
示例 1:
输入: [1,2,0]
输出: 3
示例 2:
输入: [3,4,-1,1]
输出: 2
示例 3:
输入: [7,8,9,11,12]
输出: 1
提示:
你的算法的时间复杂度应为O(n),并且只能使用常数级别的额外空间。
哈希表法
// 时间复杂度 O(N) 空间复杂度 O(N)
public int firstMissingPositive(int[] nums) {
int n = nums.length;
Set<Integer> set = new HashSet<>();
for(int num : nums) set.add(num);
for(int i = 1; i <= n; i ++){
if(!set.contains(i)) return i;
}
return n + 1;
}
排序法
// 时间复杂度 O(Nlog(N)) 空
public int firstMissingPositive(int[] nums) {
// 先排序 Nlog(N)
Arrays.sort(nums);
int pre = 0;
for(int i = 0; i < nums.length; i ++){
// 跳过非正整数和重复值
if(nums[i] <= 0 || nums[i] == pre) continue;
// 找到第一个突变的元素
else if(nums[i] > pre + 1) break;
pre ++;
}
return pre + 1;
}
原地哈希法
// 时间复杂度 O(N) 空间复杂度 O(1)
// 原地哈希: 自定义哈希函数 将数值 i 映射到 i - 1的位置上
public int firstMissingPositive(int[] nums) {
int len = nums.length;
for(int i = 0; i < len; i ++){
// 在指定范围内, 且没有放在正确位置上, 交换
while(nums[i] > 0 && nums[i] <= len && nums[nums[i] - 1] != nums[i]){
swap(nums, nums[i] - 1, i);
}
}
// 找到不符合的位置
for(int i = 0; i < len; i ++){
if(nums[i] != i + 1) return i + 1;
}
// 都正确则返回数组长度 + 1
return len + 1;
}
void swap(int[] nums, int i, int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
42. 接雨水
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 感谢 Marcos 贡献此图。
示例:
输入: [0,1,0,2,1,0,1,3,2,1,2,1]
输出: 6
暴力求解
// 时间复杂度 O(N^2)
public int trap(int[] height) {
int res = 0;
// 遍历每一列柱子
for(int i = 1; i < height.length - 1; i ++){
// 分别记录当前柱子向左 向右的最大高度
int leftMax = 0, rightMax = 0;
for(int j = 0; j <= i; j ++)
leftMax = Math.max(leftMax, height[j]);
for(int j = i; j < height.length; j ++)
rightMax = Math.max(rightMax, height[j]);
// 当前位置 water[i] = min(max(h[0->i], h(i->len-1))) - h[i]
res += Math.min(leftMax, rightMax) - height[i];
}
return res;
}
动态规划
// 时间复杂度 优化至 O(N) , 但空间复杂度为 O(N)
public int trap(int[] height) {
if(height.length == 0) return 0;
int n = height.length;
// 优化方案: 创建两个备忘录,分别记录leftMax 和 rightMax
int[] leftMax = new int[n];
int[] rightMax = new int[n];
int res = 0;
// 初始化
leftMax[0] = height[0];
rightMax[n - 1] = height[n - 1];
for(int i = 1; i < n; i ++)
leftMax[i] = Math.max(height[i], leftMax[i - 1]);
for(int i = n - 2; i >= 0; i --)
rightMax[i] = Math.max(height[i] , rightMax[i + 1]);
for(int i = 1; i < n - 1; i ++)
res += Math.min(leftMax[i], rightMax[i]) - height[i];
return res;
}
双指针
// 时间复杂度 O(N) 空间 O(1)
// 省去创建备忘录的空间, 边走变算
public int trap(int[] height) {
if(height.length == 0) return 0;
int n = height.length;
int l = 0, r = height.length - 1, res = 0;
int leftMax = height[0], rightMax = height[n - 1];
while(l <= r){
leftMax = Math.max(leftMax, height[l]);
rightMax = Math.max(rightMax, height[r]);
if(leftMax < rightMax)
res += leftMax - height[l ++];
else
res += rightMax - height[r --];
}
return res;
}
43. 字符串相乘
给定两个以字符串形式表示的非负整数 num1
和 num2
,返回 num1
和 num2
的乘积,它们的乘积也表示为字符串形式。
示例 1:
输入: num1 = "2", num2 = "3"
输出: "6"
示例 2:
输入: num1 = "123", num2 = "456"
输出: "56088"
说明:
num1
和num2
的长度小于110。num1
和num2
只包含数字0-9
。num1
和num2
均不以零开头,除非是数字 0 本身。- 不能使用任何标准库的大数类型(比如 BigInteger)或直接将输入转换为整数来处理。
/**
num1的第i位(高位从0开始)和num2的第j位相乘的结果在乘积中的位置是[i+j, i+j+1]
例: 123 * 45, 123的第1位 2 和45的第0位 4 乘积 08 存放在结果的第[1, 2]位中
index: 0 1 2 3 4
1 2 3
* 4 5
---------
1 5
1 0
0 5
---------
0 6 1 5
1 2
0 8
0 4
---------
0 5 5 3 5
这样我们就可以单独都对每一位进行相乘计算把结果存入相应的index中
**/
public String multiply(String num1, String num2) {
int m = num1.length(), n = num2.length();
// 记录每位数字的数组
int[] arr = new int[m + n];
// 从个位开始计算
for(int i = m - 1; i >= 0; i --){
for(int j = n - 1; j >= 0; j --){
// i+j, i+j+1
int mul = (num1.charAt(i) - '0') * (num2.charAt(j) - '0');
int p1 = i + j, p2 = i + j + 1;
// 本身乘积 + 原来的数
int sum = mul + arr[p2];
// 取个位
arr[p2] = sum % 10;
// 进位
arr[p1] += sum / 10;
}
}
// 移除前导零,只需记录第一个非0的位置即可
int i = 0;
while(i < arr.length && arr[i] == 0) i ++;
if(i == arr.length) return "0";
StringBuilder res = new StringBuilder();
while(i < arr.length){
res.append(arr[i++]);
}
return res.toString();
}
44. 通配符匹配
给定一个字符串 (s
) 和一个字符模式 (p
) ,实现一个支持 '?'
和 '*'
的通配符匹配。
'?' 可以匹配任何单个字符。
'*' 可以匹配任意字符串(包括空字符串)。
两个字符串完全匹配才算匹配成功。
说明:
s
可能为空,且只包含从a-z
的小写字母。p
可能为空,且只包含从a-z
的小写字母,以及字符?
和*
。
示例 1:
输入:
s = "aa"
p = "a"
输出: false
解释: "a" 无法匹配 "aa" 整个字符串。
示例 2:
输入:
s = "aa"
p = "*"
输出: true
解释: '*' 可以匹配任意字符串。
示例 3:
输入:
s = "cb"
p = "?a"
输出: false
解释: '?' 可以匹配 'c', 但第二个 'a' 无法匹配 'b'。
示例 4:
输入:
s = "adceb"
p = "*a*b"
输出: true
解释: 第一个 '*' 可以匹配空字符串, 第二个 '*' 可以匹配字符串 "dce".
示例 5:
输入:
s = "acdcb"
p = "a*c?b"
输出: false
// f[i][j] = 标识 s的 0 - i 与 p的 0 - j 是否匹配
public boolean isMatch(String s, String p) {
int m = s.length(), n = p.length();
boolean[][] f = new boolean[m + 1][n + 1];
// 空串匹配
f[0][0] = true;
// s为空,只要p的开头是* 就代表匹配
for(int j = 1; j <= n; j ++){
if(p.charAt(j - 1) == '*') f[0][j] = true;
else break;
}
for(int i = 1; i <= m; i ++){
for(int j = 1; j <= n; j ++){
// f[i][j - 1]的情况 标识 ab和ab*
// f[i - 1][j]的情况 标识 abcd 和 ab*
if(p.charAt(j - 1) == '*'){
f[i][j] = f[i][j - 1] || f[i - 1][j];
}else if(p.charAt(j - 1) == '?' || s.charAt(i - 1) == p.charAt(j - 1)){
f[i][j] = f[i - 1][j - 1];
}
}
}
return f[m][n];
}
45. 跳跃游戏 II
给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
你的目标是使用最少的跳跃次数到达数组的最后一个位置。
示例:
输入: [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。
从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。
说明:
假设你总是可以到达数组的最后一个位置。
https://leetcode-cn.com/problems/jump-game-ii/solution/45-by-ikaruga/
贪心算法
public int jump(int[] nums) {
int ans = 0, start = 0, end = 1;
while(end < nums.length){
int maxPos = 0;
for(int i = l; i < end; i ++){
//能跳到的最远的距离
maxPos = Math.max(maxPos, i + nums[i]);
}
//下一次起跳点范围开始的格子
start = end;
//下一次起跳点范围结束的格子
end = maxPos + 1;
ans ++;
}
return ans;
}
46. 全排列
给定一个 没有重复 数字的序列,返回其所有可能的全排列。
示例:
输入: [1,2,3]
输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
boolean[] used;
public List<List<Integer>> permute(int[] nums) {
used = new boolean[nums.length];
dfs(nums);
return res;
}
void dfs(int[] nums){
if(path.size() == nums.length) {
res.add(new ArrayList<>(path));
return;
}
for(int i = 0; i < nums.length; i ++){
if(!used[i]){
used[i] = true;
path.add(nums[i]);
dfs(nums);
path.remove(path.size() - 1);
used[i] = false;
}
}
}
}
47. 全排列 II
给定一个可包含重复数字的序列,返回所有不重复的全排列。
示例:
输入: [1,1,2]
输出:
[
[1,1,2],
[1,2,1],
[2,1,1]
]
我们需要明确,在这个DFS的过程中,哪里出现了重复?在这个图中,已经十分明显地看到:
- 这次搜索的起点和上次的起点相同 , visited[i - 1] == visited[i]。
- 且上一次的数已经被撤销,visited[i-1] ==false。
为保证i-1不越界,加上i>0的条件:
if (i > 0 && nums[i] == nums[i - 1] && !visited[i - 1]) {
continue;
}
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
boolean[] visited;
public List<List<Integer>> permuteUnique(int[] nums) {
visited = new boolean[nums.length];
Arrays.sort(nums); //保证数组的有序性
dfs(nums);
return res;
}
void dfs(int[]nums){
if(path.size() == nums.length) {
res.add(new ArrayList<>(path));
return;
}
for(int i = 0; i < nums.length ; i++){
if( visited[i]) continue;
if( i > 0 && nums[i] == nums[i - 1] && !visited[i - 1]) continue; //此处剪枝
visited[i] = true;
path.add(nums[i]);
dfs(nums);
visited[i] = false;
path.remove(path.size()-1);
}
}
48. 旋转图像
给定一个 n × n 的二维矩阵表示一个图像。
将图像顺时针旋转 90 度。
说明:
你必须在原地旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要使用另一个矩阵来旋转图像。
示例 1:
给定 matrix =
[
[1,2,3],
[4,5,6],
[7,8,9]
],
原地旋转输入矩阵,使其变为:
[
[7,4,1],
[8,5,2],
[9,6,3]
]
示例 2:
给定 matrix =
[
[ 5, 1, 9,11],
[ 2, 4, 8,10],
[13, 3, 6, 7],
[15,14,12,16]
],
原地旋转输入矩阵,使其变为:
[
[15,13, 2, 5],
[14, 3, 4, 1],
[12, 6, 8, 9],
[16, 7,10,11]
]
public void rotate(int[][] matrix) {
int n = matrix.length;
//转置
/*
1 2 3 --> 1 4 7
4 5 6 --> 2 5 8
7 8 9 --> 3 6 9
*/
for(int i = 0; i < n; i ++){
for(int j = i; j < n; j ++){
swap(matrix, i, j, j, i);
}
}
/*
1 4 7 --> 7 4 1
2 5 8 --> 8 5 2
3 6 9 --> 9 6 3
*/
for(int i = 0; i < n; i ++){
for(int j = 0; j < n / 2; j ++){
swap(matrix, i, j, i, n - j - 1);
}
}
}
void swap(int[][] m, int i1, int j1, int i2, int j2){
int temp = m[i1][j1];
m[i1][j1] = m[i2][j2];
m[i2][j2] = temp;
}
49. 字母异位词分组
给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。
示例:
输入: ["eat", "tea", "tan", "ate", "nat", "bat"]
输出:
[
["ate","eat","tea"],
["nat","tan"],
["bat"]
]
说明:
- 所有输入均为小写字母。
- 不考虑答案输出的顺序。
public List<List<String>> groupAnagrams(String[] strs) {
//O(N KlogK) N = strs.length K = str.length()
if(strs.length == 0) return new ArrayList<>();
Map<String, List> hash = new HashMap<>();
for(String str : strs){
// 字符排序
char[] chs = str.toCharArray();
Arrays.sort(chs);
String key = new String(chs);
if(!hash.containsKey(key)) hash.put(s, new ArrayList<List>());
hash.get(key).add(str);
}
return new ArrayList(hash.values());
}
50. Pow(x, n)
实现 pow(x, n) ,即计算 x 的 n 次幂函数。
示例 1:
输入: 2.00000, 10
输出: 1024.00000
示例 2:
输入: 2.10000, 3
输出: 9.26100
示例 3:
输入: 2.00000, -2
输出: 0.25000
解释: 2-2 = 1/22 = 1/4 = 0.25
说明:
- -100.0 < x < 100.0
- n 是 32 位有符号整数,其数值范围是 [−231, 231 − 1] 。
https://leetcode-cn.com/problems/powx-n/solution/50-powx-n-kuai-su-mi-qing-xi-tu-jie-by-jyd/
public double myPow(double x, int n) {
if(x == 0.0f) return 0.0d;
long b = n;
double res = 1.0;
// 当n < 0时,转化为 n >= 0的情况
if(b < 0){
x = 1 / x;
b = -b;
}
while(b > 0){
if((b & 1) == 1) // b % 2 == 1
res *= x;
x *= x; // x = x ^ 2
b >>= 1; // b = b // 2
}
return res;
}