队栈和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; } }