1. 数组基础理论
数组是存放在连续内存空间上的相同类型数据的集合。数组可以方便的通过下标索引的方式获取到下标下对应的数据。
需要两点注意的是:
- 数组下标都是从0开始的。
- 数组内存空间的地址是连续的
因为数组的在内存空间的地址是连续的,所以我们在删除或者增添元素的时候,就难免要移动其他元素的地址。数组的元素是不能删的,只能覆盖。
2. 二分查找 704. Binary Search 💚
前提:
- 有序数组
- 数组中无重复元素,因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的
思路:重中之重是确定好边界条件,这里使用左闭右闭[left, right] while (left <= right) 要使用 <= ,因为left == right是有意义的,所以使用 <= if (nums[middle] < target) left 要赋值为 middle + 1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle +1。
class Solution{
public int search(int[] nums, int target) {
int low = 0;
int high = nums.length -1;
while(low <= high){
int mid = low + (high - low)/2;
if(nums[mid] == target){
return mid;
}
if(nums[mid] < target){
low = mid + 1;
}else{
high = mid - 1;
}
}
return -1;
}
}
//time:O(log n)
3. 移除元素 27. Remove Element 💚
双指针: 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。 定义快慢指针 快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组 慢指针:指向更新 新数组下标的位置 time: O(n)因为只有一个for loop 思路:快指针遍历,把值不等于要删除的数,赋给慢指针。当快指针的值等于要删除的元素时,快指针继续往后走,慢指针不动。
class Solution {
public int removeElement(int[] nums, int val) {
// 快慢指针
// time :O(n)
int slow = 0;
int fast = 0;
while(fast < nums.length){
if(nums[fast] != val){
nums[slow] = nums[fast];
slow++;
}
fast++;
}
return slow;
}
}
4. 有序数组的平方 977. Squares of a Sorted Array 💚
思路:双指针 数组其实是有序的, 只不过负数平方之后可能成为最大数了。那么数组平方的最大值就在数组的两端,不是最左边就是最右边,不可能是中间。此时可以考虑双指针法了,i指向起始位置,j指向终止位置。
定义一个新数组result,和A数组一样的大小,让k指向result数组终止位置。
- 如果A[i] * A[i] < A[j] * A[j] 那么result[k--] = A[j] * A[j]; 。
- 如果A[i] * A[i] >= A[j] * A[j] 那么result[k--] = A[i] * A[i];
class Solution {
public int[] sortedSquares(int[] nums) {
int left = 0;
int right = nums.length - 1;
int[] res = new int[nums.length];
for(int k = nums.length - 1; k >= 0; k--){
if(nums[left] * nums[left] >= nums[right] * nums[right]){
res[k] = nums[left] * nums[left];
left++;
}else{
res[k] = nums[right] * nums[right];
right--;
}
}
return res;
}
}
//time: O(n)
5. 长度最小的子数组 209. Minimum Size Subarray Sum 🧡
思路:所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。
在暴力解法中,是一个for循环滑动窗口的起始位置,一个for循环为滑动窗口的终止位置,用两个for循环 完成了一个不断搜索区间的过程。
那么滑动窗口如何用一个for循环来完成这个操作呢。
首先要思考 如果用一个for循环,那么应该表示 滑动窗口的起始位置,还是终止位置。如果只用一个for循环来表示 滑动窗口的起始位置,那么如何遍历剩下的终止位置?此时难免再次陷入 暴力解法的怪圈。
所以 只用一个for循环,那么这个循环的索引,一定是表示 滑动窗口的终止位置。
那么问题来了, 滑动窗口的起始位置如何移动呢? 在本题中实现滑动窗口,主要确定如下三点:
- 窗口内是什么?
- 如何移动窗口的起始位置?
- 如何移动窗口的结束位置? 窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组。
窗口的起始位置如何移动:如果当前窗口的值大于s了,窗口就要向前移动了(也就是该缩小了)。
窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。
简单来说,就是移动终止位置,直到窗口中的数大于等于目标值,然后移动开始位置,找到最小数组。然后继续移动终止位置...循环 时间复杂度: O(n)。不要以为for里放一个while就以为是O(n^2)啊, 主要是看每一个元素被操作的次数,每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被操作两次,所以时间复杂度是 2 × n 也就是O(n)。
注意最终return时的res判断,有可能没有找到,所以要判断一下,return 0
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int start = 0;
int res = Integer.MAX_VALUE;
int sum = 0;
for (int end = 0; end < nums.length; end++){
sum += nums[end];
while(sum >= target){
int step = end - start + 1;
res = res < step ? res : step;
sum = sum - nums[start];
start++;
}
}
return res == Integer.MAX_VALUE ? 0 : res;
}
}
//time: O(n)
//space:O(1)
6. 螺旋矩阵
6.1 螺旋矩阵 54. Spiral Matrix 🧡
思路: 循环不变量,循环时,每条边的处理规则应该相同
对于每一条边,边界条件都是处理所有数,对于处理完成的边,移动整条边界。
如首先处理最上面的边,处理完成后,把整条上边界下移,即++up。 并判断up下移后是否在down的下面,如果在,则说明已经螺旋完成,break退出循环。up加完过后,右边处理时,up的此时值为1,即跳过已经处理好的右上角。
class Solution {
public List<Integer> spiralOrder(int[][] matrix) {
int m = matrix.length;//宽
int n = matrix[0].length;//长
int left = 0;
int right = n - 1;
int up = 0;
int down = m - 1;
List<Integer> res = new ArrayList<>();
while(true){
for(int i = left; i <= right; i++){
res.add(matrix[up][i]);
}
if(++up > down){
break;
}
for(int i = up; i <= down; i++){
res.add(matrix[i][right]);
}
if(--right < left){
break;
}
for(int i = right; i >= left; i--){
res.add(matrix[down][i]);
}
if(--down < up){
break;
}
for(int i = down; i >= up; i--){
res.add(matrix[i][left]);
}
if(++left > right){
break;
}
}
return res;
}
}
//time O(m*n)
//space O(1)
6.2 螺旋矩阵II 59. Spiral Matrix II 🧡
class Solution {
public int[][] generateMatrix(int n) {
//循环不变量
//循环时,每条边的处理规则应该相同
//左闭右开,处理第一个节点,最后一个节点留给下一条边处理
int left = 0;
int right = n - 1;
int up = 0;
int down = n - 1;
int count = 1;
int[][] res = new int[n][n];
while(true){
for(int i = left; i <= right; i++){
res[up][i] = count;
count++;
}
if(++up > down){
break;
}
for(int i = up; i <= down; i++){
res[i][right] = count;
count++;
}
if(--right < left){
break;
}
for(int i = right; i >= left; i--){
res[down][i] = count;
count++;
}
if(--down < up){
break;
}
for(int i = down; i >= up; i--){
res[i][left] = count;
count++;
}
if(++left > right){
break;
}
}
return res;
}
}