Leetcode Weekly 187 解题报告

Leetcode 1436. Destination City
Leetcode 1437. Check If All 1’s Are at Least Length K Places Away
Leetcode 1438. Longest Continuous Subarray With Absolute Diff Less Than or Equal to Limit
Leetcode 1439. Find the Kth Smallest Sum of a Matrix With Sorted Rows
\newline
\newline

Leetcode 1436. Destination City

统计out degree为零的点即可

class Solution {
    public String destCity(List<List<String>> paths) {
        Map<String, Integer> map = new HashMap<>();
        for (List<String> edge : paths) {
            map.put(edge.get(0), 1 + map.getOrDefault(edge.get(0), 0));
            map.put(edge.get(1), 0 + map.getOrDefault(edge.get(1), 0));
        }
        for (String key : map.keySet()) {
            if (map.get(key) == 0) {
                return key;
            }
        }
        return "";
    }
}

T i m e : O ( V + E ) Time: O(V + E) Time:O(V+E)
S p a c e : O ( V + E ) Space:O(V + E) Space:O(V+E)

\newline
\newline

Leetcode 1437. Check If All 1’s Are at Least Length K Places Away

记录上一个1的位置,当前只要遍历到1就和上一个1的位置计算一下距离,如果太小就返回false。

class Solution {
    public boolean kLengthApart(int[] nums, int k) {
        int p = -1;
        for (int i = 0; i < nums.length; ++i) {
            if (nums[i] == 0) {
                continue;
            }
            if (p != -1 && i - p - 1 < k) {
                return false;
            }
            p = i;
        }
        return true;
    }
}

T i m e : O ( N ) Time: O(N) Time:O(N)
S p a c e : O ( 1 ) Space:O(1) Space:O(1)
\newline
\newline

Leetcode 1438. Longest Continuous Subarray With Absolute Diff Less Than or Equal to Limit

这种有关连续的子数组的最值问题,通常可以考虑一下滑动窗口的思路。但是要注意,不是所有的类似问题都可以用滑动数组,能不能用滑动数组,有个很关键的考量就是:如果滑动窗口当前不满足条件了,那么是不是只能通过缩小窗口,即窗口左边界右移来使窗口满足条件。 如果当前窗口不满足条件,但是通过窗口扩大或者缩小都可能使得窗口满足条件,这类问题就不能使用滑动窗口思路。

看看这一题,如果我们维护一个窗口,窗口右边界不停右移。当窗口不满足条件时,即 m a x − m i n > l i m i t max - min > limit maxmin>limit 了, 那么想要让窗口重新满足条件,只能缩小窗口。因为如果向左边扩大窗口,只可能会使最大值更大,最小值更小。所以,唯一的办法就是左边界右移,缩小窗口。这样的话,我们就可以使用滑动窗口思路。窗口中需要动态维护最大最小值,可以考虑用一个递减队列来维护最大值,用一个递增队列来维护最小值。

还有一些问题是表面上看上去可以用滑动窗口,但实际上不能使用的,比如:

1124. Longest Well-Performing Interval

这一题看上去可以维护一个滑动窗口,使得窗口里面的 tiring days > non-tiring days,但是要注意:当右边界右移的时候,如果某一时候窗口不满足条件了,即 tiring days <= non-tiring days,那么我们可以通过缩小窗口来减少 non-tiring days,也可以通过扩大窗口来增加 tiring days 所以这一题就不能使用滑动窗口思路。

class Solution {
    public int longestSubarray(int[] nums, int limit) {
        LinkedList<Integer> maxList = new LinkedList<>();
        LinkedList<Integer> minList = new LinkedList<>();
        int p = -1;
        int ans = 0;
        for (int i = 0; i < nums.length; ++i) {
            while (!maxList.isEmpty() && maxList.peekLast() < nums[i]) {
                maxList.pollLast();
            }
            maxList.addLast(nums[i]);
            while (!minList.isEmpty() && minList.peekLast() > nums[i]) {
                minList.pollLast();
            }
            minList.addLast(nums[i]);
            while (maxList.peekFirst() - minList.peekFirst() > limit) {
                int val = nums[++p];
                if (val == maxList.peekFirst()) {
                    maxList.pollFirst();
                }
                if (val == minList.peekFirst()) {
                    minList.pollFirst();
                }
            }
            ans = Math.max(ans, i - p);
        }
        return ans;
    }
}

T i m e : O ( N ) Time: O(N) Time:O(N)
S p a c e : O ( N ) Space:O(N) Space:O(N)
\newline
\newline

Leetcode 1439. Find the Kth Smallest Sum of a Matrix With Sorted Rows

本题有很多可选的思路,是道很不错的题目。
\newline
\newline

思路一

当我们求关于多个数组的问题时,一种可以尝试的思路就是,看看能不能先从两个数组开始,求得的结果和第三个数组合并,再和第四个数组合并,以此类推下去。

比如这一题,我们要求 m m m 个数组形成的第 k k k 个和,可以先把第一个数组的前 k k k 个数,和第二个数组的前 k k k 个数两两相加,然后取前 k k k 个和。再和第三个数组的前 k k k 个数两两相加,再取前 k k k 个和, 然后和第四个数组做重复操作。

class Solution {
    public int kthSmallest(int[][] mat, int k) {
        List<Integer> cur = new ArrayList<>();
        int n = Math.min(mat[0].length, k);
        for (int i = 0; i < n; ++i) {
            cur.add(mat[0][i]);
        }
        for (int i = 1; i < mat.length; ++i) {
            List<Integer> nxt = new ArrayList<>();
            for (int j = 0; j < n; ++j) {
                for (int num : cur) {
                    nxt.add(mat[i][j] + num);
                }
            }
            Collections.sort(nxt);
            while (nxt.size() > k) {
                nxt.remove(nxt.size() - 1);
            }
            cur = nxt;
        }
        return cur.get(k - 1);
    }
}

T i m e : O ( m n k L o g n k ) Time: O(mnkLognk) Time:O(mnkLognk)
S p a c e : O ( n k ) Space:O(nk) Space:O(nk)
\newline
\newline

思路二

求第 k k k 大的数的问题,我们通常也可以考虑一下二分搜索的思路, 对于一个可选的 m i d mid mid 数值,我们统计一下有多少个数小于等于这个 m i d mid mid, 有下面三种情况,记小于等于 m i d mid mid 的数字个数为 c n t cnt cnt

  1. c n t < K cnt < K cnt<K,那么这个 m i d mid mid 太小了, l = m i d + 1 l = mid + 1 l=mid+1
  2. c n t = = K cnt == K cnt==K,那么说明这个 m i d mid mid 就是第 K K K 大个数。
  3. c n t > K cnt > K cnt>K,是否说明 m i d mid mid 太大了,应该 r = m i d − 1 r = mid - 1 r=mid1 呢?其实不是,因为可能有很多重复的 m i d mid mid 数值,使得小于等于 m i d mid mid 的数的个数大于 K K K。所以我们应该有 r = m i d r = mid r=mid

一个很重要的注意点是, m i d mid mid 的值必须是在 [ m i n S u m , m a x S u m ] [minSum, maxSum] [minSum,maxSum] 当中,例如数组 [ 1 , 2 , 3 , 4 , 5 ] [1, 2, 3, 4, 5] [1,2,3,4,5] 中第5大的数, m i d = 5 mid = 5 mid=5 m i d = 100 mid = 100 mid=100 都会得到5个小于等于他们的数,但是显然 m i d = 100 mid = 100 mid=100 是不对的。

下面就是思考如何统计小于等于 m i d mid mid 的数的个数。其实就是用一个深度优先搜索。但是你可能会想,搜索空间多达 n m n^{m}_{} nm个,太大了。其实不然,因为我们只要比较 c n t cnt cnt K K K 的关系,当我们知道 c n t ≥ K cnt \ge K cntK以后,应当立马剪枝,所以我们最多搜索 K K K 次。

另一个需要注意的细节就是:在已知最小的值是每个数组的第一个元素的情况下,我们在 DFS 统计有多少个数小于等于 m i d mid mid 的时候,可以把搜索值减少为 m i d − l mid - l midl,这里 l l l 是最小的和。然后在选择每一行数组的元素的时候,我们的每一个选择就是 m a t [ i d x ] [ i ] − m a t [ i d x ] [ 0 ] mat[idx][i] - mat[idx][0] mat[idx][i]mat[idx][0] 而不是 m a t [ i d x ] [ i ] mat[idx][i] mat[idx][i]。这么做的好处是:如果我们把搜索值定为 m i d mid mid,并且每次选择 m a t [ i d x ] [ i ] mat[idx][i] mat[idx][i],我们可能会搜索一些 m a t [ i d x ] [ i ] mat[idx][i] mat[idx][i] 很大的情况,导致往后有一些行没办法选,因为哪怕选第一个最小的元素也会使总和比 m i d mid mid 大,这些情况是显然没必要搜索的。

class Solution {
    private int[][] mat;
    private int m;
    private int n;
    public int kthSmallest(int[][] mat, int k) {
        this.mat = mat;
        m = mat.length;
        n = mat[0].length;
        int l = 0;
        int r = 0;
        for (int[] arr : mat) {
            l += arr[0];
            r += arr[n - 1];
        }
        int init = l;
        l--;
        while (r - l > 1) {
            int mid = l + (r - l) / 2;
            int cnt = dfs(0, mid - init, k);
            if (cnt < k) {
                l = mid;
            } else {
                r = mid;
            }
        }
        return r;
    }
    
    private int dfs(int idx, int target, int k) {
        if (idx == m) {
            return 1;
        }
        int cnt = 0;
        for (int i = 0; i < n && target >= mat[idx][i] - mat[idx][0]; ++i) {
            cnt += dfs(idx + 1, target - mat[idx][i] + mat[idx][0], k);
            // This trimming is pretty important.
            if (cnt >= k) {
                return cnt;
            }
        }
        return cnt;
    }
}

T i m e : O ( k L o g ( m a x S u m − m i n S u m ) ) Time: O(kLog(maxSum - minSum)) Time:O(kLog(maxSumminSum))
S p a c e : O ( m ) Space:O(m) Space:O(m)
\newline
\newline

思路三

思路和下面一题基本一样

264. Ugly Number II

每一个可能的数组状态和都是由前面某个数组状态演变而来。每一个数组状态,可以衍生出 m m m 个新状态,即把 m m m 个数组的 p o i n t e r pointer pointer 分别向后移动一位。

第一个状态也即第一大的数是 m m m 个数组的 p o i n t e r pointer pointer 都在 0 0 0, 可以衍生出 m 个状态,那么第二大的数就是这 m m m 个状态的最小值。然后那个最小值的状态再衍生出 m m m 个新状态,这 m m m 个新状态和之前除最小值之外的 m − 1 m - 1 m1 个状态之中的最小值就是第三大的数,以此类推。

我们可以使用一个最小堆 ( M i n i m u m   H e a p ) (Minimum \space Heap) (Minimum Heap) 来处理。需要注意的细节是:对于每一个状态我们需要筛除重复的状态,需要用一个 S e t Set Set

class Solution {
    private class Node implements Comparable<Node> {
        int sum;
        int[] p;
        
        /**
         * int[] p records the pointers position of m arrays of a specific state.
         */
        Node(int sum, int[] p) {
            this.sum = sum;
            this.p = p;
        }
        
        public int compareTo(Node node) {
            return sum - node.sum;
        }
        
        public String getCode() {
            StringBuilder sb = new StringBuilder();
            for (int a : p) {
                sb.append(a);
                sb.append('#');
            }
            return sb.toString();
        }
    }
    
    public int kthSmallest(int[][] mat, int k) {
        PriorityQueue<Node> pq = new PriorityQueue<>();
        Set<String> set = new HashSet<>();
        int sum = 0;
        for (int[] arr : mat) {
            sum += arr[0];
        }
        Node initState = new Node(sum, new int[mat.length]);
        pq.add(initState);
        set.add(initState.getCode());
        for (int t = 0; t < k - 1; ++t) {
            Node node = pq.poll();
            for (int i = 0; i < mat.length; ++i) {
                int[] p = Arrays.copyOf(node.p, mat.length);
                if (p[i] < mat[0].length - 1) {
                    p[i]++;
                    Node nxt = new Node(node.sum + mat[i][p[i]]- mat[i][p[i] - 1], p);
                    String code = nxt.getCode();
                    if (!set.contains(code)) {
                        set.add(code);
                        pq.add(nxt);
                    }
                }
            }
        }
        return pq.poll().sum;
    }
}

T i m e : O ( k m L o g k m ) Time: O(kmLogkm) Time:O(kmLogkm)
S p a c e : O ( k m 2 ) Space:O(km^{2}_{}) Space:O(km2)
\newline
\newline

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

aliengod

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值