编程导航算法通关村第五关 | Hash和队列的经典算法

91 阅读5分钟

用栈实现队列

栈的特点是后进先出,队的特点是先进先出。两个栈将底部拼接到一起就能实现队列的效果, 通过队列也能实现栈的功能

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

这个题的思路是,将一个栈当作输入栈,用于压入 push 传入的数据;另一个栈当作输出栈,用于pop 和 peek 操作

每次pop 或 peek 时,若输出栈为空则将输入栈的全部数据依次弹出并压入输出栈,这样输出栈从栈顶往栈底的顺序就是队列从队首往队尾的顺序

代码如下:

class MyQueue {
    Deque<Integer> inStack;
    Deque<Integer> outStack;

    public MyQueue() {
        inStack = new LinkedList<>();
        outStack = new LinkedList<>();
    }

    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)官网 - 全球极客挚爱的技术成长平台

分析:

这个问题首先想到的是使用两个队列来实现。为了满足栈的特性,即最后入栈的元素最先出栈,在使用队列实现栈时,应满足队列前端的元素是最后入栈的元素。可以使用两个队列实现栈的操作,其中 queue1用于存储栈内的元素,queue2作为入栈操作的辅助队列。

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

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

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

代码如下:

class MyStack {

    Queue<Integer> queue1;
    Queue<Integer> queue2;

    public MyStack() {
        queue1 = new LinkedList<>();
        queue2 = new LinkedList<>();
    }

    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();
    }
}

n数之和

两数之和

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

本题可以使用两层循环解决,第一层确定一个数,2,7,一直到11,然后内层循环继续遍历后序元素,判断是否存在target - x的数即可

代码如下:

public int[] twoSum(int[] nums, int target) {
    int n = nums.length;
    for (int i = 0; i < n; ++i) {
        for (int j = i + 1; j < n; ++j) {
            if (nums[i] + nums[j] == target) {
                return new int[]{i, j};
            }
        }
    }
    return new int[0];
}

这种方式的不足在于寻找 target - x 的时间复杂度过高,我们可以使用哈希表,可以将寻找 target - x 的时间复杂度降低到从 O(N)降低到 O(1)。这样我们创建一个哈希表,对于每一个 x,我们首先查询哈希表中是否存在 target - x,然后将 x 插入到哈希表中,即可保证不会让 x 和自己匹配。

代码如下:

public int[] twoSum(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];
}

三数之和

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

本题看似就是两数增加了一个数,但是难度增加了很多,我们可以使用三层循环直接找,时间复杂度为O(n^3),太高了,放弃。也可以使用双层循环+Hash来实现,首先按照第一题两数之和的思路,我们可以固定一个数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]],如果我们再增加一个去重方法,将直接导致执行超时。

那这时候,我们就要想其他方法了,这个公认最好的方式是”排序+双指针“。我们可以先将数组排序来处理重复结果,然后还是固定一位元素,由于数组是排好序的,所以我们用双指针来不断寻找即可求解

代码如下:

public List<List<Integer>> threeSum(int[] nums) {
    int n = nums.length;
    Arrays.sort(nums);
    List<List<Integer>> res = new ArrayList<List<Integer>>();
    // 枚举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];
        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 < c的c了,可以退出循环
            if (second == third) {
                break;
            }
            if (nums[second] + nums[third] == target) {
                List<Integer> list = new ArrayList<Integer>();
                list.add(nums[first]);
                list.add(nums[second]);
                list.add(nums[third]);
                res.add(list);
            }
        }
    }
    return res;
}