剑指offer刷题宝典--第2节
三、队列 & 栈
JZ31 栈的压入、弹出序列
考虑借用一个辅助栈 stack,模拟 压入 / 弹出操作的排列。根据是否模拟成功,即可得到结果。
入栈操作: 按照压栈序列的顺序执行。
出栈操作: 每次入栈后,循环判断 “栈顶元素 == 弹出序列的当前元素” 是否成立,将符合弹出序列顺序的栈顶元素全部弹出。
import java.util.*;
public class Solution {
public boolean IsPopOrder(int [] pushA, int [] popA) {
Stack<Integer> stack = new Stack<>();
int i = 0;
for (int num : pushA) {
stack.push(num);
while (!stack.isEmpty() && popA[i] == stack.peek()) {
stack.pop();
i++;
}
}
return stack.isEmpty();
}
}
JZ59 滑动窗口的最大值 【难!!】
思路:双端队列
队列中的元素按照从大到小排序,如果新增加的元素值>队尾元素,则依次删除,直到保证队列元素的从大到小顺序。
如果队列中元素个数超过size,则从队首删除;
如果元素个数达到size,则将队列中队首元素作为最大值加到res中
import java.util.*;
public class Solution {
public ArrayList<Integer> maxInWindows(int [] num, int size) {
ArrayList<Integer> res = new ArrayList<>();
Deque<Integer> deque = new LinkedList<>();
for (int i = 0; i < num.length; i++) {
while (!deque.isEmpty() && num[deque.peekLast()] < num[i]) {
deque.pollLast();
}
deque.addLast(i);
// 计算窗口左侧边界
// 当队首元素的下标小于滑动窗口左侧边界left时
// 表示队首元素已经不再滑动窗口内,因此将其从队首移除
if (i - deque.peekFirst() >= size)
deque.pollFirst();
if (i + 1 >= size)
res.add(num[deque.peekFirst()]);
}
return res;
}
}
面试题59 - II. 队列的最大值
Deque存储最大值,Queue存储数据
class MaxQueue {
Queue<Integer> queue;
Deque<Integer> deque;
public MaxQueue() {
queue = new LinkedList<>();
deque = new LinkedList<>();
}
public int max_value() {
return deque.isEmpty() ? -1 : deque.peekFirst();
}
public void push_back(int value) {
queue.offer(value);
while (!deque.isEmpty() && deque.peekLast() < value) {
deque.pollLast();
}
deque.offerLast(value);
}
public int pop_front() {
if (queue.isEmpty()) return -1;
if (queue.peek().equals(deque.peekFirst())) {
deque.pollFirst();
}
return queue.poll();
}
}
四、搜索算法
JZ53 数字在升序数组中出现的次数
要求:空间复杂度 O(1),时间复杂度 O(logn)
解题思路:
排序数组中的搜索问题,首先想到 二分法 解决。
本题要求统计数字 target的出现次数,可转化为:使用二分法分别找到 左边界 left 和 右边界 rightright ,易得数字 target 的数量为 right - left - 1
//二分查找
public class Solution {
public int GetNumberOfK(int [] array, int k) {
int low = 0, high = array.length-1;
//查找右边界
while (low <= high) {
int mid = low + (high - low) / 2;
if (array[mid] <= k)
low = mid + 1;
else
high = mid - 1;
}
int right = low; //right=4位置!!
//查找左边界
low=0;high=array.length-1;
while (low <= high) {
int mid = low + (high - low) / 2;
if (array[mid] < k)
low = mid + 1;
else
high = mid - 1;
}
int left = high; // left=2位置
return right-left-1;
}
}
JZ4 二维数组中的查找
题解三: 线性搜索
复杂度分析:
时间复杂度:O(M+N)
空间复杂度:O(1)
public class Solution {
public boolean Find(int target, int [][] array) {
int rows = array.length, cols = array[0].length;
int i = 0, j = cols-1;
while (i < rows && j >= 0) {
if (target < array[i][j]) {
j--;
} else if (target > array[i][j]) {
i++;
} else {
return true;
}
}
return false;
}
}
JZ11 旋转数组的最小数字
主要思路:
1.数组可以分为两个有序的子数组。其中,左排序的数组的值大于右排序数组中的值。
2.声明left,right 分别指向数组的左右两端;
3.mid = (left+right) / 2 为二分的中间位置。
4.mid,left, right分为三种情况:
a. nums[mid] > nums[right]时, 那么 最小值一定在 [mid+1,right]区间中;
b.nums[mid] < nums[right]时,那么最小值一定在[left,mid]区间内。
c. nums[mid] = nums[right]时,无法判断最小值在哪个区间,所以此时只能缩小right的值。
class Solution {
public int minArray(int[] nums) {
int n = nums.length;
int left = 0;
int right = n - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] > nums[right]) {
left = mid + 1;
} else if (nums[mid] < nums[right]) {
//注意下标!!
right = mid ;
} else {
right--;
}
}
return nums[left];
}
}
JZ44 数字序列中某一位的数字 【× 难】
时间复杂度O(logn) :
空间复杂度 O(logn) :
import java.util.*;
public class Solution {
public int findNthDigit (int n) {
int digit = 1; //位数
long start = 1; //起始范围
long count = 9; //每个范围中的数字位数
//1. 确定所求数字的起始范围和位数
while (n - count > 0) {
n -= count;
digit += 1;
start *= 10;
count = 9 * start * digit;
}
//2. 确定所求数位所在的数字
long num = start + (n - 1) / digit;
//3. 确定所求数位在 num的哪一数位
int inx = (n - 1) % digit;
return String.valueOf(num).charAt(inx) - '0';
}
}
剑指 Offer 57. 和为s的两个数字
注意有序数组的双指针处理方式,体悟!!
class Solution {
public int[] twoSum(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left < right) {
int sum = nums[left] + nums[right];
if (sum == target) {
return new int[]{nums[left], nums[right]};
} else if (sum < target) {
left++;
} else {
right--;
}
}
return new int[0];
}
}
剑指 Offer 57 - II. 和为s的连续正数序列
滑动窗口:
class Solution {
public int[][] findContinuousSequence(int target) {
//左右和总和 i是左边界值 j是右边界值 s是和
int i = 1, j = 2, s = 3;
//结果集
List<int[]> res = new ArrayList<>();
//窗口左右
while(i < j) {
//如果和为目标值 添加到结果集
if(s == target) {
//数组长度为右边界-左边界+1
int[] ans = new int[j - i + 1];
//从左边界循环到右边界 循环添加 左值到右值
for(int k = i; k <= j; k++)
ans[k - i] = k;
res.add(ans);
}
//如果窗口和大于等于目标值说明 窗口溢出或者窗口到达
//缩减 去掉最左边的值
if(s >= target) {
s -= i;
i++;
} else {
//如果小于目标值说明窗口未到达 扩张添加右值
j++;
s += j;
}
}
return res.toArray(new int[0][]);
整理不易,关注和收藏后拿走!
欢迎专注我的公众号:AdaCoding 和 Github:AdaCoding123