文章目录
数组理论基础
数组是存放在连续内存的相同类型数据的集合 通过下标索引的方式获取对应下标下对应的数据 数组下标从0开始
因为数组的在内存空间的地址是连续的,所以我们在删除或者增添元素的时候,就难免要移动其他元素的地址
数组增加或删除操作复杂 数组的元素不能删,只能覆盖
数组查询操作简单
Java二维数组的地址值并不是连续的
排列方式如下:
二分查找
特征
升序、无重复的数组查询
易错点
1. while(left<=right) or while(left<right)
2. if(arr[mid]>target) right = mid-1; or if(arr[mid]>target) right = mid;
3. righr = arr.length-1; or righr = arr.length;
两种写法
- [left,right] [1,1] 左闭右闭
- [left,right) [1,2) 左闭右开
题目[二分查找]
704 二分查找
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1
/*
解法1:【left,right】
*/
class Solution {
public int search(int[] nums, int target) {
if(nums.length==0){
return -1;
}
int left = 0;
// 注意点
int right = nums.length-1;
// 注意点
while(left <= right){
int mid = (left + right)/2;
// 注意点
if(nums[mid] > target){
right = mid -1;
}
if(nums[mid] < target){
left = mid + 1;
}
if(nums[mid] == target){
return mid;
}
}
return -1;
}
}
/*
解法2:【left,right)
*/
class Solution {
public int search(int[] nums, int target) {
if(nums.length==0){
return -1;
}
int left = 0;
// 注意点
int right = nums.length;
// 注意点
while(left < right){
int mid = (left + right)/2;
// 注意点
if(nums[mid] > target){
right = mid ;
}
if(nums[mid] < target){
left = mid + 1;
}
if(nums[mid] == target){
return mid;
}
}
return -1;
}
}
35 搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。假设数组无重复元素
思路:
- 目标值在数组所有元素之前
- 目标值等于数组的某个元素
- 目标值插入数组的位置
- 目标值在数组所有元素之后
/*
解法1: 左比右闭
*/
class Solution {
public int searchInsert(int[] nums, int target) {
if(nums.length == 0){
return 0;
}
int left = 0;
int right = nums.length -1;
// target在数组元素中
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;
}
}
// target不在数组元素中,在数组元素之前
// target在数组元素之后
// target插入数组的位置
return right + 1;
}
}
/*
解法2: 左比右开
*/
class Solution {
public int searchInsert(int[] nums, int target) {
if(nums.length == 0){
return 0;
}
int left = 0;
int right = nums.length;
// target在数组元素中
while(left < right){
int mid = (left + right)/2;
if(nums[mid] > target){
right = mid ;
}else if(nums[mid] < target){
left = mid + 1;
}else{
return mid;
}
}
// target不在数组元素中,在数组元素之前
// target在数组元素之后
// target插入数组的位置
return right;
}
}
34 在排序数组中查找元素的第一个和最后一个位置【中等】
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。如果数组中不存在目标值 target,返回 [-1, -1]。
思路:
- target 在数组范围的右边或者左边,返回{-1, -1}
- target 在数组范围中,且数组中不存在target,返回{-1, -1}
- target 在数组范围中,且数组中存在target,返回{开始位置, 结束位置}
找左边界与右边界,然后判断三种类型
class Solution {
int[] searchRange(int[] nums, int target) {
int leftBorder = getLeftBorder(nums, target);
int rightBorder = getRightBorder(nums, target);
// 情况一
if (leftBorder == -2 || rightBorder == -2) return new int[]{-1, -1};
// 情况三
if (rightBorder - leftBorder > 1) return new int[]{leftBorder + 1, rightBorder - 1};
// 情况二
return new int[]{-1, -1};
}
int getRightBorder(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
int rightBorder = -2; // 记录一下rightBorder没有被赋值的情况
while (left <= right) {
int middle = left + ((right - left) / 2);
if (nums[middle] > target) {
right = middle - 1;
} else { // 寻找右边界,nums[middle] == target的时候更新left
left = middle + 1;
rightBorder = left;
}
}
return rightBorder;
}
int getLeftBorder(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
int leftBorder = -2; // 记录一下leftBorder没有被赋值的情况
while (left <= right) {
int middle = left + ((right - left) / 2);
if (nums[middle] >= target) { // 寻找左边界,nums[middle] == target的时候更新right
right = middle - 1;
leftBorder = right;
} else {
left = middle + 1;
}
}
return leftBorder;
}
}
69. x的算数平方根
给你一个非负整数 x ,计算并返回 x 的 算术平方根 。由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。
思路:0为左,x为右,就是求k的平方小于等于x时,k的最大值
class Solution {
public int mySqrt(int x) {
int l =0;
int r = x;
int ans = -1;
while(l<=r){
int mid = l + (r-l)/2;
// 解决范围不够用
if((long)mid*mid<=x){
ans = mid;
l = mid + 1;
}else{
r = mid -1;
}
}
return ans;
}
}
367. 有效的完全平方数
给定一个 正整数 num ,编写一个函数,如果 num 是一个完全平方数,则返回 true ,否则返回 false 。
进阶:不要 使用任何内置的库函数,如 sqrt 。
思路: 左为0,右为num 然后用二分查询 就好了
class Solution {
public boolean isPerfectSquare(int num) {
int left = 0,right = num;
while(left <= right){
int mid = (left + right)/2;
if((long)mid*mid > num){
right = mid -1;
}else if((long)mid * mid < num){
left = mid +1;
}else{
return true;
}
}
return false;
}
}
题目[移除元素]
要知道数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖
27 移除元素
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度
思路: slowIndex 用于标记覆盖的位置 fastIndex用于标记不等于val的元素 然后进行相应的覆盖 就可以了,并对slowIndex fastIndex自增
public int removeElement(int[] nums, int val) {
// 双指针(快慢指针法)
if(nums.length == 0){
return 0;
}
// 标记要覆盖的位置
int slowIndex=0;
// 标记不等于val的位置
int quickIndex ;
for(quickIndex = 0;quickIndex<nums.length;quickIndex++){
if(nums[quickIndex]!=val){
nums[slowIndex] = nums[quickIndex];
slowIndex++;
}
}
return slowIndex;
26. 删除有序数组中的重复项
给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持一致
class Solution {
public int removeDuplicates(int[] nums) {
/*
双指针---快慢指针
*/
if(nums.length==0){
return 0;
}
int slowIndex = 0;
int fastIndex = 0;
for(fastIndex = 0;fastIndex < nums.length;fastIndex++){
if(nums[slowIndex] != nums[fastIndex]){
nums[++slowIndex] = nums[fastIndex];
}
}
return slowIndex+1;
}
}
283. 移动零
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。请注意 ,必须在不复制数组的情况下原地对数组进行操作
思路:将不是0的位置上往前移,然后将对应数量的0补充在数组后面
class Solution {
public void moveZeroes(int[] nums) {
/*
双指针(快慢)
*/
if(nums.length==0)
return;
int slowIndex = 0,fastIndex = 0;
for(;fastIndex<nums.length;fastIndex++){
if(nums[fastIndex] != 0){
nums[slowIndex] = nums[fastIndex];
slowIndex++;
}
}
for(int i=0;i<nums.length-slowIndex;i++){
nums[slowIndex + i] = 0;
}
}
}
844. 比较含退格的字符串
给定 s 和 t 两个字符串,当它们分别被输入到空白的文本编辑器后,如果两者相等,返回 true 。# 代表退格字符。注意:如果对空文本输入退格字符,文本继续为空。
思路:
1.将字符串转为数组
2. 定义双指针,如果fast上的元素,不等于#,就覆盖slow位置上的值
3. 如果等于#,就让slow自减
4. 然后每个都求省略后的元素,然后isequal就好了
class Solution {
public boolean backspaceCompare(String s, String t) {
char[] chs1 = s.toCharArray();
char[] chs2 = t.toCharArray();
return solve(chs1).equals(solve(chs2));
}
private String solve(char[] chs) {
/*
双指针模拟退格
*/
int len = chs.length;
int slow = -1, fast = 0;
while (fast < len) {
if (chs[fast] != '#') {
// 普通字母直接统计
chs[++slow] = chs[fast];
} else {
// 遇到#考虑退格(注意slow只统计非#的字符,并不会覆盖掉fast)
if (slow >= 0) slow--;
}
fast++;
}
// slow位索引就是最后一个字母位置,索引+1就是个数->生成String
return new String(chs, 0, slow + 1);
}
}
977. 有序数组的平方
给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方组成的新数组,要求也按 非递减顺序 排序
思路:双指针-----一个在头,一个在尾,当头<=尾是,进行一系列比较
class Solution {
public int[] sortedSquares(int[] nums) {
/*
// 暴力遍历法
for(int i=0;i<nums.length;i++){
nums[i] = nums[i]*nums[i];
}
Arrays.sort(nums);
return nums;
*/
// 双指针法(始终法)
int right = nums.length - 1;
int left = 0;
int[] result = new int[nums.length];
int index = result.length - 1;
while (left <= right) {
if (nums[left] * nums[left] > nums[right] * nums[right]) {
result[index--] = nums[left] * nums[left];
++left;
} else {
result[index--] = nums[right] * nums[right];
--right;
}
}
return result;
}
}
题目【滑动窗口】
- 滑动窗口:不断调节子序列的起始位置和终止位置,得到想要的结果(是一种双指针法)
- O(n^2)------> O(n)
209 长度最小的子数组【中等】
给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。
思路:窗口内是什么?满足其和 ≥ s 的长度最小的连续子数组
如何移动窗口的起始位置?如果当前窗口的值大于s了,窗口就要向前移动
如何移动窗口的结束位置?窗口的结束位置就是遍历数组的指针
为什么复杂度是O(n)?
不要以为for里放一个while就以为是O(n^2)啊, 主要是看每一个元素被操作的次数,每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被操作两次,所以时间复杂度是 2 × n
也就是O(n)
class Solution {
public int minSubArrayLen(int target, int[] nums) {
// 滑动窗口
int left = 0; // 滑动窗口起始位置
int sum = 0; // 滑动窗口数字之和
int result = Integer.MAX_VALUE; // 满足要求的字序列长度
for(int right=0; right<nums.length;right++){
sum = sum + nums[right];
// 使用while,每次更新left,并不断比较子序列是否符合条件
while(sum >= target){
result = Math.min(result,right-left+1);
sum = sum - nums[left++]; // 不断变动left
}
}
// 如果result没有赋值的话,就返回0,说明没有符合条件的子序列
return result == Integer.MAX_VALUE?0:result;
}
}
904. 水果成篮【中等】
76. 最小覆盖子串【困难】
题目【螺旋矩阵】
59. 螺旋矩阵 II
循环不变量: 左闭右开
给你一个正整数 n ,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。
class Solution {
public int[][] generateMatrix(int n) {
int loop = 0; // 控制循环次数
int[][] res = new int[n][n];
int start = 0; // 每次循环的开始点(start, start)
int count = 1; // 定义填充数字
int i, j;
while (loop++ < n / 2) { // 判断边界后,loop从1开始
// 模拟上侧从左到右
for (j = start; j < n - loop; j++) {
res[start][j] = count++;
}
// 模拟右侧从上到下
for (i = start; i < n - loop; i++) {
res[i][j] = count++;
}
// 模拟下侧从右到左
for (; j >= loop; j--) {
res[i][j] = count++;
}
// 模拟左侧从下到上
for (; i >= loop; i--) {
res[i][j] = count++;
}
start++;
}
if (n % 2 == 1) {
res[start][start] = count;
}
return res;
}
}
54.螺旋矩阵
29.顺时针打印矩阵
总结
参考的代码随想录进行的相关学习 https://programmercarl.com/