Day13-2021.1.21-剑指offer-栈和队列的整理。

Day13-2021.1.21-剑指offer-栈和队列的整理。

3.栈和队列

名字备注
剑指 Offer 09.用两个栈实现队列.简单形式新,实现新函数功能。
剑指 Offer 30.包含min函数的栈.简单形式新,实现新函数功能。
剑指 Offer 31.栈的压入、弹出序列.中等
剑指 Offer 58 - I.翻转单词顺序.简单
剑指 Offer 59 - I.滑动窗口的最大值.简单
剑指 Offer 59 - II.队列的最大值.中等
1.剑指 Offer 09 .用两个栈实现队列 【需要重刷】

完成在队列尾部插入整数和在队列头部删除整数的功能

思考:看着简单,但是这种题型新啊。另外,有视频讲解的挺清楚:简洁版+视频讲解的挺清楚:详细版+课后文字题解版本:

一个队列具备的两个功能分别由两个栈来完成:栈A实现入队功能,栈B实现出队功能。

栈无法实现队列功能: 栈底元素(对应队首元素)无法直接删除,需要将上方所有元素出栈。
双栈可实现列表倒序: 设有含三个元素的栈 stack1 = [1,2,3]和空栈 stack2 = []。若循环执行 stack1 元素出栈并添加入栈 stack2 ,直到栈 stack1 为空,则 stack1 = [], stack2 = [3,2,1],即 栈 stack2 元素实现栈 stack1 元素倒序 。
利用栈 B 删除队首元素: 倒序后,B 执行出栈则相当于删除了 A 的栈底元素,即对应删除了队列的队首元素。

import java.util.Stack;

// 一个队列具备的两个功能分别由两个栈来完成:栈A实现入队功能,栈B实现出队功能
public class Offer09 {
    Stack<Integer> stack1;
    Stack<Integer> stack2;
    int size;

    // 两个栈1和2
    public void CQueue() {
        stack1 = new Stack<>();
        stack2 = new Stack<>();
        size = 0;
    }

    // 存的时候存到1里
    public void appendTail(int value) {
        stack1.push(value);
        size++;
    }

    // 取的时候看2里有没有元素,如果有则将2里的元素pop出,如果没有则将1里的元素全部pop到2中
    public int deleteHead() {
        if (size == 0) {
            return -1;
        }
        if (stack2.isEmpty()) {
            while (!stack1.isEmpty()) {
                stack2.push(stack1.pop());
            }
        }
        // 然后再从2中pop出对应个数的元素
        size--;
        return stack2.pop();
    }
}
2.剑指 Offer 30 .包含min函数的栈 【需要重刷】

实现一个能够得到栈的最小元素的 min 函数—>>>栈A实现元素存储,栈B实现最小元素的存储。

注意:本题与主站 155 题相同:https://leetcode-cn.com/problems/min-stack/

思考:简单类型

序号方法描述—Java Stack 类,后进先出。
1boolean empty() 测试堆栈是否为空。
2Object peek( ) 查看堆栈顶部的对象,但不从堆栈中移除它。
3Object pop( ) 移除堆栈顶部的对象,并作为此函数的值返回该对象。
4Object push(Object element) 把项压入堆栈顶部。
5int search(Object element) 返回对象在堆栈中的位置,以 1 为基数。
import java.util.Stack;

public class Offer30 {
    Stack<Integer> A, B;

    // 在提交代码时,不能有 void ,具体原因我也不知道。
    public void MinStack() {
    // public MinStack() {
        A = new Stack<>();
        B = new Stack<>();
    }

    public void push(int x) {
        A.push(x);
        if (B.empty() || B.peek() >= x)
            B.push(x);
    }

    public void pop() {
        if (A.pop().equals(B.peek()))
            B.pop();
    }

    public int top() {
        return A.peek();
    }

    // 115题的函数名字是getMin()
    public int min() {
        return B.peek();
    }
}
3.剑指 Offer 31 .栈的压入、弹出序列

注意:本题与主站 946 题相同:https://leetcode-cn.com/problems/validate-stack-sequences/

(1)

思考:视频讲解:,判断某一个序列是不是栈的弹出序列,那么需要模拟栈的压入和弹出。

image-20210120070811990

下面的代码是按照视频上敲的,但是力扣官方通过不了,原因未知。

import java.util.Stack;

public class Offer31_1 {
    public static void main(String[] args) {
        int[] pushed = new int[]{1, 2, 3, 4, 5};
        int[] popped = new int[]{4, 5, 3, 2, 1};
        System.out.println(validateStackSequences(pushed, popped));
    }

    public static boolean validateStackSequences(int[] pushed, int[] popped) {
        // 如果两个数组大小不一致,则返回false。
        if (pushed.length != popped.length) {
            return false;
        }
        // 这个相当于 输入的数组为空数组。
        if (pushed.length < 1) {
            return true;
        }

        Stack<Integer> stack = new Stack<>();
        // 一号元素入栈
        stack.push(pushed[0]);
        int i = 1;
        for (int j = 0; j < popped.length; j++) {
            int num = popped[j];
            // 不停的入栈,直到找到相同的代码
            while (stack.peek() != num && i < pushed.length) {
                stack.push(pushed[i++]);
            }
            // 找到相同的
            if (stack.peek() == num) {
                stack.pop();
                // 结束运行
                continue;
            }
            // 这里相当于没有找到
            return false;
        }
        return true;
    }
}
(2)
4.剑指 Offer 58 - I 翻转单词顺序

思考:视频讲解:

看起来也是简单的题型,通过入栈操作来处理,栈的原则就是先入后出,正好实现了翻转。

面试时,建议使用双指针,而不是分割+倒序的做法。


(2)分割+倒序【简单做法,常规思路】
import java.util.Stack;

public class Offer58_1 {
    public static void main(String[] args) {
        String s = new String("the   sky is blue");
        System.out.println(reverseWords(s));
    }

    public static String reverseWords(String s) {
        String[] strs = s.trim().split(" "); // 删除首尾空格,分割字符串
        StringBuilder res = new StringBuilder();
        for (int i = strs.length - 1; i >= 0; i--) { // 倒序遍历单词列表
            // 这个,是
            if (strs[i].equals("")) continue; // 遇到空单词则跳过
            res.append(strs[i] + " "); // 将单词拼接至 StringBuilder
        }
        return res.toString().trim(); // 转化为字符串,删除尾部空格,并返回
    }
}
5.剑指 Offer 58 - II 左旋转字符串

思考:视频讲解:

(1)字符串切片
public class Offer58_2 {
    public static void main(String[] args) {
        String s = "abcdefg";
        int k = 2;
        System.out.println(reverseLeftWords(s, k));
    }

    public static String reverseLeftWords(String s, int n) {
        // 1 <= k < s.length <= 10000
        String s1 = s.substring(0, n);
        String s2 = s.substring(n, s.length());
        return s2 + s1;
    }
} 
(2)列表遍历拼接【也简单】

若面试规定不允许使用 切片函数 ,则使用此方法。

public class Offer58_2_2 {
    public static void main(String[] args) {
        String s = "abcdefg";
        int k = 2;
        System.out.println(reverseLeftWords(s, k));
    }

    public static String reverseLeftWords(String s, int n) {
        StringBuffer res = new StringBuffer();
        for (int i = n; i < s.length(); i++) {
            res.append(s.charAt(i));
        }
        for (int i = 0; i < n; i++) {
            res.append(s.charAt(i));
        }
        return res.toString();
    }
} 
6.剑指 Offer 59 - I 滑动窗口的最大值

队列,其实呢,你看文字解析看的是不太清楚的,去看视频吧。

思考:视频讲解:

注意:本题与主站 239 题相同:https://leetcode-cn.com/problems/sliding-window-maximum/

(1)暴力

两层 for 循环,每次都从窗口中找最大值即可。这样做的缺点是性能差。

import java.util.Deque;
import java.util.LinkedList;

public class Offer59_1_1 {
    public static void main(String[] args) {
        int[] nums = new int[]{1, 3, -1, -3, 5, 3, 6, 7};
        int k = 3;
        System.out.println(maxSlidingWindow(nums, k));
    }

    public static int[] maxSlidingWindow(int[] nums, int k) {
        int n = nums.length;
        if (n == 0) {
            return nums;
        }
        int result[] = new int[n - k + 1];
        for (int i = 0; i < result.length; i++) {
            int max = Integer.MIN_VALUE;
            for (int j = 0; j < k; j++) {
                max = Math.max(max, nums[i + j]);
            }
            result[i] = max;
        }
        return result;
    }
}

时间复杂度的话是 O(nk)

(2)单调队列

参考 这里

我们可以用一个单调递减队列。单调递减队列添加元素的算法如下。

如果当前元素比队列的最后一个元素大,那么就将最后一个元素出队,重复这步直到当前元素小于队列的最后一个元素或者队列为空。进行下一步。

如果当前元素小于等于队列的最后一个元素或者队列为空,那么就直接将当前元素入队。

按照上边的方法添加元素,队列中的元素就刚好是一个单调递减的序列,而最大值就刚好是队头的元素。

当队列的元素等于窗口的大小的时候,由于添加元素的时候我们进行了出队操作,所以我们不能像解法二那样每次都删除第一个元素,需要先判断一下队头元素是否是我们要删除的元素。

// 业务代码
public int[] maxSlidingWindow(int[] nums, int k) {
    Deque<Integer> max = new ArrayDeque<>();
    int n = nums.length;
    if (n == 0) {
        return nums;
    }
    int result[] = new int[n - k + 1];
    int index = 0;
    for (int i = 0; i < n; i++) {
        if (i >= k) {
            if (max.peekFirst() == nums[i - k]) {
                max.removeFirst();
            }
        }
        while (!max.isEmpty() && nums[i] > max.peekLast()) {
            max.removeLast();
        }

        max.addLast(nums[i]);
        if (i >= k - 1) {
            result[index++] = max.peekFirst();
        }
    }
    return result;
}

时间复杂度就是 O(n)了,因为每个元素最多只会添加到队列和从队列删除两次操作。

import java.util.Deque;
import java.util.LinkedList;

public class Offer59_1 {
    public static void main(String[] args) {
        int[] nums = new int[]{1, 3, -1, -3, 5, 3, 6, 7};
        int k = 3;
        System.out.println(maxSlidingWindow(nums, k));
    }

    public static int[] maxSlidingWindow(int[] nums, int k) {
        // 这里是说,如果数组的长度为0,或者窗口的宽度为0,那么直接返回一个结果。
        if (nums.length == 0 || k == 0) {
            return new int[0];
        }
        // Deque 双端队列,
        Deque<Integer> deque = new LinkedList<>();
        // 用来存储结果
        int[] res = new int[nums.length - k + 1];
        // i,j,分别去控制长度
        for (int j = 0, i = 1 - k; j < nums.length; i++, j++) {
            // i >= 1这里是为了控制 i-1≥0的。
            if (i >= 1 && deque.peekFirst() == nums[i - 1])
                deque.removeFirst(); // 删除 deque 中对应的 nums[i-1]
            while (!deque.isEmpty() && deque.peekLast() < nums[j])
                deque.removeLast(); // 保持 deque 递减
            deque.addLast(nums[j]);
            if (i >= 0)
                res[i] = deque.peekFirst();  // 记录窗口最大值
        }
        return res;
    }
}
7.剑指 Offer 59 - II 队列的最大值

思考:视频讲解:


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值