基本概念
-
时间复杂度:时间复杂度的计算并不是计算程序具体运行的时间,而是算法执行语句的次数。
-
空间复杂度:空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度。
-
使用二分法的前提:
- 数组为有序数组
- 数组中无重复元素
704. 二分查找
迭代法:
迭代法1:左闭右闭区间
class Solution {
public int search(int[] nums, int target) {
// 1.首先限定target落在数组里
if (target < nums[0] || target > nums[nums.length - 1]) {
return -1;
}
int left = 0;
// 2.target落在左闭右闭区间里:[left, right]
int right = nums.length -1;
// 当left = right时,依然在区间[left, right],所以这里用 <=
while (left <= right) {
// 这里等价于(left + right)/2,但是(left + right)可能会溢出
int mid = left + ((right - left) >> 1);
// 数组中找到目标值,返回数组下标
if (target == nums[mid]) {
return mid;
} else if (target < nums[mid]) {
// target落在左区间,所以将区间改为[left, mid-1]
right = mid -1;
} else if (target > nums[mid]) {
// target落在右区间,所以将区间改为[mid+1, right]
left = mid + 1;
}
}
return -1;
}
}
迭代法2:左闭右开区间
class Solution {
public int search(int[] nums, int target) {
// 1.首先限定target落在数组里
if (target < nums[0] || target > nums[nums.length - 1]) {
return -1;
}
int left = 0;
// 2.target落在左闭右闭区间里:[left, right)
int right = nums.length;
// 当left = right时,在区间[left, right)是无效空间,所以这里用 <
while (left < right) {
// 这里等价于(left + right)/2,但是(left + right)可能会溢出
int mid = left + ((right - left) >> 1);
// 数组中找到目标值,返回数组下标
if (target == nums[mid]) {
return mid;
} else if (target < nums[mid]) {
// target落在左区间,所以将区间改为[left, mid)
right = mid;
} else if (target > nums[mid]) {
// target落在右区间,所以将区间改为[mid+1, right)
left = mid + 1;
}
}
return -1;
}
}
下面我们来看一下二分法的两个迭代法的时间复杂度和空间复杂度:
时间复杂度:时间复杂度的计算并不是计算程序具体运行的时间,而是算法执行语句的次数。
假设总共有n个元素,每次查找的区间大小就是n,n/2,n/4,…,n/2^k,其中k就是循环的次数,
n/2^k >= 1(1最坏的情况,即还剩一个元素),
令n/2^k=1,
可得k=log2n(以2为底,n的对数),
所以时间复杂度可以表示O(log2n)。
空间复杂度:空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度。
因为变量只创建一次,所以空间复杂度为O(1)。
递归法:
递归法三部曲:
- 确定递归函数的参数和返回值
- 确定终止条件
- 确定单层递归的逻辑
class Solution {
public int search(int[] nums, int target) {
// 左闭右闭区间
// 1.确定递归函数的参数和返回值
int left = 0;
int right = nums.length - 1;
return binarySearch(nums, left, right, target);
}
private int binarySearch(int[] nums, int left, int right, int target) {
// 2.确定终止条件
// 注意:这里一定不要忘记left > right这个条件,否则会出现StackOverflowError
if (target < nums[0] || target > nums[nums.length - 1] || left > right) {
return -1;
}
// 3.确定单层递归的逻辑
int mid = left + ((right - left) >> 1);
if (target == nums[mid]) {
return mid;
} else if (target < nums[mid]) {
right = mid - 1;
return binarySearch(nums, left, right, target);
} else if (target > nums[mid]) {
left = mid + 1;
return binarySearch(nums, left, right, target);
}
return -1;
}
}
下面我们来看一下递归法的时间复杂度和空间复杂度:
时间复杂度为O(log2n),
递归算法的空间复杂度=递归深度N*每次递归所要的辅助空间
递归的次数和深度都是log2n,并且每进行一次递归都会创建变量,所以该递归法的空间复杂度为O(log2n)。
27. 移除元素
方法一:暴力法
class Solution {
public int removeElement(int[] nums, int val) {
int len = nums.length;
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;
}
}
时间复杂度:双层for循环,所以为O(n^2)
空间复杂度:O(1)
双指针法:快慢指针法
class Solution {
public int removeElement(int[] nums, int val) {
// 快指针:不包含目标元素
// 慢指针:更新原有数组
int slowIndex = 0;
for (int fastIndex = 0; fastIndex < nums.length; fastIndex++) {
if (val != nums[fastIndex]) {
// 这里等价于nums[slowIndex] = nums[fastIndex]; slowIndex++;
nums[slowIndex++] = nums[fastIndex];
}
}
return slowIndex;
}
}
时间复杂度:O(n)
空间复杂度:O(1)