文章目录
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 max−min>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。
- c n t < K cnt < K cnt<K,那么这个 m i d mid mid 太小了, l = m i d + 1 l = mid + 1 l=mid+1。
- c n t = = K cnt == K cnt==K,那么说明这个 m i d mid mid 就是第 K K K 大个数。
- c n t > K cnt > K cnt>K,是否说明 m i d mid mid 太大了,应该 r = m i d − 1 r = mid - 1 r=mid−1 呢?其实不是,因为可能有很多重复的 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 cnt≥K以后,应当立马剪枝,所以我们最多搜索 K K K 次。
另一个需要注意的细节就是:在已知最小的值是每个数组的第一个元素的情况下,我们在 DFS 统计有多少个数小于等于 m i d mid mid 的时候,可以把搜索值减少为 m i d − l mid - l mid−l,这里 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(maxSum−minSum))
S
p
a
c
e
:
O
(
m
)
Space:O(m)
Space:O(m)
\newline
\newline
思路三
思路和下面一题基本一样
每一个可能的数组状态和都是由前面某个数组状态演变而来。每一个数组状态,可以衍生出 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 m−1 个状态之中的最小值就是第三大的数,以此类推。
我们可以使用一个最小堆 ( 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