KK's blog

每天积累多一些

0%

LeetCode 658 Find K Closest Elements

Given a sorted array, two integers k and x, find the k closest elements to x in the array. The result should also be sorted in ascending order. If there is a tie, the smaller elements are always preferred.

Example 1:

Input: [1,2,3,4,5], k=4, x=3
Output: [1,2,3,4]

Example 2:

Input: [1,2,3,4,5], k=4, x=-1
Output: [1,2,3,4]

Note:

  1. The value k is positive and will always be smaller than the length of the sorted array.
  2. Length of the given array is positive and will not exceed 104
  3. Absolute value of elements in the array and x will not exceed 104

题目大意:

给定一个排序数组,两个整数k和x,求数组中距离x最近的k个数字。结果应该有序,距离相同时优先选择较小的数字。

解题思路:

  1. 进阶二分法找出第一个大于等于key值的元素。
  2. 左右两指针搜索k个元素。

注意事项:

  1. 指针不能越界
  2. 根据题意,与key距离一样时,取较小元素。

Java代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public List<Integer> findClosestElements(int[] arr, int k, int x) {
int upperIdx = firstEqualOrGreater(arr, x);
int lowerIdx = upperIdx-1;
List result = new ArrayList();
for(int i=k;i>0;i--){
if(lowerIdx<0)
result.add(arr[upperIdx++]);
else if(upperIdx>=arr.length)
result.add(arr[lowerIdx--]);
else if(x-arr[lowerIdx]<=arr[upperIdx]-x)
result.add(arr[lowerIdx--]);
else
result.add(arr[upperIdx++]);

}
Collections.sort(result);
return result;
}

public int firstEqualOrGreater(int[] a, int key){
int lo = 0, hi = a.length-1;
while(lo<=hi){
int mid = lo + (hi - lo) / 2;
if(a[mid]<key)
lo = mid+1;
else
hi = mid-1;
}
return lo;
}

算法分析:

时间复杂度为O(logn+k),空间复杂度O(1)

LeetCode 698 Partition to K Equal Sum Subsets

Given an array of integers nums and a positive integer k, find whether it’s possible to divide this array into k non-empty subsets whose sums are all equal.

Example 1:

Input: nums = [4, 3, 2, 3, 5, 2, 1], k = 4
Output: True
Explanation: It's possible to divide it into 4 subsets (5), (1, 4), (2,3), (2,3) with equal sums.

Note:

  • 1 <= k <= len(nums) <= 16.
  • 0 < nums[i] < 10000.

题目大意:

判断数组nums是否可以划分为k个和相等的子数组

解题思路:

这题与416类似,所以一开始考虑用0-1背包问题思路,但是0-1背包问题得出的解为2,2,1,与答案不同。因为背包问题只能求出第一个解,并不能求出k个解。所以类似于排列组合,
需要DFS来一个个数来试。参数为visited数组为记录该数是否用了,curSum,k,若curSum等于target(sum/k),找到第一个解,k–,curSum=0,找下一个解。这个方法是用k次排列组合法组成最终解。

注意事项:

  1. 数组和为不能被k整除,无解
  2. 引入st,排列组合必须,用于for循环的起始点。
  3. k=1立刻剪枝,因为前三个解都是等于sum/k,最后一个也一定是sum/k
  4. 当curSum==target时,进行k-1,curSum=0的下一轮dfs。

Java代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public boolean canPartitionKSubsets0(int[] nums, int k) {
int sum = 0;
for(int i : nums)
sum+=i;
if(sum%k!=0)
return false;
boolean[] visited = new boolean[nums.length];
return dfs0(nums, k, sum/k, 0, visited, 0);
}

public boolean dfs0(int[] nums, int k, int target, int curSum, boolean[] visited, int start){
if(k==1)
return true;
if(curSum==target)
return dfs0(nums, k-1, target, 0, visited, 0);

for(int i=start;i<nums.length;i++){
if(visited[i])
continue;
if(curSum+nums[i]<=target){
visited[i] = true;
if(dfs0(nums, k, target, curSum+nums[i], visited, i+1))
return true;
visited[i] = false;
}
}
return false;
}

算法分析:

这是NP问题。


另一个方法是,将k次排列组合法整合成一次,途径是开一个k大小数组,每一个数肯定属于其中一个。visited数组替换成ksum数组和idx控制遍历数组顺序。某一个数肯定是属于ksum数组的任一个,
所以所有可能性都考虑到,可以求得解。先对原数组排序方便从后往前遍历,贪心算法可以帮助剪枝,因为先填大的数,容易获得结果或排除结果。如[1….1, 4], target=4, k=2,从前往后的话,
多个1可以有非常多的可能。提高算法效率但若不排序的话会得到LTE。
这个方法比第一个方法的优势在于它用ksum数组取代visited和curSum参数,第一种方法要从头开始扫k遍数组,而此法只需扫一遍数组+每个元素试k次。

  1. 数组和为不能被k整除,无解
  2. 对数组排序
  3. DFS四部曲,从后往前遍历数组加入ksum

注意事项:

  1. 数组和为不能被k整除,无解
  2. 从后往前遍历数组

Java代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public boolean canPartitionKSubsets(int[] nums, int k) {
int sum = 0;
for(int i : nums)
sum+=i;
if(sum%k!=0)
return false;
Arrays.sort(nums);
int[] ksum = new int[k];
return dfs(nums, k, sum/k, ksum, nums.length-1);
}

public boolean dfs(int[] nums, int k, int target, int[] ksum, int idx){
if(idx==-1){
for(int a : ksum)
if(a!=target)
return false;
return true;
}
for(int i=0; i<k; i++){
if(ksum[i]+nums[idx]<=target){
ksum[i] += nums[idx];
if(dfs(nums, k, target, ksum, idx-1))
return true;
ksum[i] -= nums[idx];
}

}
return false;
}

算法分析:

这是NP问题。

LeetCode 416 Partition Equal Subset Sum

Given a non-empty array containing only positive integers, find if the array can be partitioned into two subsets such that the sum of elements in both subsets is equal.

Note:

  1. Each of the array element will not exceed 100.
  2. The array size will not exceed 200.

Example 1:

Input: [1, 5, 11, 5]

Output: true

Explanation: The array can be partitioned as [1, 5, 5] and [11].

Example 2:

Input: [1, 2, 3, 5]

Output: false

Explanation: The array cannot be partitioned into equal sum subsets.

题目大意:

给定一个只包含正整数的非空数组,判断数组是否可以分成两个和相等的子数组。

解题思路:

这题转化为求是否有子序列的和等于数组和的一半,这就是0-1背包问题。价值和重量是一样数组的背包问题。背包问题递归式:
f[j] = Math.max(f[j], f[j-w[i]]+v[i]);
背包问题最后的解为容量为C的背包能装的最大价值,也就是在这题中,容量为数组一半和的背包能装的最大价值是否为数组一半。如果能,即有解。
表示存在前i个数它的和(最大价值)等于和的一半。
举例说明,容量一半表示刚好可以凑到几个数和为一半,如[1,5,11,5],容量为11,就可以凑到[1,5,5]重量和价值均为一半。
如[1,2,3,10]和为16,容量为8,只能找到[1,2,3]价值为6达不到一半。所以这里C是限制条件并不是一定要达到。但刚好达到即为解。

注意事项:

  1. 数组和为奇数,无解
  2. 背包问题的解若为数组一半的和即有解
  3. 背包问题解法C向前滚

Java代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public boolean canPartition(int[] nums) {
int sum = 0;
for(int i=0;i<nums.length;i++)
sum+=nums[i];
if(sum%2==1)
return false;
int[] result = knapsack(nums,nums,sum/2);
if(sum/2==result[result.length-1])
return true;
return false;
}

public int[] knapsack(int v[], int w[], int C){
int n = v.length;
//int[][] re = new int[n][C+1];
int[] f = new int[C+1];

for(int i=0;i<n;i++){
for(int j=C;j>=w[i];j--){
f[j] = Math.max(f[j], f[j-w[i]]+v[i]);
//if(f[j]==f[j-w[i]]+v[i])
// re[i][j] = 1;
}
}
return f;
}

算法分析:

时间复杂度为O(nC),空间复杂度O(C)。C为数组和的一半,n为数组个数。

LeetCode 329 Longest Increasing Path in a Matrix

Given an integer matrix, find the length of the longest increasing path.

From each cell, you can either move to four directions: left, right, up or down. You may NOT move diagonally or move outside of the boundary (i.e. wrap-around is not allowed).

Example 1:

nums = [
  [9,9,4],
  [6,6,8],
  [2,1,1]
]

Return 4
The longest increasing path is [1, 2, 6, 9].

Example 2:

nums = [
  [3,4,5],
  [3,2,6],
  [2,2,1]
]

Return 4
The longest increasing path is [3, 4, 5, 6]. Moving diagonally is not allowed.

题目大意:

给定一个整数矩阵,计算其最长递增路径的长度。
从每一个单元格出发,你可以向四个方向移动:左右上下。你不可以沿着对角线移动也不能移出边界。(亦即,环绕是不允许的)。

Floyd解题思路:

这是经典题,类似于LIS题,只要将以每个点为终点的最长路径长度存起来,就可以类推它的邻近点的最长长度(DP)。f(x,y)=max{f(x,y),4个邻近点的f+1}
由于这是求最长路径题且为矩阵,可以考虑按步长计算,就是Floyd的思路,就是先计算步长为1,2一直到所以最长路径长度矩阵长度不再更新为止。
另一个思路也是用矩阵存起每个点最长路径长度,但用DFS搜索,直至这个点的值不为初始值为止,详见书影博客。

注意事项:

  1. 矩阵为空或长度为0
  2. 当任何值没有更新时,Floyd停止计算

Java代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public int longestIncreasingPath(int[][] matrix) {
if (matrix==null || matrix.length==0)
return 0;
int[][] path = new int[matrix.length][matrix[0].length];
for(int i=0;i<matrix.length;i++)
for(int j=0;j<matrix[0].length;j++)
path[i][j] = 1;

boolean nextPath = true;
while(nextPath){
nextPath = false;
for(int i=0;i<matrix.length;i++)
for(int j=0;j<matrix[0].length;j++){
if(changeCell(matrix, path, i, j))
nextPath = true;
}
}

int max = 1;
for(int i=0;i<matrix.length;i++)
for(int j=0;j<matrix[0].length;j++)
if(path[i][j]>max)
max = path[i][j];
return max;
}

public boolean changeCell(int[][] matrix, int[][] path, int i, int j){
int pre = path[i][j];

if(i-1>=0 && matrix[i-1][j]<matrix[i][j] && path[i-1][j]+1>path[i][j])
path[i][j] = path[i-1][j]+1;

if(i+1<matrix.length && matrix[i+1][j]<matrix[i][j] && path[i+1][j]+1>path[i][j])
path[i][j] = path[i+1][j]+1;

if(j-1>=0 && matrix[i][j-1]<matrix[i][j] && path[i][j-1]+1>path[i][j])
path[i][j] = path[i][j-1]+1;

if(j+1<matrix[0].length && matrix[i][j+1]<matrix[i][j] && path[i][j+1]+1>path[i][j])
path[i][j] = path[i][j+1]+1;

if(pre!=path[i][j])
return true;
else return false;
}

算法分析:

k为最长路径长度,时间复杂度为O(kn2),空间复杂度O(n2)


DP算法II解题思路(推荐):

求最值,考虑用DP,但DP的应用条件为有序,所以不妨将所有值排序。
先对所有数的值排序作为新的输入数组,按这个顺序,计算DP,每个点的往前4个方向的DP值+1。
如题第一个例子, 元祖里第一个为值,后两个为xy坐标
[(1, 2, 1), (1, 2, 2)…]
递归公式:

1
dp[x][y] = dp[x + dx][y + dy] + 1, ifmatrix[x][y] > matrix[x + dx][y + dy]:

最后计算max(dp)

注意事项:

  1. 当递增时,才更新DP,Line 13

Python代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def longestIncreasingPath(self, matrix: List[List[int]]) -> int:
ary = []
for i in range(len(matrix)):
for j in range(len(matrix[0])):
ary.append((matrix[i][j], i, j))
ary.sort()
dp = [[1 for _ in range(len(matrix[0]))] for _ in range(len(matrix))]
for val, _x, _y in ary:
for _dx, _dy in OFFSETS:
x, y = _x + _dx, _y + _dy
if x < 0 or x >= len(matrix) or y < 0 or y >= len(matrix[0]):
continue
if matrix[x][y] > matrix[_x][_y]:
dp[x][y] = max(dp[x][y], dp[_x][_y] + 1)
return max(map(max, dp))

算法分析:

排序是n^2logn^2 = n^2logn, dp计算是n^2
所以时间复杂度为O(n2logn),空间复杂度O(n2)


算法III解题思路:

这题也可以额用记忆性搜索DFS来解。

LeetCode 332 Reconstruct Itinerary

Given a list of airline tickets represented by pairs of departure and arrival airports [from, to], reconstruct the itinerary in order. All of the tickets belong to a man who departs from JFK. Thus, the itinerary must begin with JFK.

Note:

  1. If there are multiple valid itineraries, you should return the itinerary that has the smallest lexical order when read as a single string. For example, the itinerary ["JFK", "LGA"] has a smaller lexical order than ["JFK", "LGB"].
  2. All airports are represented by three capital letters (IATA code).
  3. You may assume all tickets form at least one valid itinerary.

Example 1:
tickets = [["MUC", "LHR"], ["JFK", "MUC"], ["SFO", "SJC"], ["LHR", "SFO"]]
Return ["JFK", "MUC", "LHR", "SFO", "SJC"].

Example 2:
tickets = [["JFK","SFO"],["JFK","ATL"],["SFO","ATL"],["ATL","JFK"],["ATL","SFO"]]
Return ["JFK","ATL","JFK","SFO","ATL","SFO"].
Another possible reconstruction is ["JFK","SFO","ATL","JFK","ATL","SFO"]. But it is larger in lexical order.

题目大意:

给定一组机票,用出发机场和到达机场[from, to]来表示,重建行程的顺序。所有的机票都属于一个从JFK(肯尼迪国际机场)出发的旅客。因此,行程必须从JFK开始。

注意:

如果存在多重有效的行程,你应当返回字典序最小的那个。例如,行程[“JFK”, “LGA”]的字典序比[“JFK”, “LGB”]要小。
所有的机场用3个大写字母表示(IATA编码)。
你可以假设所有的机票均至少包含一条有效的行程。

解题思路:

这条题实质是记录DFS路径的题目,且对儿子节点的选择是有顺序的。值得注意的是并不是所有路径都是可形成回路,所以需要DFS搜索。
既然是要记录路径就需要用数组保存结果,而有顺序则表示要排序。

  1. 建图,用邻接表来表示HashMap> graph
  2. 对每个节点的邻节点LinkList进行排序
  3. 从JFK开始dfs。1)终止条件为所有ticket都遍历了(达成回路)或者不能够遍历完。2)路径存在数组中。 3)通过删除节点表示已访问DFS该节点后图要恢复成原状态。

注意事项:

  1. 通过删除节点表示已访问DFS该节点后图要恢复成原状态。用下标i来删除,这样才能保证删除再加入仍然有序。另一种方法是用visited边来记录避免修改图。本文用前者
  2. 剪枝: 如果找到结果就返回
  3. 这题只有一条path,不是所有可能性,但让需要用res,复制path到res,因为path会恢复状态。

Python代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def findItinerary2(self, tickets: List[List[str]]) -> List[str]:
graph = collections.defaultdict(list)
for li in tickets:
graph[li[0]].append(li[1])
for li in graph.values():
li.sort()
path, res = ['JFK'], []
self.dfs2(graph, 'JFK', len(tickets), path, res)
return res[0]

def dfs2(self, graph, start, ticket_left, path, res):
if ticket_left == 0:
res.append(list(path)) # remember
return True
for i, neighbor in enumerate(graph[start]):
path.append(neighbor)
graph[start].pop(i) # remember
if self.dfs2(graph, neighbor, ticket_left - 1, path, res):
return True
graph[start].insert(i, neighbor)
path.pop()
return False

注意事项:

  1. 终止条件为所有ticket都遍历了(达成完整路)或者不能够遍历完Dfs API含ticketLeft。如{“JFK”,”KUL”},{“JFK”,”NRT”},{“NRT”,”JFK”},虽然KUL在NRT前,但KUL不能组成回路。
  2. DFS路径尽量存在数组中,否则用ArrayList中就要先add再remove。
  3. 通过删除节点表示已访问DFS该节点后图要恢复成原状态。

Java代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public List<String> findItinerary(String[][] tickets) {
HashMap<String, LinkedList<String>> graph = new HashMap<String, LinkedList<String>>();
for(int i=0;i<tickets.length;i++){
LinkedList<String> neighbor = graph.get(tickets[i][0]);
if(neighbor==null)
neighbor = new LinkedList<String>();
neighbor.add(tickets[i][1]);
graph.put(tickets[i][0], neighbor);
}
Iterator<String> it = graph.keySet().iterator();
while(it.hasNext()){
String key = it.next();
LinkedList<String> neighbor = graph.get(key);
Collections.sort(neighbor);
graph.put(key, neighbor);
}
String[] re = new String[tickets.length+1];
String cur = "JFK";
re[0] = cur;
isDFS(graph,cur,tickets.length,tickets.length,re);
return new ArrayList<String>(Arrays.asList(re));
}

public boolean isDFS(HashMap<String, LinkedList<String>> graph, String departCity
,int ticketLeft, int ticketNum, String[] re){
if(ticketLeft==0)
return true;
LinkedList<String> desCity = graph.get(departCity);
if(desCity==null)
return false;
for(int i=0;i<desCity.size();i++){
String cur = desCity.get(i);
re[ticketNum-ticketLeft+1] = cur;
desCity.remove(i);
graph.put(departCity, desCity);
if(isDFS(graph,cur,ticketLeft-1,ticketNum,re))
return true;;
desCity.add(i,cur);
graph.put(departCity, desCity);
}
return false;
}

算法分析:

时间复杂度为O(n),空间复杂度O(n)

Free mock interview