leetcode 232. 用栈实现队列
这个算法的实现思路是使用两个栈来模拟队列的入队和出队操作。其中,inStack 栈用于入队操作,outStack 栈用于出队操作。
入队操作(push)时,直接将元素压入 inStack 栈即可。
出队操作(pop)时,如果 outStack 栈为空,则需要先将 inStack 栈中的元素全部转移至 outStack 栈,然后再从 outStack 栈顶弹出一个元素作为出队结果。这样做的原因是保证出队的元素按照队列的顺序进行。
获取队头元素操作(peek)与出队操作类似,也需要先判断 outStack 栈是否为空,若为空则将 inStack 栈中的元素转移到 outStack 栈,然后取出 outStack 栈顶元素作为队头元素。
为什么要这么做呢?
在使用单个栈实现队列时,入队操作只需要将元素压入栈即可,但出队操作需要将栈底的元素作为出队元素,这涉及到将栈中的元素逐个弹出并重新压入栈中的过程,时间复杂度为 O(n)。而使用两个栈来模拟队列,可以将元素从一个栈中转移到另一个栈中,从而实现出队操作的效果。由于转移元素只需一次,所以平摊下来,出队操作的时间复杂度为 O(1),与入队操作保持一致。
通过这种方式,我们可以实现以较低的时间复杂度执行队列的入队、出队和获取队头元素操作,提高了算法的效率。
class MyQueue {
Deque<Integer> inStack; // 入队操作使用的栈
Deque<Integer> outStack; // 出队和获取队头元素操作使用的栈
public MyQueue() {
inStack = new LinkedList<Integer>(); // 初始化入队栈
outStack = new LinkedList<Integer>(); // 初始化出队和获取队头元素栈
}
public void push(int x) {
inStack.push(x); // 入队操作,直接将元素压入入队栈
}
public int pop() {
if (outStack.isEmpty()) {
in2out(); // 如果出队栈为空,则将入队栈中的元素转移到出队栈
}
return outStack.pop(); // 弹出出队栈顶元素作为出队结果
}
public int peek() {
if (outStack.isEmpty()) {
in2out(); // 如果出队栈为空,则将入队栈中的元素转移到出队栈
}
return outStack.peek(); // 返回出队栈顶元素作为队头元素
}
public boolean empty() {
return inStack.isEmpty() && outStack.isEmpty(); // 判断队列是否为空
}
private void in2out() {
while (!inStack.isEmpty()) {
outStack.push(inStack.pop()); // 将入队栈中的元素逐个转移到出队栈
}
}
}
leetcode 225. 用队列实现栈
这个实现的核心思想是将新元素插入到空的 queue2 中,并将 queue1 中的所有元素依次插入到 queue2 的后面。最后,交换 queue1 和 queue2 的引用,使得 queue1 成为包含所有元素的队列,而 queue2 变为空队列。
class MyStack {
Queue<Integer> queue1;
Queue<Integer> queue2;
public MyStack() {
queue1 = new LinkedList<Integer>();
queue2 = new LinkedList<Integer>();
}
public void push(int x) {
queue2.offer(x);
while(!queue1.isEmpty()){
queue2.offer(queue1.poll());
}
Queue<Integer> temp = queue1;
queue1 = queue2;
queue2 = temp;
}
public int pop() {
return queue1.poll();
}
public int top() {
return queue1.peek();
}
public boolean empty() {
return queue1.isEmpty();
}
}
leetcode 1. 两数之和
给定一个整数数组 nums
和一个整数目标值 target
,请你在该数组中找出 和为目标值 target
的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
这种方式没什么好说的
class Solution {
public int[] twoSum(int[] nums, int target) {
for(int n = 0; n < nums.length; n++){
for(int m = n+1; m < nums.length; m++){
if(nums[n]+nums[m] == target){
return new int[]{n,m};
}
}
}
return new int[0];
}
}
算法的实现思路如下:
hashTable
用于存储已经遍历过的元素及其对应的索引。nums[i]
:
target - nums[i]
的元素,如果存在,则找到了两个数的和为 target
,返回它们的索引。nums[i]
及其索引 i
存入哈希表中。class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> hashTable = new HashMap<>();
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];
}
}
leetcode 15. 三数之和
这里使用了双指针的方法
算法的实现思路如下:
nums
进行排序。nums[i]
,将其作为三数之和中的第一个数。
nums[i]
大于 0,那么三数之和一定大于 0,不可能存在满足条件的解,直接返回结果。nums[i]
与前一个元素相同,为了避免重复计算,跳过当前循环。target - nums[i]
,其中左指针j从i+1开始,右指针k从数组末尾开始。
nums[j] + nums[k] > target - nums[i]
,将右指针向左移动,减小和的大小。nums[j] + nums[k] < target - nums[i]
,将左指针向右移动,增大和的大小。nums[j] + nums[k] == target - nums[i]
,找到一组满足条件的解,保存并同时移动左右指针。
通过使用双指针法和排序算法,可以以较低的时间复杂度 O(n^2) 解决这个问题,其中 n 是数组的长度。这是因为排序算法的时间复杂度为 O(nlogn),而寻找满足条件的解的过程为双指针法,时间复杂度为 O(n)。
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
ArrayList<List<Integer>> res = new ArrayList<>();
Arrays.sort(nums); // 对数组进行排序,方便使用双指针法
int len = nums.length;
if (len < 3 || nums[0] > 0 || nums[len - 1] < 0 || nums == null)
return res; // 如果数组长度小于3或者数组中最小值大于0、最大值小于0,则直接返回空结果
for (int i = 0; i < len - 2; i++) {
if (i > 0 && nums[i] == nums[i - 1])
continue; // 避免重复计算,如果当前元素与前一个元素相同,则跳过当前循环
int j = i + 1;
int k = len - 1;
while (j < k) {
int sum = nums[i] + nums[j] + nums[k]; // 计算三个数的和
if (sum > 0) {
k--; // 和大于0时,将右指针向左移动,减小和的大小
} else if (sum < 0) {
j++; // 和小于0时,将左指针向右移动,增大和的大小
} else {
res.add(Arrays.asList(nums[i], nums[j], nums[k])); // 找到一组满足条件的解,保存到结果中
while (j < k && nums[j] == nums[j + 1])
j++; // 跳过与已经使用过的元素相同的元素,避免重复解的出现
while (j < k && nums[k] == nums[k - 1])
k--; // 跳过与已经使用过的元素相同的元素,避免重复解的出现
j++;
k--;
}
}
}
return res; // 返回所有满足条件的解
}
}
其实这个方法跟上一个差不多,但是这个更好理解
算法的实现思路如下:
nums
进行排序。a
,从数组的起始位置开始遍历。
c
指针为数组最右端,初始化目标值 target
为 -nums[first]
。b
,从第一个数之后的位置开始遍历。
target
,其中左指针指向 second+1
,右指针指向 n-1
。target
,则将右指针向左移动,减小和的大小。target
,则将左指针向右移动,增大和的大小。target
,找到一组满足条件的解,保存到结果中,并同时移动左右指针。
a
,重复以上步骤。int n = nums.length;
Arrays.sort(nums);
List<List<Integer>> ans = new ArrayList<>();
// 枚举 a
for (int first = 0; first < n; ++first) {
// 需要和上一次枚举的数不相同
if (first > 0 && nums[first] == nums[first - 1]) {
continue;
}
// c 对应的指针初始指向数组的最右端
int third = n - 1;
int target = -nums[first];
// 枚举 b
for (int second = first + 1; second < n; ++second) {
// 需要和上一次枚举的数不相同
if (second > first + 1 && nums[second] == nums[second - 1]) {
continue;
}
// 需要保证 b 的指针在 c 的指针的左侧
while (second < third && nums[second] + nums[third] > target) {
--third;
}
// 如果指针重合,随着 b 后续的增加
// 就不会有满足 a+b+c=0 并且 b
if (second == third) {
break;
}
if (nums[second] + nums[third] == target) {
List<Integer> list = new ArrayList<>();
list.add(nums[first]);
list.add(nums[second]);
list.add(nums[third]);
ans.add(list);
}
}
}
return ans;
通过使用双指针法和排序算法,可以以较低的时间复杂度 O(n^2) 解决这个问题,其中 n 是数组的长度。这是因为排序算法的时间复杂度为 O(nlogn),而寻找满足条件的解的过程为双指针法,时间复杂度为 O(n)。