提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、力扣704. 二分查找
- 二、力扣35. 搜索插入位置
- 三、力扣34. 在排序数组中查找元素的第一个和最后一个位置
- 四、力扣69. x 的平方根
- 五、力扣367. 有效的完全平方数
- 六、力扣27. 移除元素
- 七、力扣26. 删除有序数组中的重复项
- 八、力扣283. 移动零
- 九、力扣844. 比较含退格的字符串
- 十、力扣977. 有序数组的平方
- 十一、力扣209. 长度最小的子数组
- 十二、力扣904. 水果成篮
- 十三、力扣76. 最小覆盖子串
- 十四、力扣59. 螺旋矩阵 II
- 十五、力扣54. 螺旋矩阵
- 十六、力扣LCR 146. 螺旋遍历二维数组
- 十七、卡码网 58. 区间和(第九期模拟笔试)
- 十八、卡码网 44. 开发商购买土地(第五期模拟笔试)
前言
数组中的五个经典操作 一、二分法 二、双指针法 三、滑动窗口 四、模拟行为 五、前缀和
一、力扣704. 二分查找
·二分查找就是在一个无重复元素的有序的序列上面,根据最大值和最小值,来寻找定位目标值的方法,每次根据最大值和最小值,计算出中间值,判断目标值落在中间值的左边还是右边,从而缩小搜索范围,最后通过边界条件的等或者不等,进行定位·
class Solution {
public int search(int[] nums, int target) {
Arrays.sort(nums);
int left = 0, right = nums.length-1, mid = 0;
while(left <= right){
mid = left + (right - left) / 2;
if(nums[mid] == target){
return mid;
}else if(nums[mid] < target){
left = mid + 1;
}else{
right = mid - 1;
}
}
return -1;
}
}
左闭右开式写法
class Solution {
public int search(int[] nums, int target) {
Arrays.sort(nums);
int left = 0, right = nums.length, mid = 0;
while(left < right){
mid = left + (right - left) / 2;
if(nums[mid] == target){
return mid;
}else if(nums[mid] < target){
left = mid + 1;
}else{
right = mid;
}
}
return -1;
}
}
二、力扣35. 搜索插入位置
使用二分查找算法定位目标值的位置,当队列为有序的就可以,至于元素唯一不唯一,都能找到合适的位置
class Solution {
public int searchInsert(int[] nums, int target) {
int left = 0, right = nums.length - 1, mid = 0;
while(left <= right){
mid = left + (right - left) / 2;
if(nums[mid] == target){
return mid;
}else if(nums[mid] < target){
left = mid + 1;
}else{
right = mid - 1;
}
}
return right + 1;
}
}
三、力扣34. 在排序数组中查找元素的第一个和最后一个位置
对于在有序队列里面寻找位置,直接识别为二分算法变形,在这里面,左右边界分开寻找,一边找一个,找到第一个非目标元素就算成功 第二种方法就是,一个二分查找直接找目标值,若是找不到,就是默认值边界,要是找到了,直接写两个while循环,向左,向右找边界
class Solution {
public int[] searchRange(int[] nums, int target) {
int[] res = new int[]{-1, -1};
int left = 0, right = nums.length -1, mid = 0;
//寻找左边界
while(left <= right){
mid = left + (right - left) / 2;
if(nums[mid] == target){
if(mid > 0 && nums[mid-1] == target){
right = mid - 1;
}else{
res[0] = mid;
break;
}
}else if(nums[mid] < target){
left = mid + 1;
}else{
right = mid - 1;
}
}
//寻找右边界
left = 0;
right = nums.length -1;
while(left <= right){
mid = left + (right - left) / 2;
if(nums[mid] == target){
if(mid < nums.length-1 && nums[mid+1] == target){
left = mid + 1;
}else{
res[1] = mid;
break;
}
}else if(nums[mid] < target){
left = mid + 1;
}else{
right = mid - 1;
}
}
//返回结果
return res;
}
}
四、力扣69. x 的平方根
寻找一个数的平方根,可以直接使用二分查找,从1到这个数本身就是一个不重复的有序队列,为了防止整数溢出计算的时候,注意一下就行
class Solution {
public int mySqrt(int x) {
int left = 1, right = x, mid = 0;
while(left <= right){
mid = left + (right - left) / 2;
if(mid == x / mid){
return mid;
}else if(mid < x / mid){
left = mid + 1;
}else{
right = mid - 1;
}
}
return right;
}
}
五、力扣367. 有效的完全平方数
对于这种数值的计算,大部分都可以归为二分查找的范畴
class Solution {
public boolean isPerfectSquare(int num) {
int left = 1, right = num, mid = 0;
while(left <= right){
mid = left + (right - left) / 2;
if(mid == num / mid && mid * mid == num){
return true;
}else if(mid < num / mid){
left = mid + 1;
}else{
right = mid - 1;
}
}
return false;
}
}
六、力扣27. 移除元素
对于移除元素来说,应该尽量避免元素的大规模移动,所以应该使用双指针算法,两个指针一个用于重定位赋值,另外一个用于正向遍历扫描,遇到目标值不做处理跳过,非目标值,进行重赋值操作,直到右指针扫描完毕
class Solution {
public int removeElement(int[] nums, int val) {
int count = 0;
for(int i = 0, j = 0; j < nums.length; ){
if(nums[j] == val){
count ++;
j ++;
}else{
nums[i] = nums[j];
i ++;
j ++;
}
}
return count;
}
}
七、力扣26. 删除有序数组中的重复项
删除有序数组中的重复项,推荐使用双指针中的快慢指针法,不需要大规模移动元素,只需要进行重赋值操作
class Solution {
public int removeDuplicates(int[] nums) {
int res = 1;
if(nums.length == 1){
return res;
}
for(int i = 0, j = 1; j < nums.length;){
if(nums[j] == nums[i]){
j ++;
}else{
nums[++i] = nums[j++];
res = i+1;
}
}
return res;
}
}
八、力扣283. 移动零
只对特定元素进行移动,其他保持相对位置不变,使用快慢时针,双指针的特点就是可以进行重赋值操作,这个才是解决问题的特性
class Solution {
public void moveZeroes(int[] nums) {
int i = 0, j = 0;
for( ; j < nums.length; ){
if(nums[j] == 0){
j ++;
}else{
nums[i ++] = nums[j ++];
}
}
while(i < nums.length){
nums[i ++] = 0;
}
}
}
九、力扣844. 比较含退格的字符串
class Solution {
public boolean backspaceCompare(String s, String t) {
StringBuilder sb1 = new StringBuilder();
StringBuilder sb2 = new StringBuilder();
for(char c : s.toCharArray()){
if(c == '#'){
if(sb1.length() > 0){
sb1.deleteCharAt(sb1.length()-1);
}
}else{
sb1.append(c);
}
}
for(char c : t.toCharArray()){
if(c == '#'){
if(sb2.length() > 0){
sb2.deleteCharAt(sb2.length()-1);
}
}else{
sb2.append(c);
}
}
int i = 0, j = 0;
while(i < sb1.length() && j < sb2.length()){
if(sb1.charAt(i) != sb2.charAt(j)){
return false;
}
i ++;
j ++;
}
return sb1.length() == sb2.length();
}
}
十、力扣977. 有序数组的平方
class Solution {
public int[] sortedSquares(int[] nums) {
int[] res = new int[nums.length];
for(int i = 0, j = nums.length-1, k = j; i <= j;){
if(nums[i]*nums[i] >= nums[j]*nums[j]){
res[k --] = nums[i]*nums[i];
i ++;
}else{
res[k --] = nums[j]*nums[j];
j --;
}
}
return res;
}
}
十一、力扣209. 长度最小的子数组
经典的滑动窗口,设置一个全局最小子数组长度,窗口滑动一次,就可以获得结果 滑动的具体过程就是,右指针每次向右移动一下,之后判读窗口内的值是否达到目标值,若是达到,就开始while循环收缩左指针,直到窗口内的值不满足目标值,此时继续右指针的动作
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int res = Integer.MAX_VALUE;
int left = 0, right = 0, sum = 0;
while(right < nums.length){
sum += nums[right];
right ++;
while(left < right && sum >= target){
res = Math.min(res, right - left);
sum -= nums[left ++];
}
}
return res == Integer.MAX_VALUE ? 0 : res;
}
}
十二、力扣904. 水果成篮
class Solution {
public int totalFruit(int[] fruits) {
int res = 0;
Map<Integer,Integer> map = new HashMap<>();
int left = 0, right = 0;
while(right < fruits.length){
map.put(fruits[right],map.getOrDefault(fruits[right],0) + 1);
right ++;
if(map.size() <= 2){
res = Math.max(res,right-left);
}
while(left < right && map.size() > 2){
map.put(fruits[left], map.getOrDefault(fruits[left],0) - 1);
if(map.get(fruits[left]) == 0){
map.remove(fruits[left]);
}
left ++;
}
}
return res;
}
}
十三、力扣76. 最小覆盖子串
滑动窗口,指的的就是左右指针,一次遍历,不走回头路,在中间的窗口中收集需要的结果,大循环每循环一次,窗口就向右扩大一下,扩大后就要检测,窗口是否满足缩小的特征,若是满足就开始从左缩小窗口,在缩小的过程中进行收集
class Solution {
public String minWindow(String s, String t) {
Map<Character,Integer> map = new HashMap<>();
Set<Character> set = new HashSet<>();
String res = s + "-";
for(char c : t.toCharArray()){
set.add(c);
map.put(c, map.getOrDefault(c,0) + 1);
}
int left = 0, right = 0, count = map.size();
char[] sh = s.toCharArray();
while(right < s.length()){
if(set.contains(sh[right])){
map.put(sh[right], map.getOrDefault(sh[right], 0) - 1);
if(map.get(sh[right]) == 0){
count --;
}
}
right ++;
while(left < right && count == 0){
if(set.contains(sh[left])){
if(map.get(sh[left]) == 0){
res = (right - left) < res.length() ? s.substring(left, right) : res;
count ++;
}
map.put(sh[left], map.getOrDefault(sh[left], 0) + 1);
}
left ++;
}
}
return res.equals(s + "-") ? "" : res;
}
}
十四、力扣59. 螺旋矩阵 II
这种类型的题目属于模拟,看清楚题目,处理好边界条件就行
class Solution {
public int[][] generateMatrix(int n) {
int[][] res = new int[n][n];
int left = 0, right = n-1, low = n-1, under = 0;
for(int a = 1; a <= n*n; ){
for(int i = left; i <= right && a <= n*n; i ++){
res[under][i] = a ++;
}
under ++;
for(int i = under; i <= low && a <= n*n; i ++){
res[i][right] = a ++;
}
right --;
for(int i = right; i >= left && a <= n*n; i --){
res[low][i] = a ++;
}
low --;
for(int i = low; i >= under && a <= n*n; i --){
res[i][left] = a ++;
}
left ++;
}
return res;
}
}
十五、力扣54. 螺旋矩阵
经典模拟,先左后右,从上到下,再从右向左,再从下到上
class Solution {
public List<Integer> spiralOrder(int[][] matrix) {
List<Integer> res = new ArrayList<>();
int m = matrix.length, n = matrix[0].length;
int left = 0, right = n-1, up = 0, down = m-1;
for(int a = 1; a <= m*n; ){
for(int i = left; i <= right && a <= m*n; i ++){
res.add(matrix[up][i]);
a ++;
}
up ++;
for(int i = up; i <= down && a <= m*n; i ++){
res.add(matrix[i][right]);
a ++;
}
right --;
for(int i = right; i >= left && a <= m*n; i --){
res.add(matrix[down][i]);
a ++;
}
down --;
for(int i = down; i >= up && a <= m*n; i --){
res.add(matrix[i][left]);
a ++;
}
left ++;
}
return res;
}
}
十六、力扣LCR 146. 螺旋遍历二维数组
class Solution {
public int[] spiralArray(int[][] array) {
if(array.length == 0 || array[0].length == 0){
return new int[0];
}
int m = array.length, n = array[0].length;
int[] res = new int[m*n];
int left = 0, right = n-1, up = 0, down = m-1;
for(int a = 0; a < m*n; ){
for(int i = left; i <= right && a < m*n; i ++){
res[a ++] = array[up][i];
}
up ++;
for(int i = up; i <= down && a < m*n; i ++){
res[a ++] = array[i][right];
}
right --;
for(int i = right; i >= left && a < m*n; i --){
res[a ++] = array[down][i];
}
down --;
for(int i = down; i >= up && a < m*n; i --){
res[a ++] = array[i][left];
}
left ++;
}
return res;
}
}
十七、卡码网 58. 区间和(第九期模拟笔试)
使用前缀和来计算区间和是最好的方式,两个前缀和之间的差,就是他们下标区间左开右闭的区间和
import java.util.*;
public class Main{
public static void main(String[] args){
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int[] sum = new int[n+1];
for(int i = 1; i < sum.length; i ++){
sum[i] = sum[i-1] + scanner.nextInt();
}
int a = 0, b = 0;
while(scanner.hasNextInt()){
a = scanner.nextInt();
b = scanner.nextInt();
System.out.println(sum[b+1] - sum[a]);
}
}
}
十八、卡码网 44. 开发商购买土地(第五期模拟笔试)
import java.util.*;
public class Main{
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int m = scanner.nextInt(), n = scanner.nextInt();
scanner.nextLine();
int sum = 0;
int[][] nums = new int[m][n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
nums[i][j] = scanner.nextInt();
sum += nums[i][j];
}
}
scanner.close();
int res = Integer.MAX_VALUE;
int temp = 0;
for (int i = 0; i < m - 1; i++) {
for (int j = 0; j < n; j++) {
temp += nums[i][j];
}
res = Math.min(res, Math.abs(temp - (sum - temp)));
}
temp = 0;
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < m; j++) {
temp += nums[j][i];
}
res = Math.min(res, Math.abs(temp - (sum - temp)));
}
System.out.println(res);
}
}