第11章—妙用数据结构

数组、栈、队列、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。队尾值为13>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。队尾值为-35>-3,依次弹出后加入。队列:{5}。此时L=2,R=4,有效。result=[3,3,5]
i=5,nums[5]=3。队尾值为53<5,直接加入。队列:{5,3}。此时L=3,R=5,有效。result=[3,3,5,5]
i=6,nums[6]=6。队尾值为36>3,依次弹出后加入。队列:{6}。此时L=4,R=6,有效。result=[3,3,5,5,6]
i=7,nums[7]=7。队尾值为67>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;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值