本文由GarfieldTheOldCat原创,转载请标明dekkyandlappy-CSDN博客
Day 1 数组基础,二分查找,移除元素
前几天还在忙毕业事宜,今天才开始正式学习算法,补上第一天的打卡,希望可以尽快追上进度。
数组基础
1.数组定义:存放在连续内存空间上的相同类型的数据的集合,使用下标索引获取对应数据(从0开始)
2.数组内存空间是连续的 -> 修改列表(删除元素,增加元素),需要移动其他元素的地址。
3.数组的元素是不能删除的,只能覆盖(如何理解?)
4.二维数组的地址是否连续与语言有关。
二分查找
基础
易错点
while(left<right) 还是 <=?
if(nums[middle]>target) right=middle 还是middle-1?
解决方法:
区间定义要明确(左闭右闭[],左闭右开[))
一开始左闭右闭->边界处理时坚持左闭右闭
原则
while:检查初始区间是否符合区间边界定义
if:避免判断过的边界值进入下一轮判断,需要结合区间开闭考虑
LC704:704. 二分查找
标准的二分查找问题。
首先采用遍历的方法尝试解决:Java代码如下,python的就懒得写了。
// 土方法:遍历下标 public int search_0(int[] nums, int target) { int lenth_num = nums.length; for (int pos = 0; pos < lenth_num; pos++) { int num = nums[pos]; if (target == num) return pos; else if (target < num) return -1; } return -1; }
随后试图使用二分法解决,但是第一次写的运行有问题(还没学):
// 二分查找,但是有问题
public int search_1(int[] nums, int target) throws Throwable {
int lenth_num = nums.length;
int small =0;
int big=lenth_num-1;
int middle=(big-small)/2;
while(small < big){
if (target < nums[middle]) big=middle;
else if ( target > nums[middle]) small =middle;
else return middle;
middle=small+(big-small)/2;
}
return -1;
核查以上代码,发现问题在于完全没有注意区间的开闭,导致while和if处有误,随后更正代码:
// 二分查找 左闭右闭
public int search_2(int[] nums, int target) throws Throwable {
int lenth_num = nums.length;
int small =0;
int big=lenth_num-1;
while(small <= big){
Thread.sleep(500);
int middle=(big+small)/2;
System.out.println(small+" "+big+" "+middle);
if (target < nums[middle]) big=middle-1;
else if ( target > nums[middle]) small =middle+1;
else return middle;
}
return -1;
}
// 二分查找 左闭右开
public int search(int[] nums, int target) throws Throwable {
int small =0;
int big=nums.length;
while(small<big){
int middle=(small+big)/2;
if (nums[middle]>target) big=middle;
else if (nums[middle]<target) small =middle+1;
else return middle;
}
return -1;
}
再用python完成二分查找:
# 二分查找,左闭右闭
def search_1(self, nums, target):
left = 0;
right = len(nums) - 1
while left <= right:
middle = int((left + right) / 2) # 取整的影响?
if nums[middle] > target:
right = middle - 1
elif nums[middle] < target:
left = middle + 1
else:
return middle
return -1
# 二分查找,左闭右开
def search(self, nums, target):
left = 0;
right = len(nums)
while left < right:
middle = int((left + right) / 2) # 取整的影响?
if nums[middle] > target:
right = middle
elif nums[middle] < target:
left = middle + 1
else:
return middle
return -1
另外,利用python中list的特性可以给出一种方法(当然不见得会更快):
# 直接使用python数组的特性:
# target in nums
# list.index(value):value在list中首次出现的下标
# 作弊解法,时间复杂度 O(N),
def search_0(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: int
"""
if target in nums:
return nums.index(target)
else:
return -1
LC34:34. 在排序数组中查找元素的第一个和最后一个位置
第一次写的时候错误百出,按照力扣官方题解写的。
Q:什么时候向上取整,什么时候向下取整?
java版本
// 基础二分v4
public int[] searchRange(int[] nums, int target)throws Exception {
// target_left
int right=nums.length-1;
int left=0;
int target_left;
int target_right = 0;
if((target<nums[0]) || (target>nums[nums.length-1])){
return new int[]{-1,-1};
}
while(left<right){
target_left=(left+right)/2;
if (nums[target_left]>target) right=target_left-1;
else if(nums[target_left]<target) left=target_left+1;
else right=target_left;
// System.out.println(target_left);
}
target_left=left;
if(nums[left]!=target){
return new int[]{-1,-1};
}
// System.out.println(left);
left=0;
right=nums.length-1;
while(left<right){
target_right=(left+right+1)/2;
// System.out.println(left+" "+target_right+" "+right);
// Thread.sleep(500);
if (nums[target_right]>target) right=target_right-1;
else if(nums[target_right]<target) left=target_right+1;
else left=target_right;
}
// System.out.println(left+" "+target_right+" "+right);
target_right=left;
// System.out.println(left+" "+target_right+" "+right);
return new int[]{target_left,target_right};
}
python版本
class Solution(object):
def searchRange(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: List[int]
"""
left=0
right=len(nums)-1
target_left=0
target_right=0;
if not nums: return [-1, -1]
if target<nums[0] or target>nums[-1]: return [-1, -1]
while left<right:
target_left=int((left+right)/2)
if nums[target_left]>target:right=target_left-1
elif nums[target_left]<target: left=target_left+1
else: right=target_left
if nums[left] !=target: return [-1, -1]
target_left=left
left=0
right=len(nums)-1
while left<right:
target_right=int((left+right+1)/2)
if nums[target_right]>target:right=target_right-1
elif nums[target_right]<target: left=target_right+1
else: left=target_right
target_right=left
return [target_left,target_right]
if __name__ == "__main__":
s = Solution()
print(s.searchRange([5,7,7,8,8,10],8))
LC35:35. 搜索插入位置
没什么好说的
public int searchInsert(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
int pos;
while (left <= right) {
pos = (left + right) / 2;
if (nums[pos] < target) left = pos + 1;
else if (nums[pos] > target) right = pos - 1;
else return pos;
}
System.out.println(left);
return left;
}
移除元素(双指针)
基础
数组中的元素在内存中是连续的,不能删除,只能覆盖(即将不需要删除的元素向前移动)
eraser操作的时间复杂度是O(N)
双指针法通过两个指针,在一次遍历中完成移除操作(此方法也可以对字符串等使用)
快指针:寻找不需要删除的元素,每次遍历+1;
慢指针:标记需要更新的下表,每更新一次+1
双指针法时间复杂度O(N),空间复杂度O(1)。
LC27: 27. 移除元素
最基本的双指针法,先按照暴力方法过一遍,对于这道题,暴力方法即为遍历数组,遇到需要删除的元素后把后面所有元素向前移动一位。
第一遍暴力解法运行结果与实际不符:
//暴力解法v1,错误
public int removeElement_1(int[] nums, int val) {
int k=0;
for(int i=0;i<nums.length;i++){
if (nums[i]==val){
for (int p=i+1;p<nums.length;p++){
nums[p-1]=nums[p];
}
k++;
}
}
return nums.length-k;
}
检查原因,发现在每每次移动之后,控制位置的i应该倒退一位,否则被删除的元素下一位若是需要删除的元素的话会被错过。
为了解决这个问题,一种办法是i--:
public int removeElement(int[] nums, int val) {
int size=nums.length;
for(int i=0;i<size;i++){
if (nums[i]==val){
for (int p=i+1;p<size;p++){
nums[p-1]=nums[p];
}
i--;
size--;
}
}
return size;
}
另一种办法是倒着处理:
public int removeElement_22(int[] nums, int val) {
int k=0;
for(int i=nums.length-1;i>=0;i--){
if (nums[i]==val){
for (int p=i+1;p<nums.length;p++){
nums[p-1]=nums[p];
}
k++;
}
}
return nums.length-k;
}
当然,今天的重点是双指针法,对于这道题,明白了双指针的思路后处理起来就不难了。
java版本
public int removeElement(int[] nums, int val) {
int fast;
int slow=0;
int k=0;
for (fast=0;fast<nums.length;fast++){
if(nums[fast]!=val){
nums[slow]=nums[fast];
slow++;
k++;
}
}
return k;
}
python版本
class Solution(object):
def removeElement(self, nums, val):
"""
:type nums: List[int]
:type val: int
:rtype: int
"""
slow=0
k=0
for fast in range(0,len(nums)):
if nums[fast] != val:
nums[slow]=nums[fast]
slow=slow+1
k=k+1
return k
总结
今天主要学习了数组的基本知识和两种算法:
二分查找:时间复杂度为O(log N)
双指针:是一种原地算法。
一个原地算法(in-place algorithm)是一种使用小的,固定数量的额外之空间来转换资料的算法。当算法执行时,输入的资料通常会被要输出的部分覆盖掉。不是原地算法有时候称为非原地(not-in-place)或不得其所(out-of-place)。
疑问与TODO
疑问
3.数组的元素是不能删除的,只能覆盖(如何理解?)
Q:什么时候向上取整,什么时候向下取整?
TODO
双指针:lc26,283,844,977