二分查找
704 二分查找 拓展
- 35.搜索插入位置
- 34.在排序数组中查找元素的第一个和最后一个位置
- 69.x 的平方根
- 367.有效的完全平方数
以上是二分查找的拓展
35 搜索插入位置
和平常的二分查找一样 - 注意定义区间
难点:返回时是 right + 1 - 列举几个情况,寻找结果与mid left right 的关系规律
public int searchInsert(int[] nums, int target) {
int left = 0;
// 原则左闭右闭
int right = nums.length - 1;
int mid = 0;
while (left <= right){
mid = (left + right) / 2;
if (nums[mid] == target) return mid;
else if (nums[mid] > target) {
right = mid - 1;
} else if (nums[mid] < target) {
left = mid + 1;
}
}
return right + 1;
}
34 在排序数组中查找元素的第一个和最后一个位置
和平常的二分查找一样
查找完之后滑动指针寻找左右边界
public int[] searchRange(int[] nums, int target) {
// 二分查找
int index = midSearch(nums, target);
if (index == -1) return new int[]{-1,-1};
else {
// 滑动左右指针
int left = index;
int right = index;
while (left - 1 >= 0 && nums[left - 1] == target)
left--;
while (right + 1 < nums.length && nums[right + 1] == target)
right++;
return new int[]{left,right};
}
}
private int midSearch (int[] nums, int target){
int left = 0;
int right = nums.length - 1;
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;
}
return -1;
}
69 x 的平方根
平常的二分查找
在查找到** s <=x 时,res = mid**
- 小于时:res需要更新一个更大的数(同理 大于时就不用赋值)
- 等于时:mid就是需要返回的值
另外需要注意用于判断平方和需要是long类型 (在num大的情况下,mid的平方可能会超出int范围)
public int mySqrt(int x) {
if (x == 1 || x == 0)
return x;
int start = 0;
int end = x / 2;
int res = -1;
while (end >= start){
int mid = (start + end) / 2;
long s = (long)mid * mid;
if (s > x)
end = mid - 1;
else if (s < x) {
// 当平方和小于时,结果为mid
res = mid;
start = mid + 1;
}
else return mid;
}
return res;
}
367 有效的完全平方数
平常的二分查找
和上题一样
需要注意用于判断平方和需要是long类型 (在num大的情况下,mid的平方可能会超出int范围)
public boolean isPerfectSquare(int num) {
// 特殊情况 - 当num为1时,完全平方数就是自身
if (num == 1) return true;
int start = 0;
int end = num / 2;
while (end >= start){
int mid = (start + end) / 2;
// 注意转换成long类型 - 在num大的情况下,mid的平方可能会超出int范围
long s = (long)mid * mid;
if (s > num) end = mid - 1;
else if (s < num) start = mid + 1;
else return true;
}
return false;
}
双指针
27 移除元素 拓展
26 删除排序数组中的重复项
正常的双指针
public int removeDuplicates(int[] nums) {
// 判空
if (nums.length == 0) return 0;
int low = 0;
for (int fast = 0; fast < nums.length; fast++){
if (nums[fast] != nums[low]){
nums[low + 1] = nums[fast];
low++;
}
}
return low + 1;
}
283 移动零
正常双指针
public void moveZeroes(int[] nums) {
int low = 0;
for (int fast = 0; fast < nums.length; fast++){
if (nums[fast] != 0){
int temp = nums[low];
nums[low] = nums[fast];
nums[fast] = temp;
low++;
}
}
}
844 比较含退格的字符串
不太正常的双指针(个人感觉不止easy)
根据题目特性,两个指针指向两个数组比较情况,需要考虑情况:
- 把s t字符串中的#删除
- 结束判断 - 是否遍历完成(一定是删完#后在判断)
- 异常情况:匹配元素不相等 - false
- 相等则继续匹配 - p1-- p2–
public boolean backspaceCompare(String s, String t) {
// 方法:从后往前双指针
int p1 = s.length() - 1;
int p2 = t.length() - 1;
int sSkip = 0;
int tSkip = 0;
while (true){
// 去掉s字符串的#
while (p1 >= 0){
if (s.charAt(p1) == '#'){
sSkip++;
}
else {
// 元素不为# - 判断是否有skip记录
// 如果有则skip-- 并且往前一格
if (sSkip > 0) sSkip--;
// 如果没有记录说明既不是要删元素也不是# 则跳出循环 不用后移指针
else break;
}
p1--;
}
// 去掉t字符串中的#
while (p2 >= 0){
if (t.charAt(p2) == '#'){
tSkip++;
}
else {
// 元素不为# - 判断是否有skip记录
// 如果有则skip-- 并且往前一格
if (tSkip > 0) tSkip--;
// 如果没有记录说明既不是要删元素也不是# 则跳出循环 不用后移指针
else break;
}
p2--;
}
// 结束条件 - 遍历完了
if (p1 < 0 || p2 < 0) break;
// 异常情况:元素不相等 - false
if (s.charAt(p1) != t.charAt(p2)) return false;
// 相等则继续-- 匹配前面
p1--;p2--;
}
// p1 p2 是否同时遍历完
return p1 == -1 && p2 == -1;
}
977 有序数组的平方
不是平常的双指针
根据题目特性:两边的元素较大,两个指针从头尾开始遍历
public int[] sortedSquares(int[] nums) {
// 双指针
int k = nums.length - 1;
int[] result = new int[nums.length];
// 注意i和j范围,由于i == j时还要判断并且写入新数组,因此为<=
for (int i = 0, j = nums.length - 1; i <= j; ) {
if ((nums[i] * nums[i]) > (nums[j] * nums[j])) {
// 给新数组赋予最大值
result[k] = nums[i] * nums[i];
i++;
k--;
} else {
// 给新数组赋予最大值
result[k] = nums[j] * nums[j];
k--;
j--;
}
}
return result;
}
滑动窗口
209 长度最小的子数组 拓展题目:
滑动窗口两个阶段:
- 右指针右移 - 扩大区域 满足条件
- 左指针右移 - 缩小区域 取得最优
此外,要明确右指针停止右移,左指针开始右移标志,用于左指针开始右移的条件
记录滑动窗口最大 / 最小值:
最大值:在右指针中更新 - 原因:右指针不断向右为扩大窗口
最小值:在左指针中更新 - 原因:左指针不断向右为优化窗口
209 长度最小的子数组
右指针停止右移,左指针开始右移标志:
sum >= target
求最小值,min在左指针每次向右移后更新
public int minSubArrayLen(int target, int[] nums) {
int l = 0;
int r = 0;
int min = Integer.MAX_VALUE;
int sum = 0;
/*for (r = 0; r < nums.length; r++) {
// 指针内区域相加 - 获得区域
sum = sum + nums[r];
// 优化区域
while (sum >= target) {
// 记录当前值
min = Math.min(min, r - l + 1);
sum = sum - nums[l];
l++;
}
}*/
// 右指针右移
while (r < nums.length){
sum += nums[r];
// 左指针右移
while (sum >= target){
min = Math.min(min,r - l + 1);
sum = sum - nums[l];
l++;
}
r++;
}
return min == Integer.MAX_VALUE? 0 : min;
}
904 水果成篮
右指针停止右移,左指针开始右移标志:
hm.size() > 2
求最大值,max在每次右指针向右移后更新
public int totalFruit(int[] fruits) {
// 值 出现次数
HashMap<Integer, Integer> hm = new HashMap<>();
int l = 0;
int max = 0;
for (int r = 0; r < fruits.length;r++){
hm.put(fruits[r], hm.getOrDefault(fruits[r], 0) + 1);
// 更新左指针
while (hm.size() > 2){
hm.put(fruits[l], hm.get(fruits[l]) - 1);
if (hm.get(fruits[l]) == 0)
hm.remove(fruits[l]);
l++;
}
max = Math.max(max, r - l + 1);
}
return max;
}
76 最小覆盖子串
本题是hard题,代码很复杂。但最基本思路还是右指针右移,左指针右移,记录最小值。
会引入一个distance变量,加以判断左指针开始右移。
求最小值,min在左指针每次向右移后更新
public String minWindow(String s, String t) {
int sLen = s.length();
int tLen = t.length();
// z的ASCII码为122
int[] tChar = new int[123];
int[] sChar = new int[123];
// 初始化t数组
for (char c : t.toCharArray()) {
tChar[c]++;
}
char[] sCharArray = s.toCharArray();
// 初始化变量
int r = 0;
int l = 0;
int distance = 0;
int min = s.length() + 1;
int begin = 0;
// 原则:左闭右开 [l, r)
while (r < sLen){
// 当前右指针所指向的元素
char c = sCharArray[r];
if (tChar[c] == 0) {
r++;
continue;
}
if (sChar[c] < tChar[c])
distance++;
sChar[c]++;
r++;
// 左指针右移
while (distance == tLen){
if (min > r - l){
min = r - l;
begin = l;
}
// 左指针在s中所指的元素
char c1 = sCharArray[l];
if (tChar[c1] == 0) {
l++;
continue;
}
//if (sChar[c1] > tChar[c1]) sChar[c1]--;
if (sChar[c1] == tChar[c1]) distance--;
sChar[c1]--;
l++;
}
}
return min == s.length() + 1 ? "" : s.substring(begin, begin + min);
}
边界处理
59 螺旋矩阵Ⅱ
主要考察对边界处理,左闭右闭 or 左闭右开,并且一直遵从这个原则
绕弯一圈后思考如何将其普遍化(如何加上while循环)
public int[][] generateMatrix(int n)
int offset = 0;
int num = 1;
int loop = 0;//循环次数
int[][] arr = new int[n][n]
while (loop++ < n / 2) {
// →
for (int i = offset; i < n - 1 - offset; i++) {
arr[offset][i] = num++;
}
// ↓
for (int j = offset; j < n - 1 - offset; j++) {
arr[j][n - 1 - offset] = num++;
}
// ←
for (int i = n - 1 - offset; i > offset; i--) {
arr[n - 1 - offset][i] = num++;
}
// ↑
for(int j = n - 1 - offset; j > offset; j--){
arr[j][offset] = num++;
// 改变参数
offset++;
if (n % 2 == 1) {
arr[offset][offset] = num;
return arr;
}
54 螺旋矩阵
与59的难点:不再是方方正正的方形矩阵,变成矩形。
就要考虑最后一转是行 列 还是刚好遍历完
public List<Integer> spiralOrder(int[][] matrix) {
List<Integer> list = new ArrayList<>();
int min = Math.min(matrix.length, matrix[0].length);
// 向上取整
int num = min / 2;
int offset = 0;
int n = matrix.length;
int m = matrix[0].length;
while (num-- > 0) {
// →
for (int i = offset; i < m - 1 - offset; i++) {
list.add(matrix[offset][i]);
}
// ↓
for (int j = offset; j < n - 1 - offset; j++) {
list.add(matrix[j][m - 1 - offset]);
}
// ←
for (int i = m - 1 - offset; i > offset; i--) {
list.add(matrix[n - 1 - offset][i]);
}
// ↑
for (int j = n - 1 - offset; j > offset; j--) {
list.add(matrix[j][offset]);
}
// 改变参数
offset++;
}
if (min % 2 == 1){
if (min == n){// 剩一行
for (int i = offset;i <= m - 1 - offset; i++){
list.add(matrix[offset][i]);
}
}
else {// 剩一列
for (int j = offset;j <= n - 1 - offset;j++){
list.add(matrix[j][m - 1 - offset]);
}
}
}
return list;
}
剑指offer 29 顺时针打印矩阵
与54 螺旋矩阵思路一样,返回的数据结构不一样。上一题是List,本题是int[]数组,稍微改一下就好~
public int[] spiralOrder(int[][] matrix) {
// 判空
if (matrix == null || matrix.length == 0)
return new int[0];
// 初始化
int offset = 0;
int n = matrix.length;
int m = matrix[0].length;
int min = Math.min(m, n);
int num = min / 2;
int[] list = new int[m * n];
int count = 0;
while (num-- > 0) {
// →
for (int i = offset; i < m - 1 - offset; i++) {
list[count++] = matrix[offset][i];
}
// ↓
for (int j = offset; j < n - 1 - offset; j++) {
list[count++] = matrix[j][m - 1 - offset];
}
// ←
for (int i = m - 1 - offset; i > offset; i--) {
list[count++] = matrix[n - 1 - offset][i];
}
// ↑
for (int j = n - 1 - offset; j > offset; j--) {
list[count++] = matrix[j][offset];
}
// 改变参数
offset++;
}
if (min % 2 == 1){
if (min == n){// 剩一行
for (int i = offset;i <= m - 1 - offset; i++){
list[count++] = matrix[offset][i];
}
}
else {// 剩一列
for (int j = offset;j <= n - 1 - offset;j++){
list[count++] = matrix[j][m - 1 - offset];
}
}
}
return list;
}