第二章 数组
2.1数组的基础知识
数组占据一块连续的内存并按照顺序存储数据。动态数组
2.2 双指针
双指针是一种常用的解题思路,可以使用两个相反方向或者相同方向的指针扫描数组。在数组中,“指针”实际上是数字的下标。
方向相反的双指针经常用来求排序数组中的两个数字之和。一个指针p1指向数组中的第一个数字,另一个指针p2指向数组中的最后一个数字,然后比较两个指针指向的数字之和以及一个目标值。如果两个指针指向的数字之和大于目标值,则向左移动指针p2;如果两个指针指向的数字之和小于目标值,则向右移动指针p1,此时两个指针移动的方向是相反的。
方向相同的双指针通常用来求正数数组中子数组的和或乘积。初始化的时候两个指针p1和p2都指向数组的第一个数字,如果两个指针之间的子数组的和或者乘积大于目标值,则向右移动指针p1删除子数组中最左边的数字;如果两个指针之间得子数组得和或者乘积小于目标值,则向右移动指针p2在子数组右边增加新的数字。此时两个指针的移动方向是相同的。
面试题6
排序数组中的两个数字之和
两数之和
关键点:递增数组
public int[] twoSum(int[] numbers,int target){
int i=0;
int j=numbers.length-1;
while(i<j&&numbers[i]+numbers[j]!=target){
#由于是两个数 所以当i>=j的时候,循环条件不成立
#那有一个问题 就是如果当ij的下标都指向了同一个数字 那么岂不是直接返回[i,i]了?
if(numbers[i]+numbers[j]<target){
i++;}
else{
j--;}
}
return int[]{i,j};}
时间复杂度O(n)
面试题7
数组中和为0的三个数字
力扣
输入一个数组,如何找出数组中和为0的三个数字的三元组。需要注意的是,返回值中不得包含重复的三元组。
思想就是 固定一个i,那么遍历查找剩下的数字中和为-i的两个数字,
public List<List<Integer>> threeSum(int[] nums){
List<List<Integer>> result=new LinkedList<List<Integer>>();
if(nums.length>=3){
Arrays.sort(nums);
int i =0;
while(i<nums.length-2){
twoSum(nums,i,result);
int temp=nums[i];
if(i<nums.length&&nums[i]==temp){
i++;}
}}
return result;
}
private void twoSum(int[]nums,int i ,List<List<Integer>> result){
int j=i+1;
int k=nums.length-1;
while(j<k){
if(nums[i]+nums[j]+nums[k]==0){
result.add(Array.asList(nums[i],nums[j],nums[k]));
#为了避免重复的三元组,因此要使用一个while循环让下标j跳过重复的数字
int temp=nums[j];
while(nums[j]==temp&&j<k){
j++;}}
else if(nums[i]+nums[j]+nums[k]<0){
j++;}
else{
k--;}}
}
面试题8
和大于或等于k的最短子数组
力扣
输入一个正整数组成的数组和一个正整数k,请问数组中和大于或等于k的连续子数组的最短长度是多少?如果不存在所有数字之和大于或等于k的子数组,则返回0.
这里由于是正数,可以采用双指针的方式,设置两个左右指针初始都指向数组的第一个数字(也就是下标为0的数字),设置一个计算子数组和的变量,一个表示满足条件的子数组的最小长度的变量,均初始化为0。
首先双指针移动的依据是,子数组之和大于等于k,就将左指针向右移动,相当于把最左边的删除掉,如果子数组之和小于k,则将左指针向右移动,在子数组中加入一个数字。
可以编写代码,外循环是当右指针从0开始小于数组的长度,向右移动并同时更新sum也就是子数组的和,当满足左指针小于或等于右指针并且和大于等于k的时候,更新此时的子数组最短长度,并尝试删除最左边的数字以求最短子数组,将最左边数字的值从sum中删除并将左指针向右移动,直到不满足左指针小于等于右指针或者sum大于等于k的条件,跳出while循环。在右指针移动到数组最右边后,for循环结束。此时得到的最短长度就是要求的。返回即可。
public int minSbuArrayLen(int[]nums,int k){
int left=0;
int right=0;
int minLength=Integer.MAX_VALUE;
int sum=0;
for(right=0;right<nums.length;right++){
sum+=nums[right];
while(left<=right && sum>=k){
minLength=Math.max(right-left+1,minLength);
sum-=nums[left++];}
}
return minLength==Inter.MAX_VALUE?0:minLength;
}
时间复杂度O(n)
面试题9
乘积小于k的子数组力扣
输入一个正整数组成的数组和一个正整数k,请问数组中有多少个数字乘积小于k的连续子数组?
思路:两个指针p1和p2,两个指针之间组成一个子数组,如果这个子数组乘积小于k向右移动p2,大于等于k向右移动p1,当小于k时,此时子数组保持p2不动,向右移动p1所组成的所有子数组的乘积都小于k,所以此时两个指针之间有多少数字,就找到了多少个数字的乘积小于k的子数组
public int sumSubarrayProductLessThanK(int[] nums,int k){
int left=0;
int right=0;
int count=0;
int product=1;
while(right=0;right<nums.length;right++){
product*=nums[right];
while(left<=right&&product>=k){
product/=nums[left];
left++;
}
count+=right>=left?right-left+1:0
}
return count;
}
时间复杂度O(n)
累加数组数字求子数组之和
使用双指针解决子数组之和有一个前提条件是数组中的所有数字都是正数。
从下标0开始到下标0结束的子数组之和是S0,从下标0开始到下标1结束的子数组之和为S1,从下标0开始到最后一个数字的子数字之和为S(n-1)。
因此,从下标i开始到下标j结束的子数组之和为Sj-S(i-1)
面试题10
和为k的子数组
力扣
输入一个整数数组和一个整数k,请问数组中有多少个数字之和等于k的连续子数组?
思路:从头到尾逐个扫描数组中的数字时求前i个数字之和,并将和保存下来。数组的前i个数字之和记为x,如果存在一个j(j<i),数组的前j个数字之和为x-k,那么从j+1到i的子数字之和为k
这个题目要计算和为k的子数组的个数
当扫描到数组的第i个数字并求得前i个数字之和是k的时候,需要知道再i之前有多少个j并且前j个数字之和等于x-k。所以对于i不但要保存前i个数字之和,还要保存每个和出现的次数。
需要一个哈希表,哈希表的键是前i个数字之和,哈希表的值是和出现的次数。
代码简短但思路很难想
public int subarraySum(int[]nums,int k){
Map<Integer,Integer>sumToCount=new HashMap<>();
sumToCount.put(0,1);
int sum=0;
int count=0;
for(int num:nums){
sum+=num;
count+=sumToCount.getOrDefault(sum-k,0);#以当前位置为结束的子数组中和为k的次数
sumToCount.put(sum,sumToCount.getOrDefault(sum,0)+1);}
return count;}
时间、空间复杂度都为O(n)
面试题11
0和1个数相同的子数组
力扣
输入一个只包含0和1的数组,请问如何求0和1的个数相同的最长连续子数组的长度?
思路:把0看作-1的话,0和1个数相同,相当于和为0
既然和为0了,就和上一道题差不多的思路;
但素这道题要求的是最长连续子数组的长度
设置一个哈希表,键为从第一个数字到当前位置数字的子数组的累加数字之和,值为当前位置的下标
由于寻找和为0的连续子数组,那么如果一个位置i处子数组之和为sum,那么再i之前的j处子数组之和也为sum的话,那么j+1到i之间的子数组之和一定为0,要求的就素这个的最大长度。
public int findMaxLength(int[]nums){
Map<Integer,Integer> sumToCount=new HashMap<>();
int count=0;
int sum=0;
sumToCount.put(0,-1);#先将累加和为0的子数组的最后一个位置初始化为-1
for(int i=0;i<nums.length;i++){
sum+=nums[i]==0?-1:1;#将当前遍历到的nums[i]转化为-1/1
if(sumToCount.cotainsKey(sum)){#如果哈希表中已经包含过和为sum的键值,那么说明在位置i之前已经出现过和为sum的j,满足条件,那么从j+1到i这部分的长度就可以计入候选结果 i-(j+1)+1=i-j
count=Math.max(count,i-sumToCount.get(sum));}#那么就可以求出从i到j位置的之差
else{
sumToCount.get(sum,i);}#否则就将键值插入哈希表
}
return count;}
时间和空间复杂度都为O(n)
面试题12
左右两边子数组的和相等力扣
输入一个整数数组,如果一个数字左边的子数组的数字之和等于右边的子数组的数字之和,那么返回该数字的下标,如果存在多个这样的数字,则返回最左边一个数字的下标。如果不存在这样的数字,则返回-1。
思路:比较简单,先扫描并逐一累加扫描到的数字得到整个数组的和 然后再从位置0开始,逐个位置比较左右两边的值
public int pivotIndex(int [] nums){
int total=0;
for(int num:nums){
total+=num;}
int sum=0;
for(int i=0;i<nums.length;i++){
sum+=nums[i];
if(sum-nums[i]==total-sum){
return i;}
}
return -1;}
时间复杂度O(n)空间复杂度O(1)
面试题13 二维子矩阵的数字之和
力扣
输入一个二维矩阵,如何计算给定左上角坐标和右下角坐标的子矩阵的数字之和。
思路:均以左上角坐标为(0,0)开始,右下角坐标为(r2,c2)-(r1-1,c2)-(r2,c1-1)+(r1-1,c1-1)=左上角坐标(r1,c1)右下角坐标(r2,c2)组成的矩阵。
那么如何求左上角00,右下角(i,j)的矩阵的和呢?可以看作两部分组成,①左上角00右下角(i-1,j),②i行从0到j的数字之和
代码里还需要注意的一点是,由于防止row1 col1为0,导致row1-1/col-1为负数,因此将sums矩阵的最左边增加一列,最上面增加一行。
class NumMatrix{
private int [][]sums;
public NumMatric(int [][]matrix){
if(matrix.length==0||matrix[0].length==0){
return;}
sums=new int[matrix.length+1][matrix[0].length+1];
for(int i=0;i<matrix.length;i++){
int rowSum=0;
for(int j=0;j<matrix[0].length;j++){
rowSum+=matrix[i][j];
sums[i+1][j+1]=sums[i][j+1]+rowSum;
#这里是相当于在sum矩阵的**最左边和最上边增加了一行一列**,为了防止r1/c1为0的情况发生那么r1-1/c1-1就是负数了
}}
}
public int sumRegion(int row1,int col1,int row2,int col2){
return sums[row2][col2]-sums[row1-1][col2]-sums[row2][col1-1]+sums[row1-1][col1-1];}}
时间复杂度和空间复杂度都是O(mn)