徒手挖地球二二周目
NO.41 缺失的第一个正数 困难
思路一:两次遍历 第一次遍历将每个元素交换到其元素值对应的下标出,第二次遍历检查每个元素的值和其下标是否相等,如不相等则这个下标就是缺失的第一个正数。
-
第一次遍历:
- 将所有符合
nums[i]大于0且小于length
的元素交换到其值对应的下标位置,例如2应当交换到nums[2]的位置、6应当交换到nums[6]的位置。 - 如果
nums[i]==nums[nums[i]]
则不需要移动,例如nums[0]==3但是nums[3]==3就不需要移动了。 - 交换之后再检查nums[i]是否依然需要交换,如果需要交换继续交换,直至nums[i]无需交换再继续向后遍历。
- 将所有符合
-
第二次遍历:
- 从1开始,如果
nums[i]!=i
则i就是缺失的第一个正数。
- 从1开始,如果
-
两次遍历结束后:
- 如果没有找到缺失的第一个正数,就检查
nums[0]==length
如果相等则返回length+1,否则返回length
- 如果没有找到缺失的第一个正数,就检查
public int firstMissingPositive(int[] nums) {
if (nums==null||nums.length==0)return 1;
for (int i = 0; i < nums.length; i++) {
while (nums[i]>0&&nums[i]<nums.length&&nums[i]!=nums[nums[i]]){
int temp=nums[nums[i]];
nums[nums[i]]=nums[i];
nums[i]=temp;
}
}
for (int i = 1; i < nums.length; i++) {
if (nums[i]!=i)return i;
}
if (nums[0]==nums.length)return nums.length+1;
return nums.length;
}
时间复杂度:O(n) 这道题所用的方法比较简单,重点要学习其中的抽屉思想。
NO.42 接雨水 困难
思路一:按行计算法 计算每一行接的水,相加得到一共接的水。
用一个变量temp记录第i行的水,遍历每个"墙"的高度,遇到第一个大于等于i
的"墙"启动temp准备记录;继续遍历遇到小于i
的"墙"temp++
,否则遇到大于等于i
的"墙"ans加上temp并归零temp
。
public int trap(int[] height) {
if (height==null||height.length<3)return 0;
int ans=0,maxHeight=height[0];
for (int h : height)if (h>maxHeight)maxHeight=h;
for (int row = 1; row <= maxHeight; row++) {
int temp=0;
//是否开始统计temp
boolean isStart=false;
for (int i = 0; i < height.length; i++) {
if (isStart&&height[i]<row)temp++;
if (height[i]>=row){
ans+=temp;
temp=0;
isStart=true;
}
}
}
return ans;
}
时间复杂度:O(maxHeight*n) 此方法在leetcode上会超时!
思路二:按列计算法 计算出每一列上有多少水,相加得到一共接的水。
想知道第i列上是否有水,我们需要找到第i列的左边和右边最高的"墙",因为只有形成凹点才能存水;并且"木桶效应"告诉我们存了多少水,只需要考虑第i列左边和右边找到的最高的"墙"中最矮的一个。
第i列的高度x
和我们找到的左边和右边最高的"墙"中最矮的一个"墙"的高度y
会有三种关系:
x<y
,第i列上存的水应该是y-x。x>y
,第i列上一定接不到水。x=y
,第i列上依然是不能接到水。
知道了这些,编码就很简单了,遍历每一列的同时找到当前列左边和右边最高的两面"墙"中最矮的一个和当前列进行比较,对上述三种情况进行处理即可。
public int trap(int[] height) {
if (height==null||height.length<3)return 0;
int ans=0;
for (int col = 0; col < height.length; col++) {
//找到col左右最高的墙
int leftMax=0;
for (int i = col-1; i >=0; i--) leftMax=Math.max(leftMax,height[i]);
int rightMax=0;
for (int i = col+1; i < height.length; i++) rightMax=Math.max(rightMax,height[i]);
//如果最高的墙中最矮的一个大于col的高度,计算当前列上接的水并加入结果
if (Math.min(leftMax,rightMax)>height[col])ans+=Math.min(leftMax,rightMax)-height[col];
}
return ans;
}
时间复杂度:O(n^2)
思路三:动态规划优化按列计算法 思路二中每次寻找第i列的左右最高墙的时候都需要遍历一次整个数组。
思路三就是针对这点进行优化。空间换时间。
以题目中实例来演示:
每次寻找第i列左边做高的"墙",只需要将"已经记录的i-1列左边最高的墙和i-1列进行比较,取大的即可"。避免了每次遍历整个数组。
寻找第i列右边做高的"墙"同理,只需要将"已经记录的i+1列右边最高的墙和i+1列进行比较,取大的即可"。
所以leftMax[i]的含义就是第i列左边最高的"墙",rightMax[i]的含义就是第i列右边最高的"墙"(不含第i列本身)。
初始化:顺序遍历一次填写leftMax数组,逆序遍历一次填写rightMax数组。
有了leftMax和rightMax之后,然后计算每一列上接的水和思路二中方法一样。
public int trap(int[] height) {
if (height==null||height.length<3)return 0;
//计算leftMax,rightMax
int[] leftMax=new int[height.length],rightMax=new int[height.length];
for (int i = 1; i < height.length; i++) leftMax[i]=Math.max(leftMax[i-1],height[i-1]);
for (int i=height.length-2;i>=0;i--) rightMax[i]=Math.max(rightMax[i+1],height[i+1]);
//计算每一列上的水
int ans=0;
for (int i = 1; i < height.length-1; i++) {
int min=Math.min(leftMax[i],rightMax[i]);
if (min>height[i]){
ans+=min-height[i];
}
}
return ans;
}
时间复杂度:O(n) 三次遍历
空间复杂度:O(n)
思路四:双指针优化动态规划法 双指针主要是对思路三中的空间复杂度进行优化。
上述方法中,两个数组中的每个元素在计算每一列上的水的时候只会被使用一次。所以完全可以用两个int类型的变量实现。
依然是用题目中的实例来演示:
就按照上述方式计算出每一列上的水并加入结果,直至left>right结束。
双指针法虽然是沿用了思路二思路三的方式去找左右最高"墙",但是为什么计算leftMax和rightMax的位置相差这么远依然可以有效判断呢?
例如,对于left墙height[left],如果leftMax比height[left]高。那么如果rightMax比leftMax高,那么就说明left右边一定存在比height[left]高的墙,那么left列上面一定可以接到水。
即使rightMax对于left右边来说不是最高的墙也无所谓,因为如果不是最高的墙,那么同样存在另一个比height[left]高的墙,那么left列上同样可以接到水,且接水量同样是leftMax-height[left]。
public int trap(int[] height) {
if (height==null||height.length<3)return 0;
int leftMax=0,rightMax=0,ans=0,left=1,right=height.length-2;
while (left<=right){
//计算更新leftMax rightMax
leftMax=Math.max(leftMax,height[left-1]);
rightMax=Math.max(rightMax,height[right+1]);
//比较leftMax和rightMax,找到较低的那一侧
if (leftMax<rightMax){
if (leftMax>height[left])ans+=leftMax-height[left];
left++;
}else {
if (rightMax>height[right])ans+=rightMax-height[right];
right--;
}
}
return ans;
}
时间复杂度:O(n)
本人菜鸟,有错误请告知,感激不尽!