数组、栈、队列、hash表、集合
11.1 数组
题目代号: 448 数组中小事的数字
题目描述:
给定一个长度为 n 的数组,其中包含范围为 1 到 n 的整数,有些整数重复了多次,有些整数没有出现,求 1 到 n 中没有出现过的整数。
测试用例:
Input: [4,3,2,7,8,2,3,1]
Output: [5,6]
我的分析:
我们遍历一遍数组nums,这样谁出现了,新数组中对应下标的位置+1;
这样的话没有出现大的位置就一直是0
第二次遍历,看位置是0的位置,就代表数字没出现
代码:
public List<Integer> findDisappearedNumbers(int[] nums) {
List<Integer> list = new ArrayList<>();
int[] copy = new int[nums.length+1];
for(int num : nums) {copy[num]++;}//就把在nums里面出现的数字,在copy中对应位置++
//所以最后不是0的就出现过,是0的就未出现过
for(int i = 1;i < copy.length;i++){
if(copy[i] == 0){
list.add(i);
}
}
return list;
}
题目代号: 240 二维数组中是否存在某个数字
题目描述:
给定一个二维矩阵,已知每行和每列都是增序的,尝试设计一个快速搜索一个数字是否在矩阵中存在的算法
测试用例:
Input: matrix =
[ [1, 4, 7, 11, 15],
[2, 5, 8, 12, 19],
[3, 6, 9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]], target = 5
Output: true
我的分析:
我们对每一行来进行二分查找,遍历某一行的时候,就看左右中三个位置
代码:
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
//对每一行进行二分查找
for(int i = 0;i < matrix.length;i++){
int jieguo = binarySearch(matrix[i],target);
if(jieguo != -1) {
return true;
}
}
return false;
}
public int binarySearch(int[] nums,int target){
int left = 0,right = nums.length-1;
while (left <= right){
int mid = (left + right)/2;
if(nums[mid] == target){
return mid;
}else if(nums[mid] > target){
right = mid-1;
}else {
left = mid+1;
}
}
return -1;
}
}
11.2 栈和队列
题目代号: 232 栈和队列
题目描述:
使用栈(stack)来实现队列(queue)
测试用例:
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):
实现 MyQueue 类:
void push(int x) 将元素 x 推到队列的末尾
int pop() 从队列的开头移除并返回元素
int peek() 返回队列开头的元素
boolean empty() 如果队列为空,返回 true ;否则,返回 false
我的分析:
当咱们压入栈的时候,只要栈是空的,那很简单就直接压入
当栈不是空的
1、那就把栈里面的元素放入替换栈里
2、元素入栈
3、替换栈再转移回来
这样有个好处是,每次压入的元素,都在栈顶,这样出栈,拿栈中元素,判断是否空就都很好操作了
代码:
class MyQueue {
/** Initialize your data structure here. */
private Deque<Integer> queue;
private Deque<Integer> queue_tihuan;
public MyQueue() {
queue = new LinkedList<>();
queue_tihuan = new LinkedList<>();
}
/** Push element x to the back of queue. */
public void push(int x) {
//当进来元素的时候,就弹出queue里的元素放进queue_tihuan里,把传进来的数据加进queue里,再把queue_tihuan的元素弹出到queue
if(queue.isEmpty()){
queue.push(x);
}else {
while (!queue.isEmpty()){
queue_tihuan.push(queue.pop());//需要全部pop出来
}
queue.push(x);
while (!queue_tihuan.isEmpty()){
queue.push(queue_tihuan.pop());
}
}
}
/** Removes the element from in front of queue and returns that element. */
public int pop() {
return queue.pop();
}
/** Get the front element. */
public int peek() {
return queue.peek();
}
/** Returns whether the queue is empty. */
public boolean empty() {
return queue.isEmpty();
}
}
题目代号: 155 最小栈
题目描述:
设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
push(x) —— 将元素 x 推入栈中。
pop() —— 删除栈顶的元素。
top() —— 获取栈顶元素。
getMin() —— 检索栈中的最小元素。
测试用例:
输入:
[“MinStack”,“push”,“push”,“push”,“getMin”,“pop”,“top”,“getMin”]
[[],[-2],[0],[-3],[],[],[],[]]
输出:
[null,null,null,null,-3,null,0,-2]
解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.getMin(); --> 返回 -2.
我的分析:
代码:
class MinStack {
/*
本题思路:搞两个栈,一个栈就一直执行操作就好,另一个栈只存储一直的最小值即可,而且把最小值一直放在栈顶
*/
/** initialize your data structure here. */
private Stack<Integer> stack;
private Stack<Integer> min_stack;
public MinStack() {
stack = new Stack<>();//就只是为了建立两个栈而已
min_stack = new Stack<>();
}
public void push(int x) {
//peek查看栈顶元素而不移除它
//每当进来一个元素,就直接进入stack即可
//但要与min_stack栈顶的元素进行比较一下,如果比栈顶元素小,就要入min_stack
stack.push(x);
if(min_stack.isEmpty() || x <= min_stack.peek()){
min_stack.push(x);//其实思路还是很明显的,其他步骤就直接操作即可吗,在入栈和出栈的时候,请考虑下min_stack的存在
}
}
public void pop() {
//判断被pop()出去的元素是否是min_stack 的栈顶元素
//若是的话,那么min_stack的栈顶元素也要被弹出,这样就能保证min_stack的栈顶一直是最小值了
int value = stack.pop();
if(value == min_stack.peek()){
min_stack.pop();
}
}
public int top() {
return stack.peek();
}
public int getMin() {
return min_stack.peek();
}
}
/**
* Your MinStack object will be instantiated and called as such:
* MinStack obj = new MinStack();
* obj.push(x);
* obj.pop();
* int param_3 = obj.top();
* int param_4 = obj.getMin();
*/
题目代号: 20有效的括号
题目描述:
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
测试用例:
Input: “{[]}()”
Output: true
我的分析:
咱们就一个一个来遍历嘛,如果是左半部分,那就直接压入栈
如果是右半部分,那就看栈顶的元素是否跟左半部分是一对,如果是一对,那就继续看下一个元素,把这个栈顶元素弹出来,如果不是一对,那就直接返回false即可
代码:
public boolean isValid(String s) {
Stack<Character> stack = new Stack<>();
for(char yuansu : s.toCharArray()){//字符串转化成字符数组
if(yuansu == '{' || yuansu == '[' || yuansu == '('){
stack.push(yuansu);//既然是左半部分,那就直接压入就好了
}else {//那就代表肯定不是左半部分了,进来的是右半部分了
if(stack.isEmpty()){
return false;//右半部分进来时候不能面对空
}
//既然有元素,那就先拿出来看看
char ppp = stack.peek();
if((yuansu == '}' && ppp == '{') || (yuansu == ')' && ppp == '(') || (yuansu == ']' && ppp == '[') ){
stack.pop();//既然是一对,就弹出来
}else {
return false;//不是一对就报错
}
}
}
return stack.isEmpty();//最后只要栈是空,说明一直是一对一对的
}
11.3 单调栈
题目代号: 739 每日温度
题目描述:
给定每天的温度,求对于每一天需要等几天才可以等到更暖和的一天。如果该天之后不存在更暖和的天气,则记为 0。
测试用例:
Input: [73, 74, 75, 71, 69, 72, 76, 73]
Output: [1, 1, 4, 2, 1, 1, 0, 0]
我的分析:
我们在一个栈中去放数组下标
1、栈是空,那就直接放入
2、目前拿到的温度小于等于栈顶对应的温度,也直接压入
3、目前拿到的温度大于栈顶对应的温度,那就弹出栈顶,来计算下天数,放入结果数组
代码:
public int[] dailyTemperatures(int[] temperatures) {
int n = temperatures.length;
int[] ans = new int[n];//最后结果
Stack<Integer> stack = new Stack<>();//压入栈的是数组的下标
for (int i = 0;i < n;i++){
while (!stack.isEmpty()){//既然现在栈不是空了,那就看看如何入栈了
int uuu = stack.peek();//之前的一天
if(temperatures[uuu] < temperatures[i]){
ans[uuu] = i - uuu;
stack.pop();
}else {//现在不符合了
break;//就代表小的不往里面放
}
}
stack.push(i);//栈是空的时候,就直接压入就好了
//这个操作代表不管哪种情况,都会压入栈的
}
return ans;
}
11.4 优先队列
题目代号: 23 合并K个升序链表
题目描述:
给定 k 个增序的链表,试将它们合并成一条增序链表
测试用例:
输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
1->4->5,
1->3->4,
2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6
我的分析:
我们需要一个优先队列,这个队列里面就存着这些链表,但它特殊的是,会一直维持链表头节点是最小值的排在第一个(对链表头节点进行排序)
创建一个结果链表呗,那就依次来看喽,只要队列里面有值,那就把队列第一个链表的头节点弹出来,让结果指针指向它;结果指针向后移动,这个链表继续加入优先队列
代码:
public ListNode mergeKLists(ListNode[] lists) {
//把链表放进队列中,要变为最小堆,递减,这样每次选出最小值
Queue<ListNode> queue = new PriorityQueue<>(new Comparator<ListNode>() {
@Override
public int compare(ListNode o1, ListNode o2) {
return o1.val-o2.val;
}
});
//把数组中的每一个链表都放进队列中,这个队列好像有一个好处,就是能排序的优先队列
for (ListNode list : lists) {
if (list != null){
queue.offer(list);
}
}
ListNode dummyHead = new ListNode(0);//为了存放结果的一个链表
ListNode tail = dummyHead;//拿这个指针来指向头节点
while(!queue.isEmpty()){
ListNode minNode = queue.poll();//把队列的第一个弹出来,就是当前最小值
tail.next=minNode;//用结果指针指向当前最小值
tail = tail.next;//结果链表指针肯定要向后移动了
if (minNode.next!=null){//现在minNode.next指向的是第一条链表的第二个值,
//只要这条链表上还有元素,就把这个链表加入到优先队列中,让这些链表进行重新排序
queue.offer(minNode.next);
}
}
return dummyHead.next;
}
11.5 双端队列
题目代号: 滑动窗口最大值
题目描述:
给定一个整数数组和一个滑动窗口大小,求在这个窗口的滑动过程中,每个时刻其包含的最大值。
测试用例:
Input: nums = [1,3,-1,-3,5,3,6,7], k = 3
Output: [3,3,5,5,6,7]
我的分析:
输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7]
解释过程中队列中都是具体的值,方便理解,具体见代码。
初始状态:L=R=0,队列:{}
i=0,nums[0]=1。队列为空,直接加入。队列:{1}
i=1,nums[1]=3。队尾值为1,3>1,弹出队尾值,加入3。队列:{3}
i=2,nums[2]=-1。队尾值为3,-1<3,直接加入。队列:{3,-1}。此时窗口已经形成,L=0,R=2,result=[3]
i=3,nums[3]=-3。队尾值为-1,-3<-1,直接加入。队列:{3,-1,-3}。队首3对应的下标为1,L=1,R=3,有效。result=[3,3]
i=4,nums[4]=5。队尾值为-3,5>-3,依次弹出后加入。队列:{5}。此时L=2,R=4,有效。result=[3,3,5]
i=5,nums[5]=3。队尾值为5,3<5,直接加入。队列:{5,3}。此时L=3,R=5,有效。result=[3,3,5,5]
i=6,nums[6]=6。队尾值为3,6>3,依次弹出后加入。队列:{6}。此时L=4,R=6,有效。result=[3,3,5,5,6]
i=7,nums[7]=7。队尾值为6,7>6,弹出队尾值后加入。队列:{7}。此时L=5,R=7,有效。result=[3,3,5,5,6,7]
这个队列一定是单调递减的
每当向右移动时,把窗口左端的值从队列左端剔除,把队列右边小于窗口
右端的值全部剔除。这样双端队列的最左端永远是当前窗口内的最大值。
代码:
public int[] maxSlidingWindow(int[] nums, int k) {
if(nums.length == 0 && k == 0){
return new int[0];
}
int[] res = new int[nums.length-k+1];//结果数组就这么大
int index = 0;//是res的索引
Deque<Integer> qp = new LinkedList<>();//存放的是单调队列
for(int i = 0; i < nums.length;i++){
if(qp.size() > 0 && i - qp.peekFirst() >= k){//队列里面的元素不能超过滑动窗口的长度(不可能出现3个)
qp.pollFirst();
}
while (qp.size() > 0 && nums[i] > nums[qp.peekLast()]){
qp.pollLast();
}
qp.add(i);
if(i >= k-1){
res[index++] = nums[qp.peekFirst()];//这里是peek只是拿出来看看,也就意味着i只要大于2时候,每一位都要拿第一个来看
}
}
return res;
}
11.7 哈希表
题目代号: 128 最长连续序列
题目描述:
给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
测试用例:
输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。
我的分析:
把数组中的元素放进集合里面,然后拿出来集合中的元素一个一个来看,每次咱们一定要找到一段子序列的最小值
这样就可以开始while循环了,把这段子序列完全找到,记录一次子序列,
再从for循环开始下一段子序列。。。。
代码:
public int longestConsecutive(int[] nums) {
Set<Integer> num_set = new HashSet<Integer>();
for (int num : nums) {
num_set.add(num);//先把数组里面的元素放进集合里面
}
//这个题不能我想的那么简单,直接排序就可以了,原因有二:1、时间复杂度会超O(n)2、比如0 1 2 3 5 6 7 8 9这种好几个最长的子序列呢
int longestStreak = 0;//看最长的子序列到底多长
for (int num : num_set) {//现在从集合中拿出数来看
if (!num_set.contains(num - 1)) {//必须要挑出那个第一个值,前面已经没值了,这样才开始统计
int currentNum = num;
int currentStreak = 1;
while (num_set.contains(currentNum + 1)) {//一旦找到最小值后就要用while来寻找连续的值,并且记录下来
currentNum += 1;
currentStreak += 1;
}//一出while说明统计了一种情况了,有几个连续子序列就统计几次
longestStreak = Math.max(longestStreak, currentStreak);
}
}
return longestStreak;
}
题目代号: 149 直线上最多的点数
题目描述:
给定一些二维坐标中的点,求同一条线上最多由多少点
测试用例:
我的分析:
[[1,1],[3,2],[5,3],[4,1],[2,3],[1,4]]
i=0,x=[1,1] j=1,y=[3,2] k=2,p=[5,3][4,1][2,3][1,4]依次都要遍历
i=0,x=[1,1] j=2,y=[5,3] k=3,p=[4,1][2,3][1,4]依次都要遍历
i=0,x=[1,1] j=3,y=[4,1] k=4,p=[2,3][1,4]依次都要遍历
i=1,x=[3,2] j=2,y=[5,3] k=3,p=[4,1][2,3][1,4]依次都要遍历
i=1,x=[3,2] j=3,y=[4,1] k=4,p=[2,3][1,4]依次都要遍历
i=1,x=[3,2] j=4,y=[2,3] k=5,p=[1,4]依次都要遍历
为了避开0,以及乘积需要约分的情况,其实是(y[1] - x[1])/(y[0] - x[0])和(p[1] - y[1])/(p[0] - y[0])
代码:
public int maxPoints(int[][] ps) {
int n = ps.length;//看二维数组有多少行
int ans = 1;//最后结果统计的值
for (int i = 0; i < n; i++) {
int[] x = ps[i];//每一位上都是一个坐标x=[1,1]
for (int j = i + 1; j < n; j++) {
int[] y = ps[j];//y=[3,2]
int cnt = 2;//x和y这两个点已经可以了
for (int k = j + 1; k < n; k++) {//x,y,k是三个下标指针
int[] p = ps[k];//p=[5,3]
/*
为了避开0,以及乘积需要约分的情况,其实是(y[1] - x[1])/(y[0] - x[0])
和(p[1] - y[1])/(p[0] - y[0])
*/
int s1 = (y[1] - x[1]) * (p[0] - y[0]);//求的是两个斜率
int s2 = (p[1] - y[1]) * (y[0] - x[0]);
if (s1 == s2) cnt++;
}
ans = Math.max(ans, cnt);
}
}
return ans;
}