Leetcode数组部分问题及解决
011 盛最多水的容器
1.利用双重for循环解决
public static int maxArea(int[] height){
int n = height.length;
int max = 0;
int now_intent = 0;
for (int i = 0; i < n; i++) {
for (int j = i+1; j < n; j++) {
now_intent = (j-i)*(Math.min(height[i], height[j]));
if(now_intent>max)
max = now_intent;
}
}
return max;
}
- 结果:通过55/60;
- 超出时间限制
- O(n^2)
2.双指针
- 左右最两端分别设置指针 left & right
- 左右两端指针之间的长度为 len
- 容器初始容量: Math.min(left,right)*len
- 指针移动:低的一端指针向内移动;
- 将每次比较的最大值保存下来
指针移动:假设 Math.min(left,right) X len
结果为 left X len。若移动的是更高的一端的指针,则下一次的结果一定会小于 left X len,
因为下一次:len-1 < len(容器底部变小了) , Math.min(left , right) <= left(容器两端最低处不会超过上一次的最低处);
因此,指针移动应该干掉低端的,在容器底部变小的情况下,想办法让”短板“变长。
public static int maxArea2(int[] height){
int left = 0;
int right = height.length-1;
int max = 0;
int now_intent = 0;
while(left<right){
now_intent = Math.min(height[left],height[right])*(right-left);
if(now_intent>max)
max = now_intent;
if (height[left] < height[right])
left++;
else
right--;
}
return max;
}
- 结果:O(n)
015 三数之和
思路(将题目隐式地转为两数之和):
- 首先,示例2、3的输出[ ]中括号的情况,是返回了空的
List<List<Integer>> resultList
的结果 - 满足条件后,将数组先进行从小到大的排序,若最小的数也大于0,则没有符合条件的结果
- 两层循环:
- 将外层循环的值保存为first;
- 内层循环中,设置一个set数组(去重\second的值在其中查找)。内层循环中每次取出一个值记录为third(因为这样可以使first<second<third);
- 在set中查找是否有值等于 -(first+third),即记录为second的值
- 没有,则把当前third存到set数组中(后续second的值从set中找)
- 有,则添加这三个数到resultList中,按大小顺序。不用退出当前的循环,因为后续可能有其他的组合方式;
需要注意的问题:
- 外层循环中的first值:如果本次的first值和上一次first值相同,则应该跳过本次循环。因为在上一次循环时,已经考虑完了所有的情况。如果这次再继续,则会发生重复的结果。
- 内层循环中,set中找到想要的值后,如果下一次的third值和本次一样,那么应该跳过下一次的third值,否则会产生和本次相同的结果。
public static List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> resultList = new ArrayList<>();
if(nums.length<3)
return resultList;
Arrays.sort(nums);
for(int i = 0; i< nums.length;i++){
//当前排序下的第一个大于零,则后续一定都大于零
if(nums[i]>0)
break;
int first = nums[i];
//如果本次循环的first和上次一样,则跳过本次循环
if(i>0 && first == nums[i-1])
continue;
Set<Integer> set = new HashSet<>();
for(int j = i+1; j< nums.length ;j++){
int third = nums[j];
int second = -(first+third);
if(set.contains(second)){
resultList.add(Arrays.asList(first,second,third));
while(j < nums.length-1 && third == nums[j+1]) {j++; continue;}
}
set.add(third);
}
}
return resultList;
}
public static void main(String[] args) {
// int[] nums = {-1,-1,0,1,2,3};
// int sum = Arrays.stream(nums).sum();
int[] nums={0,0,0,0}; //超出时间,67行应添加j++
List<List<Integer>> lists = threeSum(nums);
System.out.println(lists);
}
- 结果:O(n^2)
016 最接近的三数之和
思路:
- 数组排序
- 三重循环
- 当每层循环的和大于target时,就比较abs(result,target)和abs(当前结果,target),更新result,并退出当前这层循环
public static int threeSumClosest(int[] nums, int target) {
//从小到大排序
Arrays.sort(nums);
int result = nums[0]+nums[1]+nums[2];
//最小的也大于目标值
if(result>target)
return result;
for (int i = 0; i < nums.length-2; i++) {
if(nums[i]+nums[i+1]+nums[i+2]>target){
if(Math.abs(result-target)>Math.abs(nums[i]+nums[i+1]+nums[i+2]-target))
result = nums[i]+nums[i+1]+nums[i+2];
break;
}
for (int j = i+1; j < nums.length-1; j++) {
if(nums[i]+nums[j]+nums[j+1]>target){
if(Math.abs(result-target)>Math.abs(nums[i]+nums[j]+nums[j+1]-target))
result = nums[i]+nums[j]+nums[j+1];
break;
}
for (int k = j+1; k < nums.length; k++) {
if(nums[i]+nums[j]+nums[k]>target){
if(Math.abs(result-target)>Math.abs(nums[i]+nums[j]+nums[k]-target))
result = nums[i]+nums[j]+nums[k];
break;
}
if(nums[i]+nums[j]+nums[k]==target)
//如果差为0;则退出循环
return target;
if(Math.abs(result-target)>Math.abs(nums[i]+nums[j]+nums[k]-target))
result = nums[i]+nums[j]+nums[k];
}
}
}
return result;
}
运行结果:
- 326 / 381
- 超出时间限制
思路2:
- 参考三数之和的思路:排序+双指针
- 外层循环用于定下first,内层first右侧的数组左右两端设置指针left&right
- 比较first+nums[left]+nums[right]的值与target的大小:
- first+nums[left]+nums[right] > target —>使值变小—> right–
- first+nums[left]+nums[right] < target —>使值变大—> left++
public static int threeSumClosest2(int[] nums, int target) {
//从小到大排序
Arrays.sort(nums);
//初始化目标值
int result = nums[0]+nums[1]+nums[2];
//最小的也大于目标值
if(result>target)
return result;
int first;
int left;
int right;
for (int i = 0; i < nums.length-2; i++) {
//保证和上次枚举的不一样
if(i>0&&nums[i]==nums[i-1])
continue;
first = nums[i];
//设置双指针,指向first之后的范围
left = i+1;
right = nums.length-1;
//本次循环一开始前三项就大于target,则可以考虑退出循环了
if(first+nums[i+1]+nums[i+2]>target){
if(Math.abs(result-target)>Math.abs(first+nums[i+1]+nums[i+2]-target))
result = nums[i]+nums[i+1]+nums[i+2];
break;
}
while(left<right){
if(first+nums[left]+nums[right]==target)
return target;
else if(first+nums[left]+nums[right]>target)
{
if(Math.abs(result-target)>Math.abs(first+nums[left]+nums[right]-target))
result = first+nums[left]+nums[right];
right--;
}
else{
if(Math.abs(result-target)>Math.abs(first+nums[left]+nums[right]-target))
result = first+nums[left]+nums[right];
left++;
}
}
}
return result;
}
075 颜色分类
思路:
-
方法一:单指针 时间:O(n) 空间:O(1)
遍历两次:第一次确定0的位置;第二次确定1的位置
-
方法二:双指针[1] 时间: O(n) 空间:O(1)
设置指针 p0、p1:
- p0用于指向数字0的空间的下一个位置;
- p1指针指向序列1末尾的下一个位置;
开始p0 p1指向最开始的元素,从i=0开始遍历;
- num[i] = 2,则 i++ ; 找下一个元素;
- num[i] = 1,则交换num[i] 和 num[p1] ,p1++; 即把新找到的1放到1序列中,指针p1向后移动一位
- num[i] = 0 ,则交换num[i] 和 num[p1] ,p0++,p1++; 即把新找到的0放到0序列中,此时可能会把0后面紧跟的1换到num[i]去了,因此需要再将num[i]的1换到1序列的尾部去。因此两个指针都要向后移动一个。
-
方法三:双指针[2] 时间: O(n) 空间:O(1)
-
数组两端设置指针p0,p2,把0换到最前面,2换到后面。
-
每次交换,要改动其中一个指针的位置(p0右移或者p2左移)
-
每次交换后,i++;但是
- 换出来的结果如果不是1,需要 --i,再进行一次判断
-
代码:
- 方法一:
public void sortColors(int[] nums) {
int p = 0;
//第一次遍历确定0的位置
for (int i = 0; i < nums.length; i++) {
if(nums[i]==0){
nums[i] = nums[p];
nums[p] = 0;
p++;
}
}
//第二次遍历确定1的位置
for (int j = p; j < nums.length; j++) {
if(nums[j]==1){
nums[j] = nums[p];
nums[p] = 1;
p++;
}
}
}
- 方法二
public void sortColors(int[] nums) {
int length = nums.length;
//p0,p1指针初始位置
int p0=0,p1=0;
int temp;
for (int i = 0; i < length; i++) {
//当前元素是1,则放到1序列中去
if(nums[i]==1){
temp = nums[p1];
nums[p1] = 1;
nums[i] = temp;
//1指针向后移动一个
p1++;
}
//当前元素是0,则放到0序列中去
else if (nums[i]==0){
temp = nums[p0];
nums[p0] = 0;
nums[i] = temp;
//可能会把0后面的1换走,因此需要把1换到1序列末尾去
if(nums[i]==1){
temp = nums[p1];
nums[p1] = 1;
nums[i] = temp;
}
//1指针一定会在0右边或者和0重合,因此0指针动了,则1也必须动
p1++;
p0++;
}
}
}
- 方法三
public void sortColors3(int[] nums) {
if(nums.length<=1)
return;
int p0 = 0;
int p2 = nums.length-1;
int i = 0;
//当i>p2时,说明分组结束了
while(i<=p2){
while(p0<=nums.length-1&&nums[p0]==0&&i<=p2) {i++;p0++;}
while(p2>=0&&nums[p2]==2&&i<=p2) {p2--;}
if(i>p2) break;
//把0换到前面
if(nums[i]==0){
nums[i] = nums[p0];
nums[p0]= 0;
i++;
p0++;
//容易产生越界异常
if(i<= nums.length-1&&nums[i-1]!=1){
i--;
}
}// 把2换到后面
else if(nums[i]==2){
nums[i] = nums[p2];
nums[p2] = 2;
p2--;
i++;
//容易产生越界异常
if(i<= nums.length-1 && nums[i-1]!=1)
i--;
}else{
i++;
}
}
}
026 删除有序数组重复项
思路:
- 已经升序,则只需要设置两个指针p0,p1,当指针指向值相等时,p1向后移动,不等时,将p0增1,再将p1值赋值给p0指向位置即可
public int removeDuplicates(int[] nums) {
if(nums.length==1)
return 1;
int p = 0;
for (int i = 1; i < nums.length; i++) {
while(i < nums.length&&nums[i]==nums[p])
i++;
if(i < nums.length&&nums[i]!=nums[p])
nums[++p] = nums[i];
}
return p+1;
}
-
优化:
当如图形式时,就不用再操作一次将q的值赋给p+1的值了。因此可以设置一个条件,即 q-p>1 时,才需执行赋值操作
if(i < nums.length && i-p>1 && nums[i]!=nums[p]) nums[++p] = nums[i]; else if(i < nums.length && i-p==1 && nums[i]!=nums[p]) p++;
别人的代码[好简洁]:
public int removeDuplicates(int[] nums) {
if(nums == null || nums.length == 0) return 0;
int p = 0;
int q = 1;
while(q < nums.length){
if(nums[p] != nums[q]){
if(q - p > 1){
nums[p + 1] = nums[q];
}
p++;
}
q++;
}
return p + 1;
}
031 下一个排列
思路:
以下思路存在错误
- 从右往左比较两数大小,左小右大时将两数进行交换,产生交换行为则停止;
- [1,1,3,5,2] --> [1,1,5,3,2]
- 将右侧遍历到的数进行升序排序。
- //(由算法知,右边是天然降序,只需交换两端即可)–错误,如果两数相同,则会出错
- [1,1,5,3,2] --> [1,1,5,2,3]
正确思路:
- 从右往左比较两数大小,左小右大时,则停止比较,并记录当前左边小一点的数的位置为p0;
- 将p0右边的数再次进行遍历,找到比nums[p0]大的数中,最小的数nums[p1]
- 交换p0和p1位置的数
- 将p0右侧(不含p0)的数进行升序排序
指定位置的升序公式:
//Arrays.sort(int[] a, int fromIndex, int toIndex)
//fromIndex:起点的下标【含】
//toIndex:终点的下标【不含】
Arrays.sort(nums,p+1,nums.length);
public static void nextPermutation(int[] nums) {
if(nums.length==1) return;
int p = nums.length-1;
int q = p;
int min_max = -1;
int temp;
while(p>0&&nums[p]<=nums[p-1]){
p--;
}
if(p==0){
Arrays.sort(nums);
}
else{
p--;
//找到后续大的数之中的最小值
while(p<q){
if(min_max==-1&&nums[p]<nums[q])
min_max = q;
if(nums[p]<nums[q]&&nums[min_max]>nums[q])
min_max = q;
q--;
}
//交换
temp = nums[min_max];
nums[min_max] = nums[p];
nums[p] = temp;
//对后续排序
Arrays.sort(nums,p+1,nums.length);
}
}
054.螺旋矩阵
给你一个 m
行 n
列的矩阵 matrix
,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。
-
思路:
每一层的遍历顺序为:
- 从左到右–> 上边界-1
- 从上到下–> 右边界-1
- 从右到左–> 下边界-1
- 从下到上–> 左边界-1
停止遍历的条件为:
- 上下边界重合,或者
- 左右边界重合
代码实现:
public static List<Integer> spiralOrder(int[][] matrix) {
int m = matrix.length;
int n = matrix[0].length;
List<Integer> aList = new ArrayList<>();
int up = 0;
int down = m;
int left = 0;
int right = n;
int j = 0;
int i = 0;
while(true){
j = left;
//从左往右,横坐标不变,纵坐标++
while(j!=right){
aList.add(matrix[up][j++]);
}
up++; j--;
if(up==down) break;
//从上往下,纵坐标不变,横坐标++
i = up;
while(left!=right&&i!=down){
aList.add(matrix[i++][j]);
}
right--; i--;
if(left==right) break;
//从左往右,横坐标不变,纵坐标--
while(j!=left){
aList.add(matrix[i][--j]);
}
down--;
if(up==down) break;
//从下往上,纵坐标不变,横坐标--
while(i!=up){
aList.add(matrix[--i][j]);
}
left++;
if(left==right) break;
}
return aList;
}
059. 螺旋矩阵Ⅱ
给你一个正整数 n
,生成一个包含 1
到 n2
所有元素,且元素按顺时针顺序螺旋排列的 n x n
正方形矩阵 matrix
。
思路:
依旧在while循环中按层遍历:
- 每一层的操作如下:
- (top,left)–>(top,right)
- (top+1,right)–>(bottom,right)
- (bottom,right-1)–>(bottom,left)
- (bottom-1,left)–>(top+1,left)
-
循环的条件是: top<=bottom&&left<=right
-
注意矩阵的初始化方式是:
int[][] result = new int[n][n];
实现代码:
public int[][] generateMatrix(int n) {
int[][] result = new int[n][n];
//按照螺旋数组1的方式,设置边界
//但是,正方形,可以更优化,有规律可循
int top = 0;
int bottom = n-1;
int left = 0;
int right = n-1;
int i = 0;
int count = 1;
while(top<=bottom&&left<=right) {
//(top,left)-->(top,right)
for (int j = left; j <= right; j++) {
result[top][j] = count++;
}
//(top+1,right)-->(bottom,right)
for (int j = top+1; j <= bottom; j++) {
result[j][right] = count++;
}
//(bottom,right-1)-->(bottom,left)
for (int j = right-1; j >= left; j--) {
result[bottom][j] = count++;
}
//(bottom-1,left)-->(top+1,left)
for (int j = bottom-1; j >= top+1; j--) {
result[j][left] = count++;
}
top++;bottom--;left++;right--;
}
return result;
}
遇到的问题
1 List数组的初始化
在初始化
List aList=null;
后,运行时,发生空指针异常;
原因在于,这种初始化方法只在栈中有引用而在堆中没有分配到内存空间
正确的初始化方法应该是
List aList = new ArrayList[];
2 asList()
该方法是将数组转成list,是JDK中java.util包中Arrays类的静态方法。
使用示例:
ArrayList<Integer> copyArrays=new ArrayList<Integer>(Arrays.asList(ob));//这样就得到一个新的list,可对其进行add,remove了
copyArrays.add(222);//正常,不会报错
Collections.addAll(new ArrayList<Integer>(5), ob);//或者新建一个空的list,把要转换的数组用Collections.addAll添加进去
3 static 关键字
static关键字用于内存管理中,可以使用在:
- 方法
- 变量
- 代码块
- 嵌套类
- 静态变量 :
- 节省内存;静态化的这个字段将只获得内存一次。
- 每个实例对象都共享操作同一个内存的这个字段。
- 静态方法:
- 静态方法属于类,可以直接在类中调用,无需创建类的实例。
- 并不属于实例对象。
- 静态方法可以访问静态数据成员,并可以更改静态数据成员的值。
- 静态方法限制:
- 静态方法不能直接使用非静态数据成员或调用非静态方法。
this
和super
两个关键字不能在静态上下文中使用。
- 静态块
- 初始化静态数据成员
- 类加载时,在main方法之前执行
4. 矩阵相关
- 矩阵的行数:matrix.length
- 矩阵的列数:matrix[0].length