本文声明:本文参考【代码随想录】的文章,思路以及代码。基于他的文章(希望大家直接去看原作者的优秀文章【代码随想录】,这篇博客仅为了记录自己的学习过程),形成了自己的理解,希望总结成自己的经验与知识,于是发表了这篇博客,如有不妥的地方欢迎大家指正。
一,数组的理论基础:
定义:数组是存放在连续内存空间上的相同类型数据的集合。
数组下标都是从0开始的。
数组内存空间的地址是连续的(重点)
特性:数组可以方便的通过下标索引的方式获取到下标下对应的数据。
正是因为数组的在内存空间的地址是连续的,所以我们在删除或者增添元素的时候,就难免要移动其他元素的地址。
二,数组的算法模板解题:
1,二分查找
原理讲解:
基于数组可以方便的通过下标索引的方式获取到下标下对应的数据的特性,因此数组的模板1为二分查找法:
问题描述: 二分查找涉及的很多的边界条件,逻辑比较简单,但就是写不好。例如到底是 while(left < right)
还是 while(left <= right)
,到底是right = middle
呢,还是要right = middle - 1
呢?
原理分析:为避免各种边界条件分不清:面对二分查找所需的第一步是确定区间的定义:这就是循环不变量规则。主要是因为对区间的定义没有想清楚,区间的定义就是不变量。要在二分查找的过程中,保持不变量,就是在while寻找中每一次边界的处理都要坚持根据区间的定义来操作
解决办法:区间的定义一般为两种,左闭右闭即[left, right],或者左闭右开即[left, right)。
注:以上内容均为复制粘贴【代码随想录】中的内容,第一次写博客(不太清楚规则),若有侵权联系我进行删除。
Leetcode实战:
思路:
本题的操作对象是有序数组(满足了二分查找的要求),且数组中元素并不重复(这些都是二分查找法的前提)。因此本题可使用二分查找法:
代码:
"""
大佬讲解:
二分法经常写乱,主要是因为对区间的定义没有想清楚,区间的定义就是不变量
二分查找涉及的很多的边界条件,逻辑比较简单,但就是写不好。例如到底是 while(left < right)
还是 while(left <= right),到底是right = middle呢,还是要right = middle - 1呢?
要在二分查找的过程中,保持不变量,就是在while寻找中每一次边界的处理都要坚持根据区间的定义来操作,这就是循环不变量规则。
写二分法,区间的定义一般为两种,左闭右闭即[left, right],或者左闭右开即[left, right)。
例如我自己写的就是左闭右闭型: 加上注释应该为:
high = len(nums) - 1 // 定义target在左闭右闭的区间里,[left, right]
low = 0
while (high >= low): // 当left==right,区间[left, right]依然有效,所以用 <=
mid = (high + low) / 2
if(nums[mid] == target):
return mid
elif nums[mid] > target:
high = mid - 1 // target 在左区间,所以[left, middle - 1]
else:
low = mid + 1 // target 在右区间,所以[middle + 1, right]
return -1
"""
class Solution(object):
def search(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: int
"""
'''
定义 target 是在一个在左闭右开的区间里,也就是[left, right) ,那么二分法的边界处理方式则截然不同。
有如下两点:
while (left < right),这里使用 < ,因为left == right在区间[left, right)是没有意义的
if (nums[middle] > target) right 更新为 middle,因为当前nums[middle]不等于target,去左区间继续寻找,
而寻找区间是左闭右开区间,所以right更新为middle,即:下一个查询区间不会去比较nums[middle]
'''
left = 0
right = len(nums) # 定义target在左闭右开的区间里,即:[left, right)
while(left < right):
mid = left + (right - left) / 2
if nums[mid] == target:
return mid
elif nums[mid] > target:
right = mid // target 在左区间,在[left, middle)中 左闭右开啊!!!
else:
left = mid + 1
return -1
2,双指针法
原理讲解:
双指针法(快慢指针法): 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。精髓:在一个for下完成两个for的工作。双指针法(快慢指针法)在数组和链表的操作中是非常常见的,很多考察数组、链表、字符串等操作的面试题,都使用双指针法。
问题描述:在做题时容易出现的懵逼点,代码写着写着就逻辑不清了
原理分析及解决方法:不理解 快慢指针究竟都是什么含义,所以一定要明确含义,后面的思路就更容易理解了。
Leetcode实战:
思路:定义快慢指针(的清楚):
- 快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
- 慢指针:指向更新 新数组下标的位置
3,滑动窗口(重难点)
原理讲解:
接下来就开始介绍数组操作中另一个重要的方法:滑动窗口。
所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。
在暴力解法中,是一个for循环滑动窗口的起始位置,一个for循环为滑动窗口的终止位置,用两个for循环 完成了一个不断搜索区间的过程。
那么滑动窗口如何用一个for循环来完成这个操作呢。首先要思考 如果用一个for循环,那么应该表示 滑动窗口的起始位置,还是终止位置。如果只用一个for循环来表示 滑动窗口的起始位置,那么如何遍历剩下的终止位置?此时难免再次陷入 暴力解法的怪圈。所以 只用一个for循环,那么这个循环的索引,一定是表示 滑动窗口的终止位置。那么问题来了, 滑动窗口的起始位置如何移动呢?
关键点:定义好窗口的起始位置(及其含义)和终止位置(及其含义)、 起始位置移动条件
滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)。
Leetcode实战:
题目描述:力扣(LeetCode)官网 - 209题长度最小的子数组
思路分析:
代码:
class Solution(object):
def minSubArrayLen(self, target, nums):
"""
:type target: int
:type nums: List[int]
:rtype: int
"""
result = 1000000002
sum = 0 #窗口内子串之和
i = 0 #窗口起始位置
sublength = 0 #窗口长度
for j in range(len(nums)): #j为终止位置的遍历
sum += nums[j]
while sum >= target:
sublength = j - i + 1
#result = result < sublength ? result : sublength
# if sublength < result:
# result = sublength
result = min(result,sublength) #python写法
#这里体现出滑动窗口的精髓之处,不断变更i(子序列的起始位置)
sum = sum - nums[i]
i += 1
#return result == 100000 ? 0 : result
# if result == 1000000002:
# return 0
# else:
# return result
return result if result != 1000000002 else 0 #python写法