二分查找:(注意是应用在有序数组里面!)

- 思路:设置三个下标(left mid right),在查找的过程中通过将目标元素比较mid下标对应的数字进行比较,大了就缩小right(right=mid-1),小了就放大left(left=mid+1),等于就是答案。
- 亿些细节:while(中到底是left<=right 还是left<right?)
这里就不得不涉及到数组的基础知识了,由于数组下标都是从0开始,一直到总长度-1,那我们在设置left和right的初始下标就要小心了!
①L=0 R=总长度-1:那么我们每一次比较都是有意义的,都是属于数组内部的,那么我们采用left<=right(推荐,比较容易理解)
②L=0 R=总长度:那么我们最后一次的比较没比较去比较=这种情况,因为一开始R指向的并不是数组内部的元素。left<right
class Solution { public int search(int[] nums, int target) { int left=0; int right=nums.length-1; while(left<=right){ int mid=(left+right)/2; if(nums[mid]>target){ right=mid-1; }else if(nums[mid]<target){ left=mid+1; }else return mid; } return -1; } }
移除元素:

- 思路:可以用两层暴力,但是推荐通过双指针优化成为一层for循环
- 理解数组是连续的内存空间,不能单独删除数组中的某个元素,只能覆盖。
- 一点细节:明确每个指针的意义和用途很重要!!如何确保覆盖这个动作!
快指针:用于指向后面去覆盖的元素
慢指针:用于指向待覆盖的元素
那么,如何让两个指针运作起来去 确保遇到了待覆盖的元素,然后快慢指针刚好相差1个单位呢?
这里借助卡尔哥的代码吧,真的很巧妙:
class Solution {
public int removeElement(int[] nums, int val) {
int slowIndex = 0;//慢指针
//快指针作为for循环里面的单位,会自动++
for (int fastIndex = 0; fastIndex < nums.length; fastIndex++) {
//只有当快指针没有遇到指定元素,快慢指针会一起同步运动
if (val != nums[fastIndex]) {
nums[slowIndex++] = nums[fastIndex];
}//除了快指针遇到就会快一个单位,然后自然就可以做到覆盖啦
}
return slowIndex;//注意返回的是慢指针下标!
}
}
有序数组的平方:

- 思路1:很简单,平方然后用Array.sort(nums)就好了,O(n^2)

- 思路2:

通过双指针从两边向中间移动,两个指针进行比较!
class Solution {
public int[] sortedSquares(int[] nums) {
int slow=0,fast=nums.length-1;
int []result=new int[nums.length];
//由于答案数组从小到大排序 倒叙!
for(int i=nums.length-1;i>=0;i--){
if(slow>fast) break;
int num1=nums[slow]*nums[slow];
int num2=nums[fast]*nums[fast];
if(num1>=num2){
result[i]=num1;
slow++;
}else{
result[i]=num2;
fast--;
}
}
return result;
}
}
总结:在数组里用双指针的话,必须是我们得去挖掘数组的信息(有序?还是趋势是什么?),要么就是通过速度差去完成某一个前后的动作等等!
长度最小的子数组

思路1:暴力
class Solution {
public int minSubArrayLen(int target, int[] nums) {
//两层暴力:
int result=nums.length+1; //用于记录是否有找到过答案的
for(int i=0;i<nums.length;i++){
int sum=0;
for(int j=i;j<nums.length;j++){
sum+=nums[j];
if (sum >= target) {
result = Math.min(result, j - i + 1);
break;
}
}
} return result>nums.length? 0:result; //是否有更改,没有意味着找不到就是0
}
}
明显第一个for用来规定起始位置,第二个for用来规定终止位置,O(n^2)的大小,超时
那么我们优化的时候绝对会思考滑动窗口,因为太自然了,但是第一次接触滑动窗口的小伙伴就会疑惑,只用一个for循环如何记录起始位置和终止位置的?
那我们就来分析:
①如果for定的是起始位置,必然少不了往后遍历的过程
②如果for定的是终止位置,那么如何记录起始位置?那么我们就来详细学习滑动窗口!
窗口内是什么?-------满足其和 ≥ s 的长度最小的 连续 子数组
什么时候移动窗口的起始位置?----窗口已经满足>=s了,为了寻找最短,移动起始位置
什么时候移动窗口的结束位置?----窗口内子数组 和<s,为了扩大
思路2:滑动窗口
模板:
// 开始滑动窗口(外面定结束位置) while (right < s.length()) { // c 是将移入窗口的字符 char c = s.charAt(right); // 增大窗口 right++; // 进行窗口内数据的一系列更新 // ... // 判断左侧窗口是否要收缩(里面定起始位置) while (window needs shrink) { // 这个条件根据具体问题来设定 // d 是将移出窗口的字符 char d = s.charAt(left); // 缩小窗口 left++; // 进行窗口内数据的一系列更新 // ... // 更新最小窗口大小 // ... } // 更新结果 // ... }
class Solution { public int minSubArrayLen(int target, int[] nums) { int left=0; int result=nums.length+1; int sum=0; for (int right = 0; right < nums.length; right++) { sum+=nums[right]; //窗口内已经找到了答案(注意不是if而是while,因为是连续的过程) while(sum>=target){ result=Math.min(result,right-left+1); //记录好了之后,移动起始位置,试探最短的长度 sum-=nums[left++]; } } return result>nums.length? 0:result; } }
疑惑:为什么又有for和while,时间复杂度是O(n)呢?
分析:
- 外层的
for循环用来移动right指针,它遍历整个数组一次。因此,这个循环的时间复杂度是O(n)。 - 内层的
while循环用来移动left指针,收缩窗口的大小,直到窗口内的数组和小于target。重要的是要注意,尽管看起来while循环可能会被执行多次,但实际上,每个元素最多只会被left指针和right指针各访问一次 - 由于每个元素只被访问两次(一次由
right指针,一次由left指针),整个过程的时间复杂度是线性的,即O(n)。尽管代码中有嵌套循环,但它们并不是传统意义上的嵌套循环,因为每个元素不会被多次重复处理。
双指针总结:
- 在有序数组中找到目标元素
- 用于有速度差达到覆盖目的的
- 数组有大小大趋势的,为了获得一个递增的数组
- 滑动窗口
- (约瑟夫环的问题!)
147

被折叠的 条评论
为什么被折叠?



