题目列表
解题过程
1、704.二分查找
给定一个n个元素有序的(升序)整型数组nums和一个目标值target,写一个函数搜索nums中的target,如果目标值存在返回下标,否则返回-1。
class Solution {
public int search(int[] nums, int target) {
return binarySearch(nums, target, 0, nums.length - 1);
}
public int binarySearch(int[] nums, int target, int l, int r) {
int mid = (l + r) / 2;
if (nums[mid] == target) {
return mid;
}else if (l == r) {
return -1;
}else if (nums[mid] > target) {
return binarySearch(nums, target, l, mid);
}else {
return binarySearch(nums, target, mid + 1 , r);
}
}
}
注意
- 题目所给数组为有序数组,查找左右子数组只需查找其中一个。
2、27.移除元素
给你一个数组nums和一个值val,你需要原地移除所有数值等于val的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用O(1)额外空间并原地修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
思路: 由于不需要考虑超出新长度后面的元素,因此可以在遍历过程中将被移除元素与数组当前最后一个元素进行交换,并同步将数组长度-1。
class Solution {
public int removeElement(int[] nums, int val) {
int length = nums.length;
for(int i = 0; i < length; i++) {
if(nums[i] == val) {
nums[i] = nums[i] ^ nums[length-1];
nums[length-1] = nums[i] ^ nums[length-1];
nums[i] = nums[i] ^ nums[length-1];
length--;
i--;
}
}
return length;
}
}
注意
- 数组元素交换采用的异或操作,不需要另外声明一个变量。
3、35.搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为O(log n)的算法。
思路: 这也是一道很经典的二分查找问题,所以直接在704.题解上进行修改,添加一个三目运算符。
class Solution {
public int searchInsert(int[] nums, int target) {
return binarySearch(nums, target, 0, nums.length - 1);
}
public int binarySearch(int[] nums, int target, int l, int r) {
int mid = (l + r) / 2;
if (nums[mid] == target) {
return mid;
}else if (l == r) {
return nums[l] > target ? l : l + 1;
}else if (nums[mid] > target) {
return binarySearch(nums, target, l, mid);
}else {
return binarySearch(nums, target, mid + 1 , r);
}
}
}
4、34.在排序数组中查找元素的第一个和最后一个位置
给你一个按照非递减顺序排列的整数数组nums,和一个目标值target。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值target,返回[-1, -1]。
你必须设计并实现时间复杂度为O(log n)的算法解决此问题。
思路: 数组左右边界逼近,找到第一个值为target的位置(可能不存在)。
class Solution {
public int[] searchRange(int[] nums, int target) {
//左右边界逼近,二分查找
int left = 0, right = nums.length - 1;
while(left <= right) {
//求中点
int mid = left + (right - left) / 2;
if(target <= nums[mid]) {
right = mid - 1;
}else {
left = mid + 1;
}
}
//边界条件:数组长度为0,target比所有元素都大,target不在数组中
if(nums.length == 0 || left == nums.length || nums[left] != target) {
return new int[]{-1, -1};
}
//能找到第一个元素值等于target的位置,继续往后查找最后一个位置
int start = left;
//边界条件的判断要放在前面
while(left < nums.length && nums[left] == target) {
left++;
}
return new int[] {start, left-1};
}
}
注意
- 先进行左右边界的逼近;
- 找到第一个值为
target的位置/没有这样的元素; - 边界条件的判断要放在前面。
5、69.x的平方根
给你一个非负整数x,计算并返回x的算术平方根。
由于返回类型是整数,结果只保留整数部分,小数部分将被舍去。
不允许使用任何内置指数函数和算符,例如pow(x, 0.5)或者x ** 0.5。
思路: 可以用暴力或者二分法,考虑到时间复杂度,这里更推荐使用二分法。
暴力
class Solution {
public int mySqrt(int x) {
long i = 1;
for(; i <= x/2 + 1; i++) {
if((long)i * i == x) {
return (int)i;
}else if((long)i * i > x) {
break;
}
}
return (int)i - 1;
}
}
二分法
class Solution {
public int mySqrt(int x) {
//依然是一个左边界,一个右边界,一个最终结果
int left = 0, right = x, ans = -1;
while(left <= right) {
//求中点的值
int mid = left + (right - left) / 2;
if ((long)mid * mid <= x) {
ans = mid;
left = mid + 1;
} else {
//修改右边界,进一步缩小范围
right = mid - 1;
}
}
return ans;
}
}
注意
- 使用暴力解法需要考虑整数溢出问题并进行强制类型转换。
6、367.有效的完全平方数
给你一个正整数num。如果num是一个完全平方数,则返回true,否则返回false。
完全平方数是一个可以写成某个整数的平方的整数。
不能使用任何内置的库函数,如sqrt。
思路: 依旧是二分法。
class Solution {
public boolean isPerfectSquare(int num) {
//二分法标准流程:左边界,右边界,最后结果
int left = 0, right = num;
boolean ans = false;
while (left <= right) {
int mid = (right - left) / 2 + left;
long square = (long) mid * mid;
if (square < num) {
left = mid + 1;
} else if (square > num) {
right = mid - 1;
} else {
ans = true;
break;
}
}
return ans;
}
}
注意
- 使用long类型防止整数溢出;
- 是平方数的时候要直接返回或跳出循环,不然会继续执行直到超出边界,这样会造成时间超出限制。
总结
今天(2023.3.15)是我正式刷题第一天,也是我用博客进行记录的第一天。第一天题目难度不大,基本都可以用二分法解决,只是有些小细节需要注意。
写博客也算是一个总结回顾的过程,虽然需要花一些时间,但我认为是值得的,希望自己可以坚持下去!