队栈和hash的经典算法题(算法村第五关白银挑战)

用栈实现队列

232. 用栈实现队列 - 力扣(LeetCode)

请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(pushpoppeekempty):

实现 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
  • 最多调用 100pushpoppeekempty
  • 假设所有操作都是有效的 (例如,一个空的队列不会调用 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)。

用队列实现栈

225. 用队列实现栈 - 力扣(LeetCode)

请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(pushtoppopempty)。

实现 MyStack 类:

  • void push(int x) 将元素 x 压入栈顶。
  • int pop() 移除并返回栈顶元素。
  • int top() 返回栈顶元素。
  • boolean empty() 如果栈是空的,返回 true ;否则,返回 false

注意:

  • 你只能使用队列的基本操作 —— 也就是 push to backpeek/pop from frontsizeis 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
  • 最多调用100pushpoptopempty
  • 每次调用 poptop 都保证栈不为空

**进阶:**你能否仅用一个队列来实现栈。

一个队列

可以把队列想成一个环,在队尾加入新元素后,之前已存在的元素沿着环回到了新元素的后面,使新元素变成了队头,而已存在元素的相对位置不会发生改变(队列输出不改变元素顺序)。从一开始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数之和专题

两数之和

1. 两数之和 - 力扣(LeetCode)

给定一个整数数组 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
  1. key为数组元素,value存储数组下标
  2. 问题转化:寻找值为 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};	//没有找到符合要求的两个元素
}

三数之和

15. 三数之和 - 力扣(LeetCode)

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != kj != 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)

四数之和 Ⅱ

454. 四数相加 II - 力扣(LeetCode)

给你四个整数数组 nums1nums2nums3nums4 ,数组长度都是 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;
}
时间复杂度分析
  1. HashMap 存一个数组,如 A。然后计算三个数组之和,如 BCD。时间复杂度为:O(n)+O(n^3),得到 O(n^3).
  2. HashMap 存三个数组之和,如 ABC。然后计算一个数组,如 D。时间复杂度为:O(n^3)+O(n),得到 O(n^3).
  3. HashMap存两个数组之和,如AB。然后计算两个数组之和,如 CD。时间复杂度为:O(n2)+O(n2),得到 O(n^2).

本题采用第三种方式,时间复杂度为O(n^2).

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值