算法学习day03(队列/栈/队列)

一、字符串中的单词反转(反转字符串/双指针去空格)

你的朋友喜欢从右往左读,给你一个正序的字符串。将它设置为反序的 并且单词之间或者开头有若干个空格(最少是1)

输入:
the sky is blue
输出
blue is sky the

大致思路:

1.先将整个字符串反转一下。这样单词的位置就正确了。

2.可以去空格/也可以将单词进行反转(这样单词的顺序就正确了)

3.将单词进行反转

反转字符串的代码:

    public char[] reverse(char[] ch, int left, int right) {
        while (left < right) {
            char temp = ch[left];
            ch[left] = ch[right];
            ch[right] = temp;
            left++;
            right--;
        }
        return ch;
    }

去除空格(使用双指针):(和删除元素类似)

1.当快指针所指向的位置为'  '的时候,fast++

2.当快指针指向的不是空的时候,并且slow指向不是下标0的时候(第一个单词是不用加空格的),此时要先在单词前面加一个空格,然后使用while循环遍历,将整个单词复制。

代码:

int fast=0;int slow=0;
        while(fast<ch.length){
            //跳过空格
            while(fast<ch.length&&ch[fast]==' ')fast++;
            //复制单词
            while(fast<ch.length&&ch[fast]!=' '){
                if(slow!=0){
                    ch[slow++]=' ';
                }
                while(fast<ch.length&&ch[fast]!=' '){
                    ch[slow++]=ch[fast++];
                }//这个循环结束那么外层的while循环也会结束的
            }
        }
        ch=Arrays.copyOf(ch,slow);//要更新ch的长度

将每个单词都反转:

使用for循环,当遇到空格的时候就停下来,对前面的字符串进行反转。可以使用index计数器然后计算字符串的长度,使用reverse(ch,left,right)进行反转。

注意:

1.当第一个单词找到之后,新的left要更新至index+1,因为index这个位置是空格。

2.因为是找到空格 然后更新前面的单词。但是最后一个单词前面是没有空格的,所以要特殊考虑最后一种情况

int left=0;
        for(int i=0;i<ch.length;i++){
            if(i<ch.length&&ch[i]==' '){
                //当遇到空格之后才会停止下来 进行翻转
                reverse(ch,left,i-1);//翻转left->i-1的
                left=i+1;//i是空格 直接跳到i+1
            }else if(i==ch.length-1){
                reverse(ch,left,i);
            }
        }

        return new String(ch);

二、使用队列实现栈

双栈,一个输入,一个输出。因为栈的特点是先进后出,因此双栈就可以实现先进的先出。

     将in栈中的数据都放到out栈中
    private void in2out() {
        // 将in栈中的元素都移到out栈中
        while (!inStack.isEmpty()) {
            outStack.push(inStack.pop());
        }
    }

队列的pop():

public int pop(){
    if(outStack.isEmpty()){
        in2out();
    }
        return outStack.pop();
}

队列中的peek():返回队列的首个元素

public int peek(){

    int result=this.pop();
    outStack.pull(result);//因为第一个元素已经弹出去了,所以要把它加回来
    return result; 
}

三、用队列实现栈

弹栈的时候pop(),因为栈具有先进后出的特点,队列具有先进先出的特点。

因此弹栈的时候,队列应该先将最后一个元素之前的元素都移到后面去。

疑惑的地方:在挨个获取元素往后移的时候,我只用移动n-1次。但是(n-1)次无法运行出正确的结果。n次才是正确的

​
    public void push(int x) {
        queue1.push(x);
    }

    public int pop() {
        int size=queue1.size();
        for (int i = 1; i <=size; i++) {
            int number = queue1.peekFirst();// 挨个获取元素
            System.out.println(i+":"+number);
            queue1.pollFirst();
        }
        int res = queue1.pollFirst();//到了最后一个元素 弹出
        return res;
    }

​

四、有效的括号:

卡尔哥的思路非常好:当遇到( { [的时候,就将另一半的括号放到栈里面。如果遇到)}]就去和栈顶的元素做比较,如果无法匹配,return false;如果可以匹配,就将栈顶的元素弹出;在遍历完之后,如果遇到栈不为空,说明有括号没有消除(有多余的左括号或者右括号)。

为什么在遇到右半括号时,要将栈顶元素和ch[i]作比较?因为每一对括号都是相互对称的,先加进去的括号离得远,后进来的括号离得近,就要先比较。正好符合栈的先进后出的特点。

代码:
public boolean isValid(String s) {
        Stack<Character> stack =new Stack<>();
        char[] ch = s.toCharArray();
        if ((ch.length) % 2 != 0)
            return false;
        for (int i = 0; i < ch.length; i++) {
            if (ch[i] == '(')
                stack.push(')');
            else if (ch[i] == '[')
                stack.push(']');
            else if (ch[i] == '{')
                stack.push('}');
            else if (stack.isEmpty() || ch[i] != stack.peek()) {
                return false;
            } else {
                stack.pop();
            }
        }
        return stack.isEmpty();
    }

五、删除字符串中的所有相邻重复项

我的想法:仍然利用栈的先进后出的特点。

1.当栈里面为空的话,直接push(char[i]);

2.1如果栈里面不为空,如果char[i]和栈顶元素相同的话,那么就pop掉栈顶元素。

2.2如果不相同,那么就push(char[i]);

最后栈里面剩下的元素就是结果。我这里使用reverse将它反转输出了。

  public String removeDuplicates(String s) {
        //将每次ch[i]和栈顶的元素相比较
        //如果相同 就pop掉 如果不相同就push进去
        Stack<Character> stack=new Stack<>();
        char[] ch=s.toCharArray();
        int size=ch.length;
        for(int i=0;i<size;i++){
            if(stack.isEmpty()){
                stack.push(ch[i]);
            }
            else if(ch[i]!=stack.peek()){
                stack.push(ch[i]);
            }else if(ch[i]==stack.peek()){
                stack.pop();
            }
        }
        StringBuilder sb=new StringBuilder();
        while(!stack.isEmpty()){
            sb.append(stack.pop());
        }
        return reverse(sb.toString());
        
    }
    public String reverse(String str){
        char[] ch=str.toCharArray();
        int left=0;
        int right=ch.length-1;
        while(left<right){
            char temp=ch[left];
            ch[left]=ch[right];
            ch[right]=temp;
            left++;
            right--;
        }
        return new String(ch);
    }

六、逆波兰表达式求值:

将表达式按照二叉树的形式铺展下来,逆波兰表达式就是对二叉树进行后序遍历。而普通的写法是对二叉树的中序遍历。逆波兰表达式:12+34*+5-  = 中序遍历(1+2) +3*4-5

规则:遇到数字添加到栈中,遇到表达式,从栈中弹出两个元素,运算之后加入到栈中。

逻辑很简单。

public int evalRPN(String[] tokens) {
        Deque<Integer> stack = new LinkedList();
        for (String s : tokens) {
            if ("+".equals(s)) {        // leetcode 内置jdk的问题,不能使用==判断字符串是否相等
                stack.push(stack.pop() + stack.pop());      // 注意 - 和/ 需要特殊处理
            } else if ("-".equals(s)) {
                stack.push(-stack.pop() + stack.pop());
            } else if ("*".equals(s)) {
                stack.push(stack.pop() * stack.pop());
            } else if ("/".equals(s)) {
                int temp1 = stack.pop();
                int temp2 = stack.pop();
                stack.push(temp2 / temp1);
            } else {
                stack.push(Integer.valueOf(s));
            }
        }
        return stack.pop();
    }

七、滑动窗口最大值(暴力法/单调队列)

暴力法的话,会有超时的问题。

暴力代码:

    public int[] maxSlidingWindow(int[] nums, int k) {
        int size=nums.length-k;
        int max[]=new int[size+1];
        for(int i=0;i<=size;i++){
            int maxs=findMax(nums,i,i+k-1);
            max[i]=maxs;
        }
        return max;
    }
    public int findMax(int []nums,int left,int right){
        int max=nums[left];
        for(int i=left;i<=right;i++){
            if(nums[i]>max)max=nums[i];
        }
        return max;
    }

单调队列:

如果它不是最大的,它不可能在最前边,因为最大的会被它消除。如果它在最前面,那么它一定是最大的。

自定义单调队列:

poll弹出:如果==val?弹出????

add添加:添加一个值,比前面谁大,谁就出去。removeLast();

peek取值:每次取顶点的值

代码:

先add四个元素,添加之后,最大的会在最前面。然后从k开始,从下标为0开始弹出,因为已经装满四个了,所以先弹出,再添加,再返回顶点。

class MyQueue {
    // 自定义单调队列中包含poll弹出、add添加、peek查看顶点
    Deque<Integer> deque = new LinkedList<>();

    // poll弹出
    void poll(int val) {
        if (!deque.isEmpty() && deque.peek() == val) {
            deque.poll();
        }
    }

    // add添加
    void add(int val) {
        while (!deque.isEmpty() && val > deque.getLast()) {
            deque.removeLast();// 如果添加了一个元素 比前面的元素大 比谁大谁就被移出去
        }
        deque.add(val);
    }

    // 查看顶点(最大值)
    int peek() {
        return deque.peek();
    }
}

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        // 首先剪枝操作
        if (nums.length == 1)
            return nums;
        // 创建存放结果的数组
        int size = nums.length - k + 1;
        int[] result = new int[size];
        // 然后创建自定义队列
        MyQueue myqueue = new MyQueue();
        for (int i = 0; i < k; i++) {
            myqueue.add(nums[i]);
        }
        int num = 0;
        result[num++] = myqueue.peek();
        
        // 从下面这个循环就要开始移除了 因为已经装入了k个元素
        for (int i = k; i < nums.length; i++) {
            myqueue.poll(nums[i - k]);// i=0开始移除
            myqueue.add(nums[i]);// 如果不大就放后面 如果大 前面的移除
            result[num++] = myqueue.peek();
        }
        return result;
    }
}

八、前K个高频元素(大根堆/小根堆)

大根堆和小根堆都满足完全二叉树的性质,父节点的下标为i,两个子节点的下标为2i+1,2i+2

大根堆:父节点比子节点大

小根堆:父节点比子节点小,最小的在上面。依次pop(),就是从小到大堆排序

上滤:根节点向上调整的操作。当处于上面的结点破坏了堆序性,就要进行上滤操作。维护堆的性质

下滤:根节点向下调整的操作。

比如在大根堆中,8作为6的子节点,却比6还要大。因此8要进行上滤,维护堆的性质

自上而下建堆法 (上滤)

自下而上建堆法(按照前后顺序先放到堆里面,然后根据下滤操作,将下面的结点往上移)

大根堆进行堆排序的时候,pop栈顶元素,然后将最后一个元素放到栈顶进行下滤操作,操作之后找到最大值然后再次pop,循环操作。

代码:

首先将各个数字出现的次数放到map集合中保存。然后将(num,cnt)插入到pq中。

pq:优先级队列,在创建pq的时候会定义一个比较规则作为参数。

    public int[] topKFrequent(int[] nums, int k) {
        //将各个数字出现的次数 放到一个map集合中 key:数组值 value:频率
        Map<Integer,Integer> map=new HashMap<>();
        for(int num:nums){
            map.put(num,map.getOrDefault(num,0)+1);
        }
        //在优先队列中存储二元组(num,cnt),cnt是出现的次数
        //优先级队列pq 参数里面是比较的规则 加进去就直接排好了
        PriorityQueue<int[]> pq=new PriorityQueue<>((pair1,pair2)->pair2[1]-pair1[1]);
        //将map集合中的数据放在pq中
        for(Map.Entry<Integer,Integer> entry:map.entrySet()){
            pq.add(new int[]{entry.getKey(),entry.getValue()});
        }
        int[] ans=new int[k];//大小为k的数组
        for(int i=0;i<k;i++){
            ans[i]=pq.poll()[0];
        }
        return ans;
    }

  • 33
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值