LeetCode每日一题(持续更新)

3月1日

225. 用队列实现栈

使用队列实现栈的下列操作:

  • push(x) -- 元素 x 入栈
  • pop() -- 移除栈顶元素
  • top() -- 获取栈顶元素
  • empty() -- 返回栈是否为空

注意:
你只能使用队列的基本操作-- 也就是 push to back, peek/pop from front, size, 和 is empty 这些操作是合法的。
你所使用的语言也许不支持队列。 你可以使用 list 或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
你可以假设所有操作都是有效的(例如, 对一个空的栈不会调用 pop 或者 top 操作)。

        这道题,我的做法不是最简单的做法,但是是最容易理解的做法。

思路: 
一个队列加入元素,弹出元素时,需要把队列中的 元素放到另外一个队列中,弹出最后一个元素 
两个队列始终保持只有一个队列是有数据的

class MyStack {
    Queue<Integer> queueA;
    Queue<Integer> queueB;

    /** Initialize your data structure here. */
    public MyStack() {
        queueA = new LinkedList<>();
        queueB = new LinkedList<>();
    }
    
    /** Push element x onto stack. */
    public void push(int x) {
        if (!queueA.isEmpty()) {
            queueA.offer(x);
        } else {
            queueB.offer(x);
        }
    }
    
    /** Removes the element on top of the stack and returns that element. */
    public int pop() {
        if (!queueA.isEmpty()) {
            while (queueA.size() > 1) {
                queueB.offer(queueA.poll());
            }
            return queueA.poll();
        } else {
            while (queueB.size() > 1) {
                queueA.offer(queueB.poll());
            }
            return queueB.poll();
        }
    }
    
    /** Get the top element. */
    public int top() {
        int top = 0;
        if (!queueA.isEmpty()) {
            while (queueA.size() > 1) {
                queueB.offer(queueA.poll());
            }
            top = queueA.poll();
            queueB.offer(top);
        } else {
            while (queueB.size() > 1) {
                queueA.offer(queueB.poll());
            }
            top = queueB.poll();
            queueA.offer(top);
        }
        return top;
    }
    
    /** Returns whether the stack is empty. */
    public boolean empty() {
        return queueA.isEmpty() && queueB.isEmpty();
    }
}

3月2日

面试题24. 反转链表

反转一个单链表。

示例:

输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL

        经典题,不多BB。重点是对递归法的理解。

public class Solution {
    public ListNode reverseList(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        //这里的cur就是最后一个节点
        ListNode p = reverseList(head.next);
        //如果链表是 1->2->3->4->5,那么此时的cur就是5
		//而head是4,head的下一个是5,下下一个是空
		//所以head.next.next 就是5->4
        head.next.next = head;
        //断开当前连接
        head.next = null;
        //每层递归函数都返回cur,也就是最后一个节点
        return p;
    }
}

3月3日

面试题 10.01. 合并排序的数组

给定两个排序后的数组 A 和 B,其中 A 的末端有足够的缓冲空间容纳 B。 编写一个方法,将 B 合并入 A 并排序。

初始化 A 和 B 的元素数量分别为 m 和 n。

示例:

输入:
A = [1,2,3,0,0,0], m = 3
B = [2,5,6],       n = 3

输出: [1,2,2,3,5,6]
public class Solution {
    public void merge(int[] A, int m, int[] B, int n) {
        //方法1:利用Collections.sort进行排序(执行用时:3 ms)
        /*
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < m; i++) {
            list.add(A[i]);
        }
        for (int i = 0; i < n; i++) {
            list.add(B[i]);
        }
        Collections.sort(list);
        for (int i = 0; i < m + n; i++) {
            A[i] = list.get(i);
        }
         */
        //方法2:利用Arrays.sort进行排序(执行用时:1 ms)
        /*
        for (int i = 0; i < n; i++) {
            A[m + i] = B[i];
        }
        Arrays.sort(A);
         */
        //方法3:逆向双指针(执行用时:0 ms)
        int i = m - 1, j = n - 1, idx = m + n - 1;
        while (j >= 0) {
            if (i < 0 || B[j] >= A[i]) {
                A[idx--] = B[j--];
            } else {
                A[idx--] = A[i--];
            }
        }
    }
}

3月4日

994. 腐烂的橘子

在给定的网格中,每个单元格可以有以下三个值之一:

  • 值 0 代表空单元格;
  • 值 1 代表新鲜橘子;
  • 值 2 代表腐烂的橘子。

每分钟,任何与腐烂的橘子(在 4 个正方向上)相邻的新鲜橘子都会腐烂。

返回直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回 -1。

示例 1:
输入:[[2,1,1],[1,1,0],[0,1,1]]
输出:4

        经典的广度优先搜索问题,代码如下:

public class Solution {
    //分别对应 左、下、右、上 四个方向
    int[] dr = new int[]{-1, 0, 1, 0}; //行方向
    int[] dc = new int[]{0, -1, 0, 1}; //列方向

    public int orangesRotting(int[][] grid) {
        int R = grid.length, C = grid[0].length;

        // queue : all starting cells with rotten oranges
        Queue<Integer> queue = new ArrayDeque();
        Map<Integer, Integer> depth = new HashMap(); //key: 位置   value: 时间
        //初始化
        for (int r = 0; r < R; ++r)
            for (int c = 0; c < C; ++c)
                if (grid[r][c] == 2) {
                    int code = r * C + c;  //位置(index)
                    queue.add(code);
                    depth.put(code, 0);
                }

        int ans = 0;
        while (!queue.isEmpty()) {  //借助队列进行广度优先搜素
            int code = queue.remove();
            int r = code / C, c = code % C;
            for (int k = 0; k < 4; ++k) {  //遍历四个方向
                int nr = r + dr[k];
                int nc = c + dc[k];
                if (0 <= nr && nr < R && 0 <= nc && nc < C && grid[nr][nc] == 1) {
                    grid[nr][nc] = 2;
                    int ncode = nr * C + nc;
                    queue.add(ncode);
                    depth.put(ncode, depth.get(code) + 1);
                    ans = depth.get(ncode);
                }
            }
        }
        for (int[] row: grid) {
            for (int v: row) {
                if (v == 1) {
                    return -1;
                }

            }
        }
        return ans;
    }
}

3月5日

1103. 分糖果 II

排排坐,分糖果。

我们买了一些糖果 candies,打算把它们分给排好队的 n = num_people 个小朋友。

给第一个小朋友 1 颗糖果,第二个小朋友 2 颗,依此类推,直到给最后一个小朋友 n 颗糖果。

然后,我们再回到队伍的起点,给第一个小朋友 n + 1 颗糖果,第二个小朋友 n + 2 颗,依此类推,直到给最后一个小朋友 2 * n 颗糖果。

重复上述过程(每次都比上一次多给出一颗糖果,当到达队伍终点后再次从队伍起点开始),直到我们分完所有的糖果。注意,就算我们手中的剩下糖果数不够(不比前一次发出的糖果多),这些糖果也会全部发给当前的小朋友。

返回一个长度为 num_people、元素之和为 candies 的数组,以表示糖果的最终分发情况(即 ans[i] 表示第 i 个小朋友分到的糖果数)。

示例 1:

输入:candies = 7, num_people = 4
输出:[1,2,3,1]
解释:
第一次,ans[0] += 1,数组变为 [1,0,0,0]。
第二次,ans[1] += 2,数组变为 [1,2,0,0]。
第三次,ans[2] += 3,数组变为 [1,2,3,0]。
第四次,ans[3] += 1(因为此时只剩下 1 颗糖果),最终数组变为 [1,2,3,1]。
class Solution {
    public int[] distributeCandies(int candies, int num_people) {
        int[] res = new int[num_people];
        int index = 0;
        while (candies > 0) {
            for (int i = 0; i < num_people; i++) {
                int sum = index * num_people + (i + 1);
                if (candies > sum) {
                    res[i] += sum;
                    candies -= sum;
                } else {
                    res[i] += candies;
                    candies = 0;
                }
            }
            index++;
        }
        return res;
    }
}

3月6日

面试题57 - II. 和为s的连续正数序列

输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。

序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。

示例 1:

输入:target = 9
输出:[[2,3,4],[4,5]]

示例 2:

输入:target = 15
输出:[[1,2,3,4,5],[4,5,6],[7,8]]

        这道题可以通过暴力来解,但是最好的做法是采用滑动窗口的方式。 

        滑动窗口可以看成数组中框起来的一个部分。在一些数组类题目中,我们可以用滑动窗口来观察可能的候选结果。当滑动窗口从数组的左边滑到了右边,我们就可以从所有的候选结果中找到最优的结果。

sliding-window

  • 当窗口的和小于 target 的时候,窗口的和需要增加,所以要扩大窗口,窗口的右边界向右移动
  • 当窗口的和大于 target 的时候,窗口的和需要减少,所以要缩小窗口,窗口的左边界向右移动
  • 当窗口的和恰好等于 target 的时候,我们需要记录此时的结果。设此时的窗口为 [i, j)[i,j),那么我们已经找到了一个 ii 开头的序列,也是唯一一个 ii 开头的序列,接下来需要找 i+1i+1 开头的序列,所以窗口的左边界要向右移动
class Solution {
    public int[][] findContinuousSequence(int target) {
        int i = 1; // 滑动窗口的左边界
        int j = 1; // 滑动窗口的右边界
        int sum = 0; // 滑动窗口中数字的和
        List<int[]> res = new ArrayList<>();  //注意写法:目的是为了方便将List转换成二维数组

        while (i <= target / 2) {
            if (sum < target) {
                // 右边界向右移动
                sum += j;
                j++;
            } else if (sum > target) {
                // 左边界向右移动
                sum -= i;
                i++;
            } else {
                // 记录结果
                int[] arr = new int[j-i];
                for (int k = i; k < j; k++) {
                    arr[k-i] = k;
                }
                res.add(arr);
                // 左边界向右移动
                sum -= i;
                i++;
            }
        }

        return res.toArray(new int[res.size()][]);
    }
}

3月7日

面试题59 - II. 队列的最大值

请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_value、push_back 和 pop_front 的时间复杂度都是O(1)。

若队列为空,pop_front 和 max_value 需要返回 -1

示例 1:

输入: 
["MaxQueue","push_back","push_back","max_value","pop_front","max_value"]
[[],[1],[2],[],[],[]]
输出: [null,null,null,2,1,2]

        这道题主要采用 队列 + 双端队列 的方式。

class MaxQueue {
    Queue<Integer> queue;
    Deque<Integer> maxQueue;  //双端队列(也需要存全部元素,只不过是单调递减的)
    public MaxQueue() {
        queue = new ArrayDeque();
        maxQueue = new ArrayDeque();
    }
    public int max_value() {
        return maxQueue.isEmpty() ? -1 : maxQueue.peek();
    }
    public void push_back(int value) {
        queue.add(value);
        while(!maxQueue.isEmpty() && value > maxQueue.getLast()) {
            maxQueue.pollLast();  //从尾部把小于当前value的全部删除(因为用不到了)
        }
        maxQueue.add(value);
    }
    public int pop_front() {
        if(queue.isEmpty())
            return -1;
        int ans = queue.poll();
        if(ans == maxQueue.peek()) {
            maxQueue.poll();
        } 
        return ans;
    }
}

3月8日 

322. 零钱兑换

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。

示例 1:

输入: coins = [1, 2, 5], amount = 11
输出: 3 
解释: 11 = 5 + 5 + 1

        这道题是典型的的DP问题,当然也可以使用DFS求解。 

public class Solution {
    //动态规划
    public int coinChange(int[] coins, int amount) {
        int dp[] = new int[amount + 1];
        Arrays.fill(dp, amount + 1);
        dp[0] = 0;

        //反向dp
        for (int i = 1; i <= amount; i++) {
            for (int j = 0; j < coins.length; j++) {
                if (coins[j] <= i) {
                    dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);
                }
            }
        }
        return dp[amount] > amount ? -1 : dp[amount];
    }

    //DFS + 剪枝
    int res = Integer.MAX_VALUE;
    public int coinChange(int[] coins, int amount) {
        Arrays.sort(coins);
        dfs(coins, coins.length - 1, amount, 0);  //用最大的面额作为初始条件
        return res == Integer.MAX_VALUE ? -1 : res;
    }
    public void dfs(int[] coins, int index, int amount, int cnt){
        if(index < 0){
            return;
        }
        for(int c = amount/coins[index]; c >= 0; c--){
            int na = amount - c * coins[index];  //剩下的面额
            int ncnt = cnt + c;  //目前使用的硬币数
            if(na == 0){  //剩余的面额为0(符合)
                res = Math.min(res, ncnt);
                break; //剪枝1
            }
            if(ncnt + 1 >= res){  //使用的硬币数过多,已无法满足最优的要求(舍弃)
                break; //剪枝2
            }
            dfs(coins, index-1, na, ncnt);
        }
    }
}

3月9日

121. 买卖股票的最佳时机

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。

注意你不能在买入股票前卖出股票。

示例 1:

输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意:利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。

         这道题较为暴力的方式是使用两重循环,其实可以使用一重循环进行改进,代码如下:

public class Solution {
    public int maxProfit(int[] prices) {
        int min = Integer.MAX_VALUE;
        int maxProfit = 0;
        for (int i = 0; i < prices.length; i++) {
            if (prices[i] < min) {
                min = prices[i];
            } else if (prices[i] - min > maxProfit) {
                maxProfit = prices[i] - min;
            }
        }
        return maxProfit;
    }
}

3月10日

543. 二叉树的直径

给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过根结点。

示例 :
给定二叉树

          1
         / \
        2   3
       / \     
      4   5    

屏幕快照 2020-03-10 00.36.38.png

        这道题有个坑。。注意:最大值不一定包含根节点。但是一定是经过某个节点,可以使用 DFS

class Solution {
    int res = 0;
    public int diameterOfBinaryTree(TreeNode root) {
        dfs(root);
        return res;
    }

    // 函数dfs的作用:找到以root为根节点的二叉树的最大深度
    private int dfs(TreeNode root){
        if(root == null){
            return 0;
        }
        int leftDepth = dfs(root.left);
        int rightDepth = dfs(root.right);
        res = Math.max(res, leftDepth + rightDepth);
        return Math.max(leftDepth, rightDepth) + 1;
    }
}

 

3月14日

300. 最长上升子序列

给定一个无序的整数数组,找到其中最长上升子序列的长度。

示例:

输入: [10,9,2,5,3,7,101,18]
输出: 4 
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
public class Solution {
    //动态规划法
    public int lengthOfLIS(int[] nums) {
        if (nums.length == 0) {
            return 0;
        }
        int[] dp = new int[nums.length];
        //dp[j]代表nums [0…j] 中以 nums[j] 结尾的最长上升子序列的长度
        dp[0] = 1;
        int maxans = 1;
        for (int i = 1; i < dp.length; i++) {
            int maxval = 0;
            for (int j = 0; j < i; j++) {
                if (nums[i] > nums[j]) {
                    maxval = Math.max(maxval, dp[j]);
                }
            }
            dp[i] = maxval + 1;
            maxans = Math.max(maxans, dp[i]);
        }
        return maxans;
    }

    //贪心 + 二分查找
    public int lengthOfLIS1(int[] nums) {
        /**
         由定义知dp数组必然是一个递增数组, 可以用 maxL 来表示最长递增子序列的长度.
         对数组进行迭代, 依次判断每个数num将其插入dp数组相应的位置:
         1. num > dp[maxL], 表示num比所有已知递增序列的尾数都大, 将num添加入dp
         数组尾部, 并将最长递增序列长度maxL加1
         2. dp[i-1] < num <= dp[i], 只更新相应的dp[i]
         **/
        int maxL = 0;  //最长递增子序列的长度
        int[] dp = new int[nums.length];
        //dp[i]: 所有长度为i+1的递增子序列中, 最小的那个序列尾数
        for(int num : nums) {
            // 二分法查找, 也可以调用库函数如binary_search
            int lo = 0, hi = maxL;
            while(lo < hi) {
                int mid = lo + (hi - lo) / 2;
                if(dp[mid] < num) {
                    lo = mid+1;
                } else {
                    hi = mid;
                }
            }
            dp[lo] = num;
            if(lo == maxL) {
                maxL++;
            }
        }
        return maxL;
    }
}

持续更新中……

猜你喜欢

转载自blog.csdn.net/qq_34519487/article/details/104642141