刷题班
- 数组
- 1.二分查找
- 704.二分查找
- 35.搜索插入位置
- 34.在排序数组中查找元素的第一个和最后一个位置。
- 69.X的平方和
- 367.有效的完全平方数
- 2.移除元素
- 27.移除元素
- 283.移动零
- 844.比较含退格的字符串
- 977.有序数组的平方
- 3.长度最小的子数组
- 209.长度最小的子数组
- 904.水果成蓝
- 76.最小覆盖子串
- 4.螺旋矩阵
- 59.螺旋矩阵||
- 剑指offer29.顺时针打印矩阵
数组
1.二分查找
704.二分查找
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
思路: 一个有序数组内,如果中间的位置比目标值大,则在左区间查找,反之,则在右区间查找,如果中间值等于目标值,直接返回下标。
代码:
class Solution {
public int search(int[] nums, int target) {
if (target < nums[0] || target > nums[nums.length - 1]) {
return -1;
}
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + ((right - left) >> 1);
if (nums[mid] == target) {
return mid;
} else if (nums[mid] > target) {
right = mid - 1;
} else if (nums[mid] < target) {
left = mid + 1;
}
}
return -1;
}
}
35.搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。
思路:
和二分查找类似,这道题是如果没有找到目标的节点,要返回该目标应该插入的位置,此时我们直接返回左指针即可,因为找到最后,左指针是挨着这个目标最近的数字。
代码:
public int searchInsert(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + ((right - left) >> 1);
if (nums[mid] == target) {
return mid;
} else if (nums[mid] > target) {
right = mid - 1;
} else if (nums[mid] < target) {
left = mid + 1;
}
}
return left;
}
34.在排序数组中查找元素的第一个和最后一个位置。
给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。
思路:
由于是要找到一个范围。所以分两次二分查找。左边界,当mid等于target还要继续往前找。直到left=right,此时找到的左边界就是小于目标值的第一个。
右边界:当mid=target的适合更新left。指针的左节点就是右边界的值。同样,此时的右边界也是大于目标的第一个元素。
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 。
思路: 由于是查找x的平方根。我们可以从0到x进行二分查找的方法。因为返回的是整数,我们就不需要考虑小数的情况,只需要进行二分运算。如果mid^2比目标值大,我们需要缩小。如果mid的平方恰好等于目标值,我们只需要返回这个mid就可以了,即使找不到恰好等于的,我们返回r就是恰好不等于目标值的整数。
class Solution {
public static int mySqrt(int x) {
int l=0;
int r=x;
int mid=1;
while (l<=r) {
mid=l+((r-l)>>1);
if((long)mid*mid>x) {
r=mid-1;
}else if((long)mid*mid<x) {
l=mid+1;
}else if((long)mid*mid==x) {
return mid;
}
}
return r;
}
}
367.有效的完全平方数
给你一个正整数 num 。如果 num 是一个完全平方数,则返回 true ,否则返回 false 。
完全平方数 是一个可以写成某个整数的平方的整数。换句话说,它可以写成某个整数和自身的乘积。
不能使用任何内置的库函数,如 sqrt 。
思路:
用二分查找来找num的根,如果找到了返回true,找不到就返回false。
class Solution {
public boolean isPerfectSquare(int num) {
int l=0;
int r=num;
int mid=-1;
while(l<=r) {
mid=l+((r-l)>>1);
if((long)mid*mid>num) {
r=mid-1;
}else if((long)mid*mid<num) {
l=mid+1;
}else if((long)mid*mid==num) {
return true;
}
}
return false;
}
}
2.移除元素
27.移除元素
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
思路:
用快慢指针法,用两个指针,快指针去过滤后边的元素,慢指针记录已有的元素,当快指针指向的元素不是目标值再往慢指针里边放。
class Solution {
public int removeElement(int[] nums, int val) {
int slowindex=0;
int quick=0;
for( quick=0;quick<nums.length;quick++) {
if(nums[quick]!=val) {
nums[slowindex++]=nums[quick];
}
}
return slowindex;
}
}
283.移动零
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
思路1:
双指针法,快指针指到非零的元素就交给慢指针,然后后边就补0就好了。
思路2:
也是快慢指针法,只要快指针指到非0的元素,那么就将快慢指针的元素交换,这样即使快指针指到最后都是0的元素了
public void moveZeroes(int[] nums) {
int slowindex=0;
int quick=0;
for(quick=0;quick<nums.length;quick++) {
if(nums[quick]!=0) {
nums[slowindex++]=nums[quick];
}
}
for(;slowindex<nums.length;slowindex++) {
nums[slowindex]=0;
}
}
844.比较含退格的字符串
给定 s 和 t 两个字符串,当它们分别被输入到空白的文本编辑器后,如果两者相等,返回 true 。# 代表退格字符。
注意:如果对空文本输入退格字符,文本继续为空。
思路:
这是一个栈结构,当遇到#号键的时候,就让栈pop出来一个,然后再比较两个栈里的元素就可以了。
代码:
public static boolean backspaceCompare(String s, String t) {
Stack a=new Stack();
Stack b=new Stack();
for(int i=0;i<s.length();i++) {
if(s.charAt(i)=='#') {
if(!a.isEmpty()){
a.pop();
}
}else {
a.push(s.charAt(i));
}
}
for(int i=0;i<t.length();i++) {
if(t.charAt(i)=='#') {
if(!b.isEmpty()){
b.pop();
}
}else {
b.push(t.charAt(i));
}
}
return a.equals(b);
}
977.有序数组的平方
给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
思路:
把原数组排好序后,用两个指针,一个指向头部,一个指向尾部,把两端较大的逆序放入新数组中,这样不用考虑指针越界的问题,如果分两个指针的话,还需要考虑数组指到头的问题。
public int[] sortedSquares(int[] nums) {
for(int i=0;i<nums.length;i++) {
nums[i]=nums[i]*nums[i];
}
int n=nums.length;
int []newnums=new int[n];
for(int i=0,j=n-1,pos=n-1;i<=j;) {
if(nums[i]>nums[j]) {
newnums[pos]=nums[i];
i++;
}else {
newnums[pos]=nums[j];
j--;
}
--pos;
}
return newnums;
}
3.长度最小的子数组
209.长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。
示例 1:
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
示例 2:
输入:target = 4, nums = [1,4,4]
输出:1
示例 3:
输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0
思路:
使用滑动窗口,窗口里面是满足目标条件的数组,只需要一个for循环。我们把右指针一直往右移,然后满足目标条件时停止。找到此时满足条件的最小值,即左指针向右移动,这样一遍遍历下来,就可以找到满足条件的最小值了。
public static int minSubArrayLen(int target, int[] nums) {
int right=0;
int result=Integer.MAX_VALUE;
int sum=0;
int left=0;
for(right=0;right<nums.length;right++) {
sum+=nums[right];
while(sum>=target) {
result=Math.min(result, right-left+1);
sum-=nums[left++];
}
}
return result==Integer.MAX_VALUE?0:result;
}
904.水果成蓝
你正在探访一家农场,农场从左到右种植了一排果树。这些树用一个整数数组 fruits 表示,其中 fruits[i] 是第 i 棵树上的水果 种类 。
你想要尽可能多地收集水果。然而,农场的主人设定了一些严格的规矩,你必须按照要求采摘水果:
你只有 两个 篮子,并且每个篮子只能装 单一类型 的水果。每个篮子能够装的水果总量没有限制。
你可以选择任意一棵树开始采摘,你必须从 每棵 树(包括开始采摘的树)上 恰好摘一个水果 。采摘的水果应当符合篮子中的水果类型。每采摘一次,你将会向右移动到下一棵树,并继续采摘。
一旦你走到某棵树前,但水果不符合篮子的水果类型,那么就必须停止采摘。
给你一个整数数组 fruits ,返回你可以收集的水果的 最大 数目。
思路:
滑动窗口法,也是将右指针继续往右移动,然后左边一点一点的移动,然后怎么判断临界条件呢,也就是你的篮子里只有两种类别的水果。这里我们用hashmap来存储,当map里边的种类大于2的时候,我们就要移动左指针,移动一次,背包里对应类别的水果就要减1,直到把它移动完。注意这里左指针要一步一步的移,不能一下跳步,因为中间可能还有其他类别的水果,也不能只删除相同种类的水果,因为题目里要求要一颗一颗的往右摘。
public static int totalFruit(int[] fruits) {
int right=0;
int number=0;
int left=0;
Map<Integer,Integer> pack=new HashMap();
for(right=0;right<fruits.length;right++) {
pack.put(fruits[right], pack.getOrDefault(fruits[right], 0)+1);
while(pack.size()>2) {
pack.put(fruits[left],pack.get(fruits[left])-1);
if(pack.get(fruits[left])==0) {
pack.remove(fruits[left]);
}
left++;
}
number=Math.max(number, right-left+1);
}
return number;
}
76.最小覆盖子串
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。
注意:
对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
如果 s 中存在这样的子串,我们保证它是唯一的答案。
示例 1:
输入:s = “ADOBECODEBANC”, t = “ABC”
输出:“BANC”
解释:最小覆盖子串 “BANC” 包含来自字符串 t 的 ‘A’、‘B’ 和 ‘C’。
示例 2:
输入:s = “a”, t = “a”
输出:“a”
解释:整个字符串 s 是最小覆盖子串。
示例 3:
输入: s = “a”, t = “aa”
输出: “”
解释: t 中两个字符 ‘a’ 均应包含在 s 的子串中,
因此没有符合条件的子字符串,返回空字符串。
思路:
果然是hard题,想了半天啊。
首先用哈希表来统计目标数组每个字符出现的次数。然后用另一个哈希表统计窗口内出现的次数。用distance这个变量来判断窗口内是否能满足全部出现目标字符串的条件。当distance正好等于目标字符串的长度时,这样才是满足条件。然后开始来移动右指针。当右指针指向的字符没有出现在目标字符串里,继续往右走,当窗口内的字符个数小于目标的字符个数时,distance++。同理,当左指针向右移动时,窗口内的字符个数等于目标的字符个数时,distance–。
public String minWindow(String s, String t) {
//两个字符串如果是空的或者s串小于t串,直接返回""
int sLen=s.length();
int tLen=t.length();
if(sLen==0||tLen==0||sLen<tLen) {
return "";
}
//将原字符串转换成字符数组
char []charArrayS=s.toCharArray();
char []charArrayT=t.toCharArray();
//分别记录窗口里和原字符串的字符个数
int []winFreq=new int[128];
int []tFreq=new int[128];
for(char c:charArrayT) {
tFreq[c]++;
}
int distance=0;//滑动窗口里包含了多少个T中的字符串,如果字符频数超过,则不要计算
int minLen=sLen+1;
int begin=0;//筛选出满足条件数组的起始位置
int left=0;
int right=0;
//[left,right) 左开右闭区间
while(right<sLen) {
//当右指针遇见的字符串在tFreq中没有出现,右指针继续往右移动
if(tFreq[charArrayS[right]]==0) {
right++;
continue;
}
//当遇到的这个字符,窗口内的字符个数小于目标字符个数时,distance++
if(winFreq[charArrayS[right]]<tFreq[charArrayS[right]]) {
distance++;
}
winFreq[charArrayS[right]]++;
right++;
//此时滑动窗口内部的字符包含了t中所有的字符
while(distance==tLen) {
if(right-left<minLen) {
minLen=right-left;
begin=left;
}
//当前出现的元素没有在t中出现
if(tFreq[charArrayS[left]]==0) {
left++;
continue;
}
//当遇到的这个字符,窗口内的字符个数=目标字符个数时,distance--
if(winFreq[charArrayS[left]]==tFreq[charArrayS[left]]) {
distance--;
}
winFreq[charArrayS[left]]--;
left++;
}
}
if(minLen==sLen+1) {
return "";
}
return s.substring(begin,begin+minLen);
}
4.螺旋矩阵
59.螺旋矩阵||
给你一个正整数 n ,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。
示例 1:
输入:n = 3
输出:[[1,2,3],[8,9,4],[7,6,5]]
示例 2:
输入:n = 1
输出:[[1]]
思路:
依次从左向右、从右向下、从右向左、从下向上,把每个边依次遍历一遍就行了,注意要用一个变量记录此时的起始位置和终止位置。
最后,如果矩阵是奇数矩阵,就需要把中间那一个位置的元素写上。
public int[][] generateMatrix(int n) {
int startx=0;//x的起始位置
int count=1;
int starty=0;//每次遍历y的起始位置
int i=0,j=0;
int index=0;//循环的次数
int [][]arry=new int[n][n];//初始化数组
while(index++<n/2) {
//从左向右遍历
for( j=startx;j<n-index;j++ ) {
arry[startx][j]=count++;
}
//从右向下遍历
for(i=starty;i<n-index;i++) {
arry[i][j]=count++;
}
//从右向左遍历
for(;j>=index;j--) {
arry[i][j]=count++;
}
//从左向上遍历
for(;i>=index;i--) {
arry[i][j]=count++;
}
startx++;
starty++;
}
//判断n是不是奇数,如果是奇数,则填充上最后一个
if(n%2==1) {
arry[startx][startx]=count;
}
return arry;
}
剑指offer29.顺时针打印矩阵
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
示例 1:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]
示例 2:
输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
输出:[1,2,3,4,8,12,11,10,9,5,6,7]
**思路:**这个与正方形矩阵相比,要多考虑多打印一行和一列的情况,这道题我们用模拟的办法。不用考虑它需要循环几次,我们要设置上下左右四个边界值,如果边界值不符合规则了直接跳出即可。
每次填满一边,都要更新对应的值。
public static int[] spiralOrder(int[][] matrix) {
if (matrix.length==0) {
return new int[] {};
}
int l = 0, r = matrix[0].length - 1, t = 0, b = matrix.length - 1, x = 0;
int[] res = new int[(r + 1) * (b + 1)];
while(true) {
for(int i = l; i <= r; i++) res[x++] = matrix[t][i]; // left to right.
if(++t > b) break;
for(int i = t; i <= b; i++) res[x++] = matrix[i][r]; // top to bottom.
if(l > --r) break;
for(int i = r; i >= l; i--) res[x++] = matrix[b][i]; // right to left.
if(t > --b) break;
for(int i = b; i >= t; i--) res[x++] = matrix[i][l]; // bottom to top.
if(++l > r) break;
}
return res;
}