算法训练营(day01)

数组理论基础

  • 数组是存放在连续内存空间上的相同类型数据的集合。

  • 数组可以方便的通过下标索引的方式获取到下标对应的数据。

    • 数组下标都是从0开始的。
    • 数组内存空间的地址是连续的

算法通关数组

  • 因为数组的在内存空间的地址是连续的,所以我们在删除或者增添元素的时候,只能覆盖原有地址的元素进行修改

704.二分查找

题目链接:https://leetcode.cn/problems/binary-search/description/

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

二分查找理论基础

使用二分查找需要具备两个条件:

  1. 数组有序
  2. 无重复数组

需要满足这两个条件,使用二分法才能找到 唯一 的下标地址,得出结果。当然,并不是说不满足这两个条件就不能使用二分法,可以适当根据题意对二分法进行调整,比如下面的 34.在排序数组中查找元素的第一个和最后一个位置,就使用了二分法加中心扩散的思想。

使用二分法最大的优点就是可以降低时间复杂度,达到**O(nlogN)**,对于笔试刷题,能大大提高AC效率。

解题思路

首先,题目很明显是使用二分查找法解题,需要考虑的是二分查找区间定义: 左闭右开[left, right) 还是 左闭右闭[left, right]

解题过程:

  1. 设定左右指针left,right
  2. 找出中间位置mid,并判断该位置值 num[mid] 是否等于 target
    • nums[mid] == target 则返回该位置下标
    • nums[mid] > target 则右侧指针移到中间
    • nums[mid] < target 则左侧指针移到中间
  3. 时间复杂度:O(logN)

详细代码

一般我个人更喜欢使用 左闭右闭,个人感觉更易理解,下面将给出两种解决方式。

左闭右闭:right = nums.length - 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public int search(int[] nums, int target) {
int len = nums.length;
int left = 0, right = len - 1;
while(left <= right){
int mid = left + ((right - left) >> 1);
if(nums[mid] == target){
return mid;
}else if(nums[mid] > target){
right = mid - 1;
}else{
left = mid + 1;
}
}
return -1;
}
}

左闭右开:right = nums.length

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public int search(int[] nums, int target) {
int len = nums.length;
int left = 0, right = len;
while(left < right){
int mid = left + ((right - left) >> 1);
if(nums[mid] == target){
return mid;
}else if(nums[mid] > target){
right = mid;
}else{
left = mid + 1;
}
}
return -1;
}
}

35.搜索插入位置

题目链接:https://leetcode.cn/problems/search-insert-position/

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

请必须使用时间复杂度为 O(log n) 的算法。

解题思路

看到题目有要求时间复杂度为 O(log n),可以考虑使用二分法。题目有个插入的要求,简单理解就是 插队,如果这个数据插入数组中,那么后面的数组都要向右延一个下标,所以插入的目标值返回的下标是左侧指针

解题过程:

  1. 设定左右指针left,right
  2. 找出中间位置mid,并判断该位置值 num[mid] 是否等于 target
    • nums[mid] == target 则返回该位置下标
    • nums[mid] > target 则右侧指针移到中间
    • nums[mid] < target 则左侧指针移到中间
  3. 如果全局都没找到target,则返回左侧指针下标
  4. 时间复杂度:O(logN)

详细代码

暴力解法:直接遍历,如果数组中有比目标值大的数, 则目标值 插队 在该数前面,否则排在数组最后面

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public int searchInsert(int[] nums, int target) {
if (nums == null || nums.length == 0){
return 0;
}
for (int i = 0; i < nums.length; i++){
if (target <= nums[i]){
return i;
}
}
return nums.length;
}
}

二分法:左闭右闭

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public int searchInsert(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while(left <= right){
int mid = left + ((right - left) >> 1);
if(nums[mid] == target){
return mid;
}else if(nums[mid] > target){
right = mid - 1;
}else{
left = mid + 1;
}
}
return left;
}
}

34.在排序数组中查找元素的第一个和最后一个位置

题目链接:https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。如果数组中不存在目标值 target,返回 [-1, -1]

你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

解题思路

题目给定了有序,所以可以试试暴力解法,但若要满足时间复杂度,要考虑使用二分法+中心扩散

思路一:暴力遍历

解题过程:

  1. 设定左右指针left,right

  2. 位置值 num[left]num[right] 是否等于 target

    • nums[left] != target 则左侧指针右移
    • nums[right] != target 则右侧指针左移
    • nums[left] == target ||nums[right] != target 则指针停止移动
  3. 返回新数组 new int[]{left, right}

  4. 如果全局都不满足,则返回 new int[]{-1, -1}

思路二:二分法+中心扩散

解题过程:

  1. 设定左右指针left,right
  2. 找出中间位置mid,并判断该位置值 num[mid] 是否等于 target
    • nums[mid] == target 则以 mid 为初始值,向左向右遍历,找到边界值
    • nums[mid] > target 则右侧指针移到中间
    • nums[mid] < target 则左侧指针移到中间
  3. 返回新数组 new int[]{left, right}
  4. 如果全局都不满足,则返回 new int[]{-1, -1}

详细代码

暴力解法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public int[] searchRange(int[] nums, int target) {
if(nums == null){
return new int[]{-1, -1};
}
int len = nums.length;
int left = 0, right = len - 1;
while(left <= right){
if(nums[left] == target && nums[right] == target){
return new int[]{left, right};
}
if(nums[left] < target){
left++;
}
if(nums[right] > target){
right--;
}
}
return new int[]{-1, -1};
}
}

二分法+中心扩散

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Solution {
public int[] searchRange(int[] nums, int target) {
int left = 0, right = nums.length - 1;
int[] res = new int[2];
while(left <= right){
int mid = left + ((right - left) >> 1);
if(nums[mid] == target){
for(int i = mid; i < nums.length; i++){
if(nums[i] > target){
res[1] = i - 1;
break;
}
if(i == right){
res[1] = i;
}
}
for(int i = mid; i >= 0; i--){
if(nums[i] < target){
res[0] = i + 1;
break;
}
}
return res;
}else if(nums[mid] > target){
right = mid - 1;
}else{
left = mid + 1;
}
}
return new int[]{-1, -1};
}

}

27.移除元素

题目链接:https://leetcode.cn/problems/remove-element/

给你一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,并返回移除后数组的新长度。不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

快慢指针理论基础

双指针法(快慢指针法): 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作

  • 快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
  • 慢指针:指向更新 新数组下标的位置

27.移除元素-双指针法

解题思路

思路一:暴力遍历

简单直接的遍历数组,只要和val不一样的就保留下来,一样的就不管。

解题过程:

  1. 定义一个常量res
  2. 遍历数组,判断 nums[i] 值是否等于val
    • nums[i] == val 则不管
    • nums[i] != val 则nums[res++] = nums[i]
  3. 返回res

思路二:快慢指针(双指针法)

其实和暴力遍历没什么区别,理解起来就是 快指针 检查数组,慢指针 整合数组

解题过程:

  1. 设定快慢指针fast,slow

  2. 让 fast 遍历数组

    • nums[fast] != val 则 nums[slow] 记录当前的 nums[fast]
    • 否则不计
  3. 返回 slow

详细代码

暴力遍历:

1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public int removeElement(int[] nums, int val) {
int res = 0;
for(int num : nums){
if(num != val){
nums[res] = num;
res++;
}
}
return res;
}
}

快慢指针:

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public int removeElement(int[] nums, int val) {
int slow = 0;
for (int fast = 0; fast < nums.length; fast++){
if (val != nums[fast]){
nums[slow++] = nums[fast];
}
}
return slow;
}
}