数组篇:
704二分查找
class Solution {
public int search(int[] nums, int target) {
int left = 0;
int right = nums.length-1;
if(target<nums[0]||target>nums[nums.length-1])
return -1;
while(left<=right){ //左闭右闭区间 当left==right时区间仍有效 因此left<=right
int mid = left + ((right - left)/2);
if(nums[mid]>target){
right = mid - 1;
}
else if(nums[mid]<target){
left = mid + 1;
}else{
return mid;
}
}
return -1;
}
}
二分查找的关键是判断区间情况,是左闭右闭区间还是左闭右开区间,不同的区间情况会影响子区间范围的确定和循环终止条件的判断。
当采用左闭右闭区间:
采用二分法找到中间点时,因为区间右端点可以取到,因此二分后的两个子区间为[left,mid-1]和[mid+1,right]。因为left==right时区间仍有效,因此循环终止条件为left<=right
采用左闭右开区间:
采用二分法找到中间点时,因为区间右端点无法取到,因此二分后的两个子区间为[left,mid]和[mid+1,right]。因为left==right时区间失效,因此循环终止条件为left<right
27 移除数组元素
最容易想到的方法是采用暴力解法,一个for循环遍历数组,一个for循环更新移除元素后的新数组,每次遇到等于val的元素就将后面元素全部前移一位进行覆盖。
其中需要注意的是,因为移除元素后下标为i的数组全部前移一位,因此外层for循环中的循环变量i也需要减一。
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int len = nums.size();
for(int i=0;i<len;i++){
if(nums[i]==val){
for(int j=i+1;j<len;j++){
nums[j-1] = nums[j];
}
i--; //因为下标为i的数值都向前移动了一位,所以i页向前移动
len--;
}
}
return len;
}
};
此外还可以采取双指针的方法
双指针法:定义一个快指针fast,一个慢指针slow,通过两个指针利用一个for循环完成两个for循环的功能。
双指针法的重点在与弄清两个指针的含义。
slow:指向移除元素后的新数组的元素的下标(即当前fast所指元素应存放的下标位置)
fast:指向应保存在新数组中的元素的下标(即值不等于val的元素的下标)
//慢指针指向新数组元素下标,即当fast指向元素与val相同时slow停止移动fast继续移动
//当fast指针所指元素与val相同时将fast的值赋给slow
class Solution {
public int removeElement(int[] nums, int val) {
int fast; //fast:快指针指向新数组中应保存的元素
int slow = 0; //slow:慢指针指向新数组的当前元素下标
for(fast = 0;fast<nums.length;fast++){
if(nums[fast] != val){
nums[slow] = nums[fast];
slow++;
}
}
return slow;
}
}
977有序数组的平方
该题最简单的想法是暴力求解,将每个元素平方后再排序。
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
for(int i = 0;i<nums.size();i++){
nums[i] = nums[i]*nums[i];
}
sort(nums.begin(),nums.end());
return nums;
}
};
还可以采用双指针的方法
由于给定的数组是一个非递减数组,因此平方后的最大项只可能出现在原数组的第一位和最后一位,第二大只可能在原数组第一位和倒数第二位中出现,以此类推...
因此只需要定义两个首尾指针,比较首尾指针所指元素的平方的大小,将较大者放在新数组的最后一位,并修改首尾指针。
class Solution {
public int[] sortedSquares(int[] nums) {
int rear = nums.length-1;
int front = 0;
int index;
int[] A = new int[nums.length];
for(index = nums.length-1;index>=0;index--){
if(nums[rear]*nums[rear] > nums[front]*nums[front]){
A[index] = nums[rear]*nums[rear];
rear--;
}
else{
A[index] = nums[front]*nums[front];
front++;
}
}
return A;
}
}
209长度最小的子数组
本题求符合条件的长度最小的连续子数组
可以采用暴力求解的方法,利用两个for循环,外层for循环负责遍历数组,控制子数组的起始下标,内层for循环负责控制子数组的终止下标(即满足子数组之和>=target时的下标)。
当子数组的和超过target时记录此时的数组长度,并与最小长度进行比较,找出最小长度。
注意边界情况,当数组全部元素之和都小于target值时,应返回0,即min仍为初始值INT32_MAX时,应返回0.
int minSubArrayLen(int target, int* nums, int numsSize) {
int min=INT32_MAX,len=0;
int ans=0;
for(int i=0;i<numsSize;i++){
ans = 0;
for(int j=i;j<numsSize;j++){
ans = nums[j] + ans;
if(ans>=target){
len = j - i + 1;
min = min < len ? min : len;
break;
}
}
}
return min == INT32_MAX ? 0: min;
}
还可以采用滑动窗口法
滑动窗口就是不断调节子数组的起始位置和终止位置,得到想要的结果,同样是通过一个for循环实现两个for循环的功能。
滑动窗口的关键在于判断循环变量代表起始位置还是终止位置。
本题需要找到长度最小的子数列使其元素和满足>=target。
假设循环变量i代表起始位置,当i向后移动时,终止位置需要遍历i后的所有元素来找到符合条件的子数组,因此仍需两个for循环实现该功能。
假设循环变量i代表终止位置,当i向后移动时,i前面的元素即是子数组序列即[0,i],当i前面的元素满足条件时,从下标0开始依次前移起始位置改变滑动窗口的大小,找到最后一个满足条件的子数组,该子数组的长度即最小长度。
应注意改变起始位置时,当前子数组的元素之和应减去起始位置对应的元素值得到新的子数组元素之和。
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int i=0;
int subL;
int result = Integer.MAX_VALUE;
int sum=0;
for(int j=0;j<nums.length;j++){
sum = sum + nums[j];
while(sum>=target){
subL = j - i + 1;
result = Math.min(result,j-i+1);
sum = sum - nums[i];
i++;
}
}
return result == Integer.MAX_VALUE ? 0 : result;
}
}
59 螺旋矩阵Ⅱ
该题主要在于处理循环的边界情况,顺时针画矩阵需要遍历上下左右四个边,然后逐渐向内循环。
重点在于采用循环不变量原则,遍历每一条边时均采用左闭右开区间,通过固定的规则可以简化边界条件的确定。
遍历上边时for循环遍历的区间为[left,right-1],
遍历右边时for循环遍历的区间为[up,down-1],
遍历下边时for循环遍历的区间为[right,left-1],
遍历左边时for循环遍历的区间为[down,up-1],
最外层遍历结束后left,right,up,down需要分别向内层移动一位。
(该代码为54螺旋矩阵对应的代码)
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
int m = matrix.size(); //行数
int n = matrix[0].size(); //列数
int left=0,right=n-1,up=0,down=m-1; //left,right,up,down用于控制边界
int k = 0;
int i;
vector<int> res(n*m,0);
while(true){
for(i = left;i<=right;i++) //遍历上边
res[k++] = matrix[up][i];
if(++up>down) break; //++up向内移动若上边界大于下边界则退出
for(i = up;i<=down;i++) //遍历右边
res[k++] = matrix[i][right];
if(--right<left) break; //--right向内移动若右边界小于左边界则退出
for(i = right;i>=left;i--) //遍历下边
res[k++] = matrix[down][i];
if(--down<up) break; //--down向内移动若下边界小于上边界则退出
for(i = down;i>=up;i--)
res[k++] = matrix[i][left];
if(++left>right) break; //++left向内移动若左边界大于右边界则退出
}
return res;
}
};
也可采用[startx,starty]来确定每一条边的遍历情况,但该方法需要单独考虑当边数为奇数时的情况,边数为奇数时,最中间元素需要单独赋值。其中offset用于控制每条边的长度。
遍历上边时for循环遍历的区间为[starty,n-offset],
遍历右边时for循环遍历的区间为[startx,n-offset],
遍历下边时for循环遍历的区间为[n-offset,starty],
遍历左边时for循环遍历的区间为[n-offset,startx],
最外层遍历结束后startx,starty需要分别向内层移动一位,offset也需要增1.
(该代码为59螺旋矩阵Ⅱ对应的代码)
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
int i=0,j=0;
int startx=0,starty=0,offset=1,count=1;
vector<vector<int>> nums(n, vector<int>(n, 0));
int loop = n/2;
while(loop--){
i = startx; //(startx,starty):(0,0)
j = starty;
for(j = starty;j<n-offset;j++) //遍历上边
nums[startx][j] = count++; //nums[][]的横坐标不变纵坐标改变
for(i = startx;i<n-offset;i++) //遍历右边
nums[i][j] = count++; //nums[][]的纵坐标不变横坐标改变,此时j=n-offset
for(;j>starty;j--) //遍历下边
nums[i][j] = count++; //nums[][]的横坐标不变纵坐标改变,此时i=n-offset,且j=n-offset不用重新赋初值
for(;i>startx;i--) //遍历左边
nums[i][j] = count++; //nums[][]的纵坐标不变横坐标改变,此时j=n-offset且i=n-offset不用重新赋初值
startx++; //startx,starty向内层移动
starty++;
offset+=1; //offset控制边长因此每一次循环收缩一位
}
if(n%2)
nums[n/2][n/2]=count; //当n为奇数的时候单独给矩阵最中间的位置赋值
return nums;
}
};