滑动窗口思想
其主要思想就是用两个指针来确定题目中所要求的范围。主要要求三点:
- 窗口内是什么?
- 如何移动窗口的起始位置
- 如何移动窗口的结束位置
窗口就是满足其合小于target的长度最小的连续子数组
窗口的起始位置如何移动:如果窗口的值大于等于target了,窗口就要向前移动了(也就是该缩小了)
窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引
解题的关键就是窗口的起始位置如何移动?
此代码就是动态调节滑动窗口的起始位置
while(sum>=s){ // 这里使用while,
// 每次更新left位置,并不断比较子序列是否符合条件
result = Math.min(result,right-left+1);
sum-=nums[left++]; //不断调整起始位置
}
可以发现滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置,将时间复杂度将为o(n)
数组计算前缀和
**前缀和算法(Prefix Sum)**是一种用于快速计算数组元素之和的技术。它通过预先计算数组中每个位置前所有元素的累加和,将这些部分和存储在一个新的数组中,从而在需要计算某个区间的和时,可以通过简单的减法操作得到结果,而不必重新遍历整个区间。
1、子数组和的计算:通过前缀和,可以快速计算任意子数组的和,从而解决一系列相关问题,如最大子数组和、最小子数组和等。
2、区间和的查询:如果需要频繁查询某个区间的和,可以利用前缀和提前计算出所有区间的和,并存储在辅助数组中,以实现快速查询。
3、数组元素更新:当数组中的元素需要频繁更新时,通过前缀和可以减少更新的时间复杂度,提高算法效率
双指针思想
快慢指针思想:通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。
定义快慢指针
- 快指针:寻找新数组的元素,新数组就是不含有目标元素的数组
- 慢指针:指向更新 新数组下标的位置
双指针方法适用于字符串,列表等能直接访问的数据结构,且有序。让两个指针从快慢或头尾分别遍历,使之满足条件
原地哈希
原地哈希用来解决这样一种问题:需要一个使得数组尽量有序的方式,并且要求[时间复杂度]达到O(n)。
原地哈希就相当于,让每个数字n都回到下标为n-1的位置。那些没有找到位置的元素要么1.小于0或者大于数组的最大范围 2.出现了重复。
这些数组被放置在i的位置上是因为元素i+1的缺失。因此需要构建原地哈希来找出重复或者消失的元素。
其中,主要代码为nums[nums[i] - 1] != nums[i],即nums[i]是否在其应该在的位置nums[nums[i]-1]上。如果不在则进行交换。如果在,比较
nums[nums[i]-1]和nums[i]如果相同,则表明重复。
leecode977
public class leecode977 {
//给你一个按 非递减顺序 排序的整数数组 nums,
// 返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
public static void main(String[] args) {
int[]nums ={-4,-1,0,3,10};
sortedSquares_01(nums);
}
public int[] sortedSquares(int[] nums) {
/**
* 题解:方法一,直接排序
* 1.遍历数组并平方
* 2.排序
*/
int []ans =new int[nums.length];
for (int i = 0; i < nums.length; i++) {
ans[i] = nums[i] * nums[i];
}
Arrays.sort(ans);
return ans;
}
public static int[] sortedSquares_01(int[] nums){
/*
方法二:双指针
1.先将数组平方
2.设两个左右指针,并开始比较大小
3.将大的放入最后,直到结束
*/
int len = nums.length;
int i = 0,j = len - 1;
int []ans = new int[len];
for(int p = len - 1;p>=0;p--){
int left = nums[i]*nums[i];
int right = nums[j]*nums[j];
if(left > right){
ans[p] = left;
i++;
}else{
ans[p] = right;
j--;
}
}
return ans;
}
}
此题应用了双指针这一经典算法,从两边往中间遍历排序。
leecode_27
public class leeCode27 {
//给你一个数组 nums 和一个值 val,
// 你需要 原地 移除所有数值等于 val 的元素。
// 元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。
public int removeElement(int[] nums, int val) {
/**
* 题解:
* 将与val值不相同的元素放入原来的数组中
*/
int k = 0;
for(int x:nums){
if(x!=val){
nums[k++] = x;
}
}
return k;
}
}
leecode_209
private int minSubArrayLen_01(int s,int[] nums){
int left = 0; //滑动窗口起始位置
int sum = 0; //滑动窗口数值之和
int result = Integer.MAX_VALUE;
for(int right = 0;right < nums.length;right++){
sum+=nums[right];
while(sum>=s){ // 这里使用while,
// 每次更新left位置,并不断比较子序列是否符合条件
result = Math.min(result,right-left+1);
sum-=nums[left++];
}
}
//如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
return result == Integer.MAX_VALUE?0:result;
}
leecode_442
public class leecode442 {
/*
给你一个长度为 n 的整数数组 nums ,其中 nums 的所有整数都在范围 [1, n] 内,且每个整数出现 一次 或 两次 。
请你找出所有出现 两次 的整数,并以数组形式返回。
*/
public List<Integer> findDuplicates(int[] nums) {
/**
* 思想:
* 原地哈希,对于值为k的数字,他应该出现的位置为k-1
*
* 从前往后遍历nums,并尝试处理num[i]放到目标位置num[i]-1处
* 如果发现
*/
List<Integer> ans = new ArrayList<>();
int len = nums.length;
for (int i = 0; i < len; i++) {
int t = nums[i];
if(t<0||t-1 == i) continue; //数据小于0或者本身就在原来的位置上
if(nums[t-1] == t){
//如果t和原本nums[i-1]位置上的数字相同,说明重复
ans.add(t);
nums[i]*=-1;
}else{
//如果t和原本nums[i-1]位置上的数字不相同
// 将此位置上的t与nums[t-1]上的数字进行交换
//nums[t-1]原本的数放在i的位置上,再将i-1等待下一轮循环再进行比较
int tmp = nums[t-1];
nums[t-1] = t;
nums[i--] = tmp;
}
}
return ans;
}
leecode_41
public class leecode41 {
/*给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。
请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。
*/
public int firstMissingPositive(int[] nums) {
/**
* 思想:
* 原地哈希所有元素应该在其应该在的位置上,即1应该在nums[0]的位置
*/
int len = nums.length;
for (int i = 0; i < len; i++) {
//if(nums[i]<0||nums[i]>len||nums[i] - 1== i) continue;
while(nums[i]>0 && nums[i]<=len && nums[nums[i]-1] != nums[i]){
int tmp = nums[nums[i]-1];
nums[nums[i]-1] = nums[i];
nums[i] = tmp;
}
}
for (int i = 0; i < len; i++) {
if(nums[i]!=i+1){
return i+1;
}
}
return len+1;
}
}