队栈和hash的经典算法题(算法村第五关白银挑战)
用栈实现队列
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push
、pop
、peek
、empty
):
实现 MyQueue
类:
void push(int x)
将元素 x 推到队列的末尾int pop()
从队列的开头移除并返回元素int peek()
返回队列开头的元素boolean empty()
如果队列为空,返回true
;否则,返回false
说明:
- 你 只能 使用标准的栈操作 —— 也就是只有
push to top
,peek/pop from top
,size
, 和is empty
操作是合法的。 - 你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。
示例 1:
输入:
["MyQueue", "push", "push", "peek", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 1, 1, false]
解释:
MyQueue myQueue = new MyQueue();
myQueue.push(1); // queue is: [1]
myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
myQueue.peek(); // return 1
myQueue.pop(); // return 1, queue is [2]
myQueue.empty(); // return false
提示:
1 <= x <= 9
- 最多调用
100
次push
、pop
、peek
和empty
- 假设所有操作都是有效的 (例如,一个空的队列不会调用
pop
或者peek
操作)
进阶:
- 你能否实现每个操作均摊时间复杂度为
O(1)
的队列?换句话说,执行n
个操作的总时间复杂度为O(n)
,即使其中一个操作可能花费较长时间。
输入栈和输出栈
输入栈用于push,输出栈用于pop 和 peek。每次pop 或 peek 时,若输出栈为空,则将输入栈的全部数据依次弹出并压入输出栈,这样输出栈从栈顶往栈底的顺序就是队列从队首往队尾的顺序
class MyQueue
{
ArrayDeque<Integer> inStack; //输入栈
ArrayDeque<Integer> outStack; //输出栈
public MyQueue()
{
inStack = new ArrayDeque<Integer>();
outStack = new ArrayDeque<Integer>();
}
//将输入栈的元素全部倒入输出栈
public void inStack_To_outStack()
{
while (!inStack.isEmpty())
outStack.push(inStack.pop());
}
public void push(int x)
{
inStack.push(x);
}
public int pop()
{
if (outStack.isEmpty())
inStack_To_outStack();
return outStack.pop();
}
public int peek()
{
if (outStack.isEmpty())
inStack_To_outStack();
return outStack.peek();
}
public boolean empty()
{
return inStack.isEmpty() && outStack.isEmpty();
}
}
复杂度分析
时间复杂度:push 和 empty 为 O(1),pop 和 peek 为均摊 O(1)。对于每个元素,至多入栈和出栈各两次,故均摊复杂度为 O(1)。
空间复杂度:O(n)。其中 n 是操作总数。对于有 n 次 push 操作的情况,队列中会有 n 个元素,故空间复杂度为 O(n)。
用队列实现栈
请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push
、top
、pop
和 empty
)。
实现 MyStack
类:
void push(int x)
将元素 x 压入栈顶。int pop()
移除并返回栈顶元素。int top()
返回栈顶元素。boolean empty()
如果栈是空的,返回true
;否则,返回false
。
注意:
- 你只能使用队列的基本操作 —— 也就是
push to back
、peek/pop from front
、size
和is empty
这些操作。 - 你所使用的语言也许不支持队列。 你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
示例:
输入:
["MyStack", "push", "push", "top", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 2, 2, false]
解释:
MyStack myStack = new MyStack();
myStack.push(1);
myStack.push(2);
myStack.top(); // 返回 2
myStack.pop(); // 返回 2
myStack.empty(); // 返回 False
提示:
1 <= x <= 9
- 最多调用
100
次push
、pop
、top
和empty
- 每次调用
pop
和top
都保证栈不为空
**进阶:**你能否仅用一个队列来实现栈。
一个队列
可以把队列想成一个环,在队尾加入新元素后,之前已存在的元素沿着环回到了新元素的后面,使新元素变成了队头,而已存在元素的相对位置不会发生改变(队列输出不改变元素顺序)。从一开始push时就维护整个队列的这种属性:新入队的元素都在队头,之后的操作也一直保持此性质,从而使整个队列变成栈。
class MyStack
{
ArrayDeque<Integer> queue;
public MyStack()
{
queue = new ArrayDeque<Integer>();
}
public void push(int x)
{
queue.offer(x);
for (int i = 0; i < queue.size() - 1; i++)
queue.offer(queue.poll());
}
public int pop()
{
return queue.poll();
}
public int top()
{
return queue.peek();
}
public boolean empty()
{
return queue.isEmpty();
}
}
n数之和专题
两数之和
给定一个整数数组 nums
和一个整数目标值 target
,请你在该数组中找出 和为目标值 target
的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
示例 :
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
提示:
2 <= nums.length <= 104
-109 <= nums[i] <= 109
-109 <= target <= 109
- 只会存在一个有效答案
**进阶:**你可以想出一个时间复杂度小于 O(n2)
的算法吗?
hashMap
- key为数组元素,value存储数组下标
- 问题转化:寻找值为 target - num[i] 的数组元素
public int[] twoSum(int[] nums,int target)
{
HashMap<Integer,Integer> hashMap = new HashMap<>();
for (int i = 0; i < nums.length; i++)
{
if(hashMap.containsKey(target - nums[i]))
return new int[]{ hashMap.get(target - nums[i]), i};
hashMap.put(nums[i], i);
}
return new int[]{Integer.MAX_VALUE}; //没有找到符合要求的两个元素
}
三数之和
给你一个整数数组 nums
,判断是否存在三元组 [nums[i], nums[j], nums[k]]
满足 i != j
、i != k
且 j != k
,同时还满足 nums[i] + nums[j] + nums[k] == 0
。请
你返回所有和为 0
且不重复的三元组。
**注意:**答案中不可以包含重复的三元组。
示例 1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。
示例 2:
输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。
示例 3:
输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。
提示:
3 <= nums.length <= 3000
-105 <= nums[i] <= 105
双层循环+hashMap
首先按照第一题两数之和的思路,我们可以先固定一个数target,再利用两数之和的思想去map中存取或查找 (-1)*target - num[j] 。
但问题是无法消除重复结果,例如如果输入[-1,0,1,2,-1,-4],返回的结果是[[-1,1,0],[-1,-1,2],[0,1,-1],[0,-1,1],[1,-1,0],[2,-1,-1]],如果我们再增加一个去重方法,将直接导致执行超时
先排序,然后定2动1
public List<List<Integer>> threeSum(int[] nums)
{
List<List<Integer>> ans = new ArrayList<>();
Arrays.sort(nums); //将nums排为升序
//枚举第一个数
for (int first = 0; first < nums.length - 2; first++)
{
//优化:数组排序后,如果某个nums[first]已经大于0,那nums[first] + nums[second] + nums[third] 不可能再= 0,这时可以直接退出最外层循环。
if(nums[first] > 0)
break;
//跳过与上次相同的数字
if (first > 0 && nums[first] == nums[first - 1])
continue;
//枚举第二个数
for(int second = first + 1; second < nums.length - 1; second++)
{
//跳过与上次相同的数字
if (second > first + 1 && nums[second] == nums[second - 1])
continue;
//把第一个数和第二个数暂时固定下来后,开始动第三个数
int third = nums.length - 1;
//第三个数必须在第二个数的右侧,避免second和third指向同一个数;避免找到重复答案;避免浪费时间找不到答案
while (second < third)
{
if(nums[first] + nums[second] + nums[third] < 0)
break; //third再往前走,三者的和更小,不可能找到答案了(不做这个优化,最后一个测试用例会超时)
if (nums[first] + nums[second] + nums[third] == 0)
{
ArrayList<Integer> list = new ArrayList<>();
list.add(nums[first]);
list.add(nums[second]);
list.add(nums[third]);
ans.add(list);
break; //找到一个即跳出,避免重复
}
third --;
}
//第三个数动完之后,如果已经和第二个数重复,那可以直接跳出第二层循环了(因为往后都是与之前重复的计算)
if(second == third)
break;
}
}
return ans;
}
时间复杂度分析
双指针的方法,可以将枚举的时间复杂度从 O(N^2) 减少至 O(N)。因为在枚举的过程每一步中,「左指针」会向右移动一个位置,而「右指针」会向左移动若干个位置,这个与数组的元素有关,但我们知道这两个指针一共只会移动 N 次,均摊下来,时间复杂度为 O(N)。
注意到还有第一重循环,时间复杂度为 O(N),因此枚举的总时间复杂度为 O(N^2)。由于排序的时间复杂度为 O(Nlog?N),在渐进意义下小于前者,因此算法的总时间复杂度为 O(N^2)
四数之和 Ⅱ
给你四个整数数组 nums1
、nums2
、nums3
和 nums4
,数组长度都是 n
,请你计算有多少个元组 (i, j, k, l)
能满足:
0 <= i, j, k, l < n
nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
hashMap
要使nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
即使nums1[i] + nums2[j] == - (nums3[k] + nums4[l])
HashMap 存两组元素的和(key)及其出现次数(value),另外两组元素的和的相反数 HashMap 的key进行比对
public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4)
{
HashMap<Integer, Integer> hashMap = new HashMap<>();
for (int A : nums1)
for (int B : nums2)
{
int sumAB = A + B;
hashMap.put(sumAB, hashMap.getOrDefault(sumAB, 0) + 1);
}
int ans = 0;
for (int C : nums3)
for (int D : nums4)
{
int sumCD = C + D;
if(hashMap.containsKey(-sumCD))
ans += hashMap.get(-sumCD);
}
return ans;
}
时间复杂度分析
- HashMap 存一个数组,如 A。然后计算三个数组之和,如 BCD。时间复杂度为:O(n)+O(n^3),得到 O(n^3).
- HashMap 存三个数组之和,如 ABC。然后计算一个数组,如 D。时间复杂度为:O(n^3)+O(n),得到 O(n^3).
- HashMap存两个数组之和,如AB。然后计算两个数组之和,如 CD。时间复杂度为:O(n2)+O(n2),得到 O(n^2).
本题采用第三种方式,时间复杂度为O(n^2).
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!