队栈和Hash的经典算法题-算法通关村

文章讲述了如何使用栈和队列数据结构分别解决LeetCode中的232和225题,以及如何用这两个基本结构实现两数之和和三数之和问题,优化搜索效率。
摘要由CSDN通过智能技术生成

队栈和Hash的经典算法题-算法通关村


1.用栈实现队列

  • 栈的特点是后进先出,队的特点是先进先出。两个栈将底部拼接到一起就能实现队列的效果,通过队列也能实现栈的功能。在很多地方能看到让你通过两个栈实现队列的题目,也有很多地方是两个队列实现栈的题目,我们就干脆一次看一下如何做。这正好对应LeetCode232和225两道题。

  • LeetCode232 :请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek, empty) :

  • 要实现的 MyQueue 类:
    void push(int x) 将元素 x 推到队列的末尾
    int pop() 从队列的开头移除并返回元素
    int peek() 返回队列开头的元素
    boolean empty() 如果队列为空,返回 true, 否则,返回 false
  • 这个题的思路是,将一个栈当作输入栈,用于压入push传入的数据;另一个栈当作输出栈,用
    于 pop 和 peek 操作。每次pop 或 peek 时,若输出栈为空则将输入栈的全部数据依次弹出并压入输出栈,这样输出栈从栈顶往栈底的顺序就是队列从队首往队尾的顺序

  •   public class MyQueue {
          Deque<Integer> inStack;
          Deque<Integer> outStack;
      
          public MyQueue(){
              inStack = new ArrayDeque<>();
              outStack = new ArrayDeque<>();
          }
      
          public void push(int x){
              inStack.push(x);
          }
      
          public int pop(){
              if(outStack.isEmpty()){
                  while(!inStack.isEmpty()){
                      outStack.push(inStack.pop());
                  }
              }
              return outStack.pop();
          }
      
          public int peek(){
              if(outStack.isEmpty()){
                  while(!inStack.isEmpty()){
                      outStack.push(inStack.pop());
                  }
              }
              return outStack.peek();
          }
      
          public boolean empty(){
              return inStack.isEmpty() && outStack.isEmpty();
          }
      }
      
    

2用队列实现栈

  • leetcode225 :请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push, top, pop 和 empty) .

  • 实现 MyStack 类:
    void push(int x) 将元素 x 压入栈顶
    int pop() 移除并返回栈顶
    int pop() 返回栈顶元素
    boolean empty() 如果栈是空的,返回 true,否则,返回 false
  • 分析:这个问题首先想到的是使用两个队列来实现。为了满足栈的特性,即最后入栈的元素最先出栈,在使用队列实现栈时,应满足队列前端的元素是最后入栈的元素。可以使用两个队列实现栈的操作,其中queue1用于存储栈内的元素,queue2作为入栈操作的辅助队列。

  • 入栈操作时,首先将元素入队到 queue2,然后将 queue1的全部元素依次出队并入队到queue2,此时 queue2的前端的元素即为新入栈的元素,再将 queue1和queue2互换,则queue1的元素即为栈内的元素,queue 1的前端和后端分别对应栈顶和栈底。

  • 由于每次入栈操作都确保queue1的前端元素栈顶元素,因此出栈操作和获得栈顶元素操作都可以简单实现。出栈操作只需要移除queue1的前端元素并返回即可,获得栈顶元素操作只需要获得queue 1的前端元素并返回即可(不移除元素)。

  • 由于 queue 1用于存储栈内的元素,判断栈是否为空时,只需要判断 queue1是否为空即可。

  •   public class StackByQueue {
          Deque<Integer> queue1;
          Deque<Integer> queue2;
      
          public StackByQueue(){
              queue1 = new ArrayDeque<>();
              queue2 = new ArrayDeque<>();
          }
      
          public void push(int x){
              //将元素 x 插入到 queue2 的末尾。
              queue2.offer(x);
              while(!queue1.isEmpty()){
                  queue2.offer(queue1.poll());
              }
              Deque<Integer> temp = queue1;
              queue1 = queue2;
              queue2 = temp;
          }
      
          public int pop(){
              //从队列 queue1 中移除并返回队头的元素。
              return queue1.poll();
          }
      
          public int top(){
              return queue1.peek();
          }
      
          public boolean empty(){
              return queue1.isEmpty();
          }
      
      }
      
    

3 n数之和专题

  • LeetCode的第一题就是求两数之和的问题,事实上除此之外,还有几个类似的问题,例如LeetCode15 三数之和,LeetCode18.四数相加和 LeetCode454.四数相加|等等。

3.1两数之和

  • LeetCode1.给定一个整数数组 nums和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那两个整数,并返回它们的数组下标。你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。你可以按任意顺序返回答案。

  • 示例1:
    输入:nums = [2, 7, 11, 15],target = 9
    输出:[0, 1]
    解释:因为 nums[0] + nums[1] == 9, 返回 [0, 1]
    示例2:
    输入:nums = [3, 2, 4], target = 6
    输出:[1, 2]
  • 可以使用两层循环解决,第一层确定一个数,2,7,一直到11,然后内层循环继续遍历后序元素,判断是否存在 target - x 的数即可。

  •   public int[] twoSum(int[] nums, int target){
              for(int i = 0; i < nums.length; i++){
                  for(int j = i+1; j < nums.length; j++){
                      if(nums[i] + nums[j] == target){
                          return new int[]{i, j};
                      }
                  }
              }
              return new int[0];
          }
    
  • 这种方式的不足在于寻找target -x 的时间复杂度过高,可以使用哈希表,将寻找target - ×的时间复杂度降低到从 O(N)降低到 O(1)。创建一个哈希表,对于每一个x,首先查询哈希表中是否存在 target -x,然后将x插入到哈希表中,即可保证不会让×和自己匹配。

  •   public int[] twoSum2(int[] nums, int target){
              Map<Integer, Integer> hashtable = new HashMap<Integer, Integer>();
              for(int i = 0; i < nums.length; i++){
                  if(hashtable.containsKey(target-nums[i])){
                      return new int[]{hashtable.get(target-nums[i]), i};
                  }
                  hashtable.put(nums[i], i);
              }
              return new int[0];
          }
    

3.2三数之和

  • LeetCode15:给你一个包含 n个整数的数组nums,判断nums 中是否存在三个元素 a,b,c,使得a +b+c=0?请你找出所有和为0且不重复的三元组。注意:答案中不可以包含重复的三元组。

  • 示例1:
    输入:nums = [-1, 0, 1, 2, -1, -4]
    输出:[[-1, -1, 2],[-1, 0, 1]]
  • 本题难度增加了很多,我们可以使用三层循环直接找,时间复杂度为0(n^3),太高了,放弃。
    公认最好的方式是**”排序+双指针“。我们可以先将数组排序来处理重复结果,然后还需固定一位元素**,由于数组是排好序的,所以我们用双指针来不断寻找即可求解。

  •   public class Solution {
          public List<List<Integer>> threeSum(int[] nums){
              Arrays.sort(nums);
              List<List<Integer>> ans = new ArrayList<>();
              //枚举 a
              for(int first = 0; first < nums.length; first++){
                  //需要和上一次枚举的数不相同
                  if(first > 0 && nums[first] == nums[first-1]){
                      continue;
                  }
                  //c 对应的指针初始化指向数组的最右端
                  int thrid = nums.length-1;
                  int target = -nums[first];
                  // 枚举 b
                  for(int second = first+1; second < nums.length; second++){
                      //需要和上一次枚举的数不相同
                      if(second > first+1 && nums[second] == nums[second-1]){
                          continue;
                      }
                      //需要保证 b 的指针在 c 的指针的左侧
                      while(second < thrid && nums[second] + nums[thrid] > target){
                          thrid--;
                      }
                      //如果指针重合,随着 b 的增加
                      //就会不满足 a+b+c =  并且不满足 b < c
                      if(second == thrid){
                          break;
                      }
                      if(nums[second] + nums[thrid] == target){
                          List<Integer> list = new ArrayList<>();
                          list.add(nums[first]);
                          list.add(nums[second]);
                          list.add(nums[thrid]);
                          ans.add(list);
                          
                      }
                  }
              }
              return ans;
          }
      }
      
    
    
    
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

种一棵树leng

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值