二分查找
例题:704(简单)二分查找
需要注意的要点有:
- 数组的大小:通过nums.size()来获得(在C版本中会直接有参数numsSize)。
- 尽量使用左闭右闭区间进行二分查找**(left<=right),这样的话无论是在哪个区间范围内都是要增减1个下标的操作**,不用多操心是哪一边的区间范围;但是使用该方法时,需要注意初始化right需要numsSize-1。
下面直接贴出代码:
CPP版本:
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0, right = nums.size();
while (left <= right)
{
int mid = left + (right - left) / 2;
if (nums[mid] == target) {return mid;}
else if (nums[mid] < target) {left = mid + 1;}
else {right = mid - 1;}
}
return -1;
}
};
C版本:
int search(int* nums, int numsSize, int target){
int left = 0, right = numsSize - 1;
while (left <= right)
{
int mid = left + (right - left) / 2;
if (nums[mid] == target) {return mid;}
else if (nums[mid] < target) {left = mid + 1;}
else {right = mid - 1;}
}
return -1;
}
}
拓展题:35(简单)搜索插入位置
注意要点:
- 这一题展示一下左闭右开区间的运算(left<right),此时,如果target在右边的区间范围内,由于left也包含在区间内,所以left=mid+1;如果在左边,由于right不包括在区间内,则right=mid;
- 如果没有找到,那么需要考虑插入的位置,需要判断的有四种情况:在最左边;在最右边以及插入在中间;可以通过输出left和right来判断,最终结果就是都返回left就可以了。
下面直接贴出代码:
CPP版本:
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int left = 0, right = nums.size();
while (left < right)
{
int mid = left + ((right - left) >> 1);
if (nums[mid] > target) {right = mid;}
else if (nums[mid] < target) {left = mid + 1;}
else {return mid;}
}
return left;
}
};
C版本:
int searchInsert(int* nums, int numsSize, int target){
int left = 0, right = numsSize;
while (left < right)
{
int mid = left + ((right - left) >> 1);
if (nums[mid] > target) {right = mid;}
else if (nums[mid] < target) {left = mid + 1;}
else {return mid;}
}
return left;
}
拓展题:69(简单)x的平方根
注意要点:
- 由于是向下取整,所以答案的更新应该在左区间范围内,且应该在更新前就赋值到记录的变量中
下面直接贴出代码:
CPP版本:
class Solution {
public:
int mySqrt(int x) {
int left = 0, right = x;
int ans = 0;
while (left <= right)
{
int mid = left + (right - left) / 2;
if ((long long)mid * mid <= x)
{
ans = mid;
left = mid + 1;
}
else {right = mid - 1;}
}
return ans;
}
};
C版本:
int mySqrt(int x){
int left = 0, right = x;
int ans = 0;
while (left <= right)
{
int mid = left + (right - left) / 2;
if ((long long)mid * mid <= x )
{
ans = mid;
left = mid + 1;
}
else {right = mid - 1;}
}
return ans;
}
拓展题:367(简单)有效的完全平方数
注意要点:
- 看到算法要求为O(logn),应该快速想起二分法;
下面直接贴出代码:
CPP版本:
class Solution {
public:
bool isPerfectSquare(int num) {
int left = 0, right = num;
while (left <= right)
{
int mid = left + (right - left) / 2;
long long square = (long long )mid * mid;
if (square < num) {left = mid + 1;}
else if (square > num) {right = mid - 1;}
else {return 1;}
}
return 0;
}
};
C版本:
bool isPerfectSquare(int num){
int left = 0, right = num;
while (left <= right)
{
int mid = left + (right - left) / 2;
long long square = (long long )mid * mid;
if (square < num) {left = mid + 1;}
else if (square > num) {right = mid - 1;}
else {return 1;}
}
return 0;
}
拓展题34(中等)在排序数组中查找元素的第一个和最后一个位置
注意要点:
- 在cpp版本中,因为public的函数类型缘故,所以可以很简单的返回例如{-1,-1},可以直接作为返回值输出;而在C版本中,就需要自己创建一个数组,最后赋值给数组并返回数组的首地址;
- 获得左边界的方法:二分查找,在更新到right的情况下,把right值赋值到创建的左边界记录变量中;反过来,寻求右边界,就是更新left的情况下把left更新到记录右边界的变量中;需要注意,都是在找到了target的情况下进行更新,即nums[mid]==target;
- 如果最终的记录边界变量有一个未更新,说明没有找到;当且仅当两个边界量的差>1才证明是找到了这个区间,否则则说明没有找到。
下面直接贴出代码:
CPP版本:
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
int leftboarder = getLeft(nums, target);
int rightboarder = getRight(nums, target);
if (leftboarder == -2 || rightboarder == -2) {return {-1, -1};}
else if (rightboarder - leftboarder > 1)
{
return {leftboarder + 1, rightboarder - 1};
}
return {-1, -1};
}
private:
int getLeft(vector<int>& nums, int target)
{
int left = 0, right = nums.size() - 1;
int leftboarder = -2; //记录一下是否找到,没找到就会=-2
while (left <= right)
{
int mid = left + (right - left) / 2;
if (nums[mid] < target) {left = mid + 1;}
else {right = mid - 1;leftboarder = right;}
}
return leftboarder;
}
int getRight(vector<int>& nums, int target)
{
int left = 0, right = nums.size() - 1;
int rightboarder = -2; //记录一下是否找到,没找到就会=-2
while (left <= right)
{
int mid = left + (right - left) / 2;
if (nums[mid] > target) {right = mid - 1;}
else {left = mid + 1;rightboarder = left;}
}
return rightboarder;
}
};
C版本:
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int getLeft(int* nums, int numsSize, int target)
{
int left = 0, right = numsSize - 1;
int boarder = -2;
while (left <= right)
{
int mid = left + (right - left) / 2;
if (nums[mid] >= target) {right = mid - 1;boarder = right;}
else if (nums[mid] < target) {left = mid + 1;}
}
return boarder;
}
int getRight(int* nums, int numsSize, int target)
{
int left = 0, right = numsSize - 1;
int boarder = -2;
while (left <= right)
{
int mid = left + (right - left) / 2;
if (nums[mid] > target) {right = mid - 1;}
else if (nums[mid] <= target) {left = mid + 1;boarder = left;}
}
return boarder;
}
int* searchRange(int* nums, int numsSize, int target, int* returnSize){
int left = getLeft(nums, numsSize, target);
int right = getRight(nums, numsSize, target);
int* ans = (int* )malloc(sizeof(int) * 2);
*returnSize = 2;
if (left == -2 || right == -2) {ans[0] = ans[1] = -1;}
else if (right - left > 1)
{
ans[0] = left + 1;
ans[1] = right - 1;
}
else {ans[0] = ans[1] = -1;}
return ans;
}
移除元素
例题:27(简单)移除元素
注意要点:
- 可以通过双指针(快慢指针的方法),进行数组的删除,慢指针代表当前的元素,快指针则是遍历所有元素;遍历过程中根据题目要求判断是否要在slow处进行数据拷贝
下面直接贴出代码:
CPP版本:
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slow = 0;
for (int fast = 0; fast < nums.size(); fast++)
{
if (nums[fast] == val) {continue;}
else {nums[slow++] = nums[fast];}
}
return slow;
}
};
C版本:
int removeElement(int* nums, int numsSize, int val){
int slow = 0;
for (int fast = 0; fast < numsSize; fast++)
{
if (nums[fast] == val) {continue;}
else {nums[slow++] = nums[fast];}
}
return slow;
}
拓展题26(简单)删除有序数组中的重复项
注意要点:
- 同样使用快慢指针,重复项需要向前比较,所以循环变量从1开始向后遍历;
- 依据题意,只有不相等才把快指针的值赋值给慢指针处,同时慢指针可以向后移一位。
下面直接贴出代码:
CPP版本:
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
if (!nums.size()) {return 0;}
int slow = 1, fast = 1;
while (fast < nums.size())
{
if (nums[fast] != nums[fast - 1])
{
nums[slow] = nums[fast];
slow++;
}
fast++;
}
return slow;
}
};
C版本:
int removeDuplicates(int* nums, int numsSize){
if (numsSize == 0) {return 0;}
int fast = 1, slow = 1;
while (fast < numsSize)
{
if (nums[fast] != nums[fast-1])
{
nums[slow] = nums[fast];
slow++;
}
fast++;
}
return slow;
}
拓展题283(简单)移动零
注意要点:
- C++中有已经实现的swap函数,直接把要交换的参数填入即可;
- 快指针非零,就移动与慢指针交换,且都右移;如果快指针是零,就右移快指针;这样最终慢指针左侧都是处理好的,右侧全是零。
下面直接贴出代码:
CPP版本:
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int slow = 0, fast = 0;
while (fast < nums.size())
{
if (nums[fast])
{
swap(nums[slow], nums[fast]);
slow++;
}
fast++;
}
}
};
C版本:
void moveZeroes(int* nums, int numsSize){
int left = 0, right = 0;
for (right; right < numsSize; right++)
{
if (nums[right])
{
int tmp = nums[right];
nums[right] = nums[left];
nums[left] = tmp;
left++;
}
}
return nums;
}
拓展题844(简单)比较含退格的字符串
注意要点:
- 双指针法,该字符是否被退格只取决于其身后有否相邻退格符,所以遍历方向应为从后向前;
- 需要记录所需退格数来判断哪一个字符是有效的;s和t都退完才进入比较;
- 对于字符串,求取字符串长度,C++用s.length(),而C则是strlen(s)。
下面直接贴出代码:
CPP版本:
class Solution {
public:
bool backspaceCompare(string s, string t) {
int i = s.length() - 1, j = t.length() - 1;
int skips = 0, skipt = 0; //记录当前的所需退格数
while (i >= 0 || j >= 0)
{
while (i >= 0) //处理s中的退格符
{
if (s[i] == '#') {skips++;i--;}
else if (skips) {skips--;i--;}
else {break;}
}
while (j >= 0) //处理t中的退格符
{
if (t[j] == '#') {skipt++;j--;}
else if (skipt) {skipt--;j--;}
else {break;}
}
if (i >= 0 && j >= 0) //都还在字符串中,需要比较是否为同一字符
{
if (s[i] != t[j]) {return 0;}
}
else {if (i >= 0 || j >= 0) {return 0;}} //判断是否已经全部比较完了,如果还有剩余字符,则说明不相同
i--;j--;
}
return 1;
}
};
C版本:
bool backspaceCompare(char * s, char * t){
int left = strlen(s) - 1, right = strlen(t) - 1;
int count_l = 0, count_r = 0;
while (left >= 0 || right >= 0)
{
while (left >= 0)
{
if (s[left] == '#') {left--;count_l++;}
else if (count_l) {left--;count_l--;}
else break;
}
while (right >= 0)
{
if (t[right] == '#') {right--;count_r++;}
else if (count_r) {right--;count_r--;}
else break;
}
if (left < 0 || right < 0) break;
else if (s[left] != t[right]) return false;
left--;
right--;
}
if (left == -1 && right == -1) return true;
return false;
}
有序数组的平方
例题977(简单)有序数组的平方
注意要点:
- 由于已经是升序数组,所以平方后的最大数一定是两侧的数字取其一(绝对值越大,越远离数组中心);
- 通过双指针,不断比较当前的两侧平方,大的那个保存到答案数组的最后面。
下面直接贴出代码:
CPP版本:
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
vector<int> ans(nums.size());
for (int i = 0, j = nums.size() - 1, pos = nums.size() - 1; i <= j;)
{
if (nums[i] * nums[i] > nums[j] * nums[j])
{
ans[pos--] = nums[i] * nums[i];
i++;
}
else
{
ans[pos--] = nums[j] * nums[j];
j--;
}
}
return ans;
}
};
C版本:
/**
1. Note: The returned array must be malloced, assume caller calls free().
*/
int* sortedSquares(int* nums, int numsSize, int* returnSize) {
int* ans = malloc(sizeof(int) * numsSize);
*returnSize = numsSize;
for (int i = 0, j = numsSize - 1, pos = numsSize - 1; i <= j;)
{
if (nums[i] * nums[i] > nums[j] * nums[j])
{
ans[pos--] = nums[i] * nums[i];
i++;
}
else
{
ans[pos--] = nums[j] * nums[j];
j--;
}
}
return ans;
}
长度最小的子数组
例题209(中等)长度最小的子数组
注意要点:
- 由于需要记录最短的子数组长度,所以记录值的初始化一定要初始化为最大值,C++中为INT32_MAX,而C中为INT_MAX;
- 通过双指针,完成滑动窗口的计算,只有当前的和>=target,进入滑动窗口的计算,计算子数组长度,并且判断是否可以赋值,并减去最左侧的数,完成窗口的滑动。
下面直接贴出代码:
CPP版本:
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int ret = INT32_MAX;
int count = 0;
int sum = 0;
int left = 0, right = 0;
while (right < nums.size())
{
sum += nums[right++];
while (sum >= target)
{
count = right - left;
ret = min(count, ret);
sum -= nums[left++];
}
}
return ret == INT32_MAX ? 0 : ret;
}
};
C版本:
int minSubArrayLen(int target, int* nums, int numsSize){
int head = 0;
int sum = 0;
int result = numsSize+1;
int sub = 0;
for (int tail = 0; tail < numsSize; tail++)
{
sum += nums[tail];
while (sum >= target)
{
sub = tail - head + 1;
result = sub < result ? sub : result;
sum -= nums[head];
head++;
}
}
return result == numsSize+1 ? 0 : result;
}
螺旋矩阵
例题59(中等):螺旋矩阵II
注意要点:
- 本题并不涉及什么算法,主要是容器和模拟;首先C++中,可以使用vector<vector<int>> ans(n, vector<int> (n, 0)),初始化一个n*n的所有元素均为零的二维数组;
- 可以通过定义一个loop记录所需旋转次数,并设置当前循环的开始位置来进行旋转,同时在旋转过程中的模拟可以统一采用左开右闭,方便代码编辑。
下面直接贴出代码:
CPP版本:
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>> ret(n, vector<int> (n, 0));
int startx = 0, starty = 0; //记录循环的起始位置
int loop = n / 2; //所需的旋转次数
int offset = 1; //在循环内的偏置大小,用于控制右开区间的位置
int count = 1; //当前的写入大小
while (loop--)
{
int i = startx, j = starty;
for (j = starty; j < n - offset; j++) {ret[i][j] = count++;}
for (i = startx; i < n - offset; i++) {ret[i][j] = count++;}
for (; j > starty; j--) {ret[i][j] = count++;}
for (; i > startx; i--) {ret[i][j] = count++;}
startx++;starty++;
offset++;
}
if (n % 2) {ret[startx][starty] = count;} //如果是奇数,则最后会剩余一个元素
return ret;
}
};
C版本:
/**
1. Return an array of arrays of size *returnSize.
2. The sizes of the arrays are returned as *returnColumnSizes array.
3. Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().
*/
int** generateMatrix(int n, int* returnSize, int** returnColumnSizes){
int** result = (int** )malloc(sizeof(int*) * n);
*returnSize = n;
*returnColumnSizes = (int* )malloc(sizeof(int) * n);
for (int i = 0; i < n; i++)
{
result[i] = (int* )malloc(sizeof(int) * n);
memset(result[i], 0, sizeof(int) * n);
(*returnColumnSizes)[i] = n;
}
int startx = 0, starty = 0;
int loop = n / 2;
int offset = 1;
int count = 1;
int i, j;
while (loop--)
{
i = startx;j = starty;
for (; j < n - offset; j++) {result[i][j] = count++;}
for (; i < n - offset; i++) {result[i][j] = count++;}
for (; j > starty; j--) {result[i][j] = count++;}
for (; i > startx; i--) {result[i][j] = count++;}
startx++;starty++;
offset++;
}
if (n % 2) {result[startx][starty] = count;}
return result;
}
拓展题54(中等)螺旋矩阵
注意要点:
- 由于是m*n矩阵,通过loop控制会非常麻烦,因为碰到奇数可能还是需要for循环来填充,需要重新判断;
- 可以定义一个方向数组,当按照当前方向递增溢出,就转入下一个方向继续遍历;
- 当前遍历结束与否,可以通过设置一个遍历的bool数组记录当前位置是否已被使用。
下面直接贴出代码:
CPP版本:
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
if (!matrix.size() || !matrix[0].size()) {return {};}
int rows = matrix.size(), cols = matrix[0].size();
vector<vector<bool>> visited(rows, vector<bool> (cols, 0));
int total = rows * cols;
vector<int> order(total);
int row = 0, col = 0;
int directionIndex = 0;
for (int i = 0; i < total; i++)
{
order[i] = matrix[row][col];
visited[row][col] = 1;
int nextrow = row + directions[directionIndex][0];
int nextcol = col + directions[directionIndex][1];
if (nextrow < 0 || nextrow >= rows || nextcol < 0 || nextcol >= cols || visited[nextrow][nextcol])
{
directionIndex = (directionIndex + 1) % 4;
}
row += directions[directionIndex][0];
col += directions[directionIndex][1];
}
return order;
}
private:
const int directions[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
};
C版本:
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int direction[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
int* spiralOrder(int** matrix, int matrixSize, int* matrixColSize, int* returnSize){
if (!matrixSize || !matrixColSize[0]) {*returnSize = 0;return NULL;}
int rows = matrixSize, cols = matrixColSize[0];
int* ans = (int* )malloc(sizeof(int) * rows * cols);
bool visited[rows][cols];
memset(visited, 0, sizeof(visited));
int row = 0, col = 0;
int directionIndex = 0;
*returnSize = rows * cols;
for (int i = 0; i < rows * cols; i++)
{
ans[i] = matrix[row][col];
visited[row][col] = 1;
int nextrow = row + direction[directionIndex][0];
int nextcol = col + direction[directionIndex][1];
if (nextrow < 0 || nextrow >= rows || nextcol < 0 || nextcol >= cols || visited[nextrow][nextcol])
{
directionIndex = (directionIndex + 1) % 4;
}
row += direction[directionIndex][0];
col += direction[directionIndex][1];
}
return ans;
}
总结
这里我直接白嫖了代码随想录的一个总结: