ios 面试题代码篇
Here comes the fourth week of the challenge. Keep grinding. If you need to catch up a bit, you can find challenges of the previous weeks here — Week 1, Week 2, and Week 3
挑战的第四周到了。 继续研磨。 如果需要赶上一点,可以在这里找到前几周的挑战- 第1 周 , 第2 周和第3周
Also, feel free to utilize my Github repo that includes a solution and unit tests for each coding question of the code challenge.
另外,请随时使用我的Github存储库 包括针对代码挑战的每个编码问题的解决方案和单元测试。
In this article, we’ll discuss the following 7 programming questions from week 4 of the code challenge.
在本文中,我们将从代码挑战的第4周开始讨论以下7个编程问题。
(🥇 🥈 🥉 indicate writer’s selection of high-frequency interview questions.)
( 🥉 表示作者选择了高频访谈问题。)
子数组总和等于K (Subarray Sum Equals K)
🥇 Writer’s selection of the week
🥇 作家的每周精选
Description: Given an array of integers and an integer k, you need to find the total number of continuous subarrays whose sum equals k.
描述 :给定一个整数数组和一个整数k ,您需要找到总和等于k的连续子数组的总数。
Example Input:nums = [1, 1, 1], k = 2Output: 2
At first glance, the solution I can think of is a brute force solution that dynamically sets the start and end of the subarray and calculates the sum of that subarray to check if it equals k. This solution uses a nested for-loops that take O(N²) runtime. Although it is an acceptable solution, it's not efficient enough.
乍一看,我能想到的解决方案是蛮力解决方案,它可以动态设置子数组的开始和结尾,并计算该子数组的总和以检查其是否等于k 。 该解决方案使用一个嵌套的for循环,占用O(N²)运行时间。 尽管这是可以接受的解决方案,但效率不够。
Let’s take a close look at the hints.
让我们仔细看看这些提示。
Skip Hint 1 and 2.Hint 3.
What about storing sum frequencies in a hash table? Will it be useful?Hint 4. sum(i,j) = sum(0,j) - sum(0,i), where sum(i,j) represents the sum of all the elements from index i to j-1. Can we use this property to optimize it.
Following Hint 3, I hereby create a Dictionary — dict to store the frequency of the sum. When we iterate over the input array, we calculate the sum of the subarray from index 0 to the current index, which I define as currentSum. Subsequently, we store currentSum as the key and 1 (count) as the value in the dict. Whenever the same sum appears, we have to increment the value for that sum key by one in the dict. Use the above example, we’ll get:
在提示3之后 ,我特此创建一个Dictionary — dict,用于存储和的频率。 当我们遍历输入数组时,我们计算从索引0到当前索引的子数组之和,我将其定义为currentSum 。 随后,我们将currentSum作为键和1 (计数)存储 作为dict中的值 。 每当出现相同的总和时,我们就必须在dict中将该总和键的值增加1。 使用上面的示例,我们将获得:
Input: [1, 1, 1]Initial dict[
0 : 1
]The dict after the iteration
[
0 : 1
1 : 1
2 : 1
3 : 1
]
It means the sum calculated from index 0 once equals to 1, 2, and 3 during the iteration. Continue to Hint 4, we want to see if currentSum -k is a valid key in the dict. If it does, it means there is a subarray (from index 0) having a sum of currentSum -k, hence, we can exclude that subarray to get the subarray that has an exact sum of k. The count stored as a value in the dict represents the frequency of currentSum -k in the array. The higher the frequency, the more subarrays we can possibly exclude from the current one to get the one having desired sum k. Therefore, we increment the output by the corresponding count. See the solution below.
这意味着迭代期间从索引0一旦等号计算为1,2和3的总和。 继续到提示4 ,我们想看看currentSum -k是否是dict中的有效键。 如果是的话,这意味着存在一个子数组(从索引0开始 ),该子数组具有currentSum -k的总和,因此,我们可以排除该子数组以获得具有精确的k的子数组。 存储为dict中的值的计数表示数组中currentSum -k的频率。 频率越高,我们可以从当前阵列中排除的子阵列越多,以获得具有期望总和k的子阵列。 因此,我们将输出增加相应的计数。 请参阅下面的解决方案。
func subarraySum(_ nums: [Int], _ k: Int) -> Int {
var output = 0
var sum = 0
var dict = [0 : 1]
for num in nums {
sum += num
if dict[sum - k] != nil {
output += dict[sum - k]!
}
if dict[sum] == nil {
dict[sum] = 1
} else {
dict[sum]! += 1
}
}
return output
}
Time complexity: O(N) as we iterate over the array only once. In terms of runtime, this DP solution does have a significant improvement to the brute force one (O(N²)). Be sure to get yourself nimble in this powerful programming technique.
时间复杂度: O(N),因为我们仅对数组进行一次迭代。 在运行时方面,该DP解决方案确实对暴力破解( O(N²) )进行了重大改进。 确保使用这种强大的编程技术来使自己敏捷。
数字范围的按位与 (Bitwise AND of Numbers Range)
Description: Given a range [m, n] where 0 <= m <= n <= 2147483647, return the bitwise AND of all numbers in this range, inclusive.
说明 :给定范围[m,n],其中0 <= m <= n <= 2147483647,返回此范围内(包括两端)的所有数字的按位与。
ExampleInput: [5, 7]Output: 4
This is a normal bitwise operation question. Just FYI, here is a link to Swift bitwise operations. After getting an understanding of the operators, you should be able to have enough material to figure out this question.
这是一个正常的按位运算问题。 仅供参考,这是Swift逐位操作的链接 。 在了解了操作员的知识之后,您应该能够掌握足够的资料来解决这个问题。
func rangeBitwiseAnd(_ m: Int, _ n: Int) -> Int {
if m == 0 {
return 0
}
var m = m
var n = n
var factor = 0
while m != n {
m >>= 1
n >>= 1
factor += 1
}
return m << factor
}
Time complexity: Let’s assume the worst case is with the widest range where n is 2147483647 and m is 1. In such a case, the operation takes 31 times to complete. If the number n is N, the runtime of this solution is O(31) which can be noted as O(1), a constant-time solution.
时间复杂度:假设最坏的情况是在最大范围内,其中n为2147483647 , m为1 。 在这种情况下,操作需要31次才能完成。 如果数字n为N ,则此解决方案的运行时间为O(31) ,可以将其记为O(1) ,这是一个恒定时间解决方案。
LRU缓存 (LRU Cache)
Description: Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get and put.
描述 :设计和实现最近最少使用(LRU)缓存的数据结构。 它应该支持以下操作: get和put 。
get(key) — Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.
get(key) —如果键存在于缓存中,则获取键的值(始终为正),否则返回-1。
put(key, value) — Set or insert the value if the key is not already present. When the cache reached its capacity, it should invalidate the least recently used item before inserting a new item.
put(key,value)—如果密钥不存在,则设置或插入该值。 当缓存达到其容量时,它应在插入新项目之前使最近最少使用的项目无效。
Note: The cache is initialized with positive capacity. Both operations are required to be in O(1) time complexity.
注意:缓存已初始化为正容量。 这两个操作都必须具有O(1)时间复杂度。
LRU cache is a classic question in all sorts of technical interviews. We can start with our primitive version that concentrates on making either get or put operation be in O(1) time. Let’s say we want to make get operation be constant time, a Dictionary can possibly achieve that. But it seems to be a bit hard to sort all values for the purpose of removing the least-used one when we run out of capacity. Although Swift Array does allow us to remove an element at a certain index, the operation takes O(N) time.
LRU缓存是各种技术面试中的经典问题。 我们可以从原始版本开始,该版本专注于使get或put操作处于O(1)时间。 假设我们要使get操作为恒定时间, Dictionary可以实现该目标。 但是,似乎很难对所有值进行排序,以在容量用尽时删除最不常用的值。 尽管Swift Array确实允许我们删除某个索引处的元素,但该操作需要O(N)时间。
According to Apple Document, the function — remove(at:) of an N-length Array is O(N) time complexity since it has to shift all elements.
根据Apple Document的规定 , N长度数组的功能— remove(at :)具有O(N)的时间复杂度,因为它必须移动所有元素。
A simple data structure — Doubly Linked List would meet the constant-time criteria for removal. Given each node in a Doubly Linked List has pointers to its previous and next nodes, we can get rid of a node in constant time while maintaining the sequence of the rest of the list. In addition, we need a head and a tail node in the list so that we can insert a node to the front or remove the back of the list.
一个简单的数据结构- 双链表将满足删除的恒定时间标准。 给定双向链接列表中的每个节点都有指向其上 一个节点和下一个节点的指针,我们可以在恒定时间内摆脱一个节点,同时保持列表其余部分的顺序。 另外,我们在列表中需要一个头节点和一个尾节点,以便我们可以在列表的前面或后面删除一个节点。
Integrating two data structures, we make the values stored in the dictionary be ListNodes, which is linked together in the Doubly Linked List. In this way, every time we operate get() function, we just look up the dictionary to find if the key exists. If it does, we remove the node from its position and insert it to the front of the list. Besides, when we call put(key, value) function, we have to check the capacity first. If the capacity is not enough, we have to remove the last node from the list and dictionary first. Afterward, we are cleared to put a new value into the storage (the list and dictionary). Be sure the new node is put at the head of the linked list. The full solution is shown below.
集成两个数据结构,我们使字典中存储的值成为ListNodes ,后者在“双重链接列表”中链接在一起。 这样,每次我们操作get()函数时,我们只需查找字典以查找键是否存在。 如果是这样,我们将从其位置删除该节点,并将其插入列表的开头。 此外,当我们调用put(key,value)函数时,我们必须首先检查容量。 如果容量不够 ,我们必须先从列表和字典中删除最后一个节点。 之后,我们被清除为将新值放入存储(列表和字典)。 确保将新节点放在链接列表的开头。 完整的解决方案如下所示。
class LRUCache {
var capacity: Int
var head: ListNode
var tail: ListNode
var dict: [Int:ListNode]
init(_ capacity: Int) {
self.capacity = capacity
dict = [Int:ListNode]()
head = ListNode(-1, -1)
tail = ListNode(-1, -1)
head.next = tail
tail.prev = head
}
func get(_ key: Int) -> Int {
if let node = dict[key] {
remove(node)
insertToHead(node)
return node.val
}
return -1
}
func insertToHead(_ node: ListNode) {
node.next = head.next
head.next?.prev = node
head.next = node
node.prev = head
}
func remove(_ node: ListNode) {
node.next?.prev = node.prev
node.prev?.next = node.next
}
func put(_ key: Int, _ value: Int) {
let node = ListNode(value, key)
if dict[key] != nil {
remove(dict[key]!)
} else if dict.keys.count == capacity {
if let lastNode = tail.prev {
remove(lastNode)
dict[lastNode.key] = nil
}
}
dict[key] = node
insertToHead(node)
}
}
class ListNode {
var val: Int
var key: Int
var next: ListNode?
var prev: ListNode?
init(_ val: Int, _ key: Int) {
self.val = val
self.key = key
}
}
Time complexity: O(1) for both operations. As requested by the question, we have adapted two data structures in order to make the operations be in constant time.
时间复杂度:两个操作的O(1) 。 根据问题的要求,我们调整了两个数据结构以使操作保持恒定的时间。
跳跃游戏 (Jump Game)
Description: Given an array of non-negative integers, you are initially positioned at the first index of the array. Each element in the array represents your maximum jump length at that position. Determine if you are able to reach the last index.
描述 :给定一个非负整数数组,您最初位于该数组的第一个索引处。 数组中的每个元素代表该位置的最大跳转长度。 确定您是否能够达到最后一个索引。
Example 1:Input: nums = [2, 3, 1, 1, 4]Output: trueExplanation: Jump 1 step from index 0 to 1, then 3 steps to the last index.Example 2:Input: nums = [3, 2, 1, 0, 4]Output: falseExplanation: Its maximum jump length is 0, so it's impossible to reach the last index.
This is a Dynamic Programming (DP) question. Typically, solving a DP question, you are able to find a recursive solution of it first. In Example 1 at index 0, we want to see the possibilities of jumping 1 step or 2 steps inclusively. Implementing that, we then put our cursor on index 1 then run the array [3, 1, 1, 4]. On the other hand, we also point to index 2 and pass [1, 1, 4] to the same function for further computing. Although the mechanism looks straightforward, the process I wrote is just the tip of an iceberg. It will be extremely costly if you run the entire recursive method.
这是一个动态编程 (DP)问题。 通常,解决DP问题,您可以首先找到该问题的递归解决方案。 在索引 1的示例1中,我们希望看到跳1步或2步(包括两个)的可能性。 为此,我们将光标放在索引1上,然后运行数组[3,1,1,4] 。 另一方面,我们还指向索引2并将[1、1、4]传递给同一函数以进行进一步的计算。 尽管该机制看起来很简单,但是我编写的过程只是个冰山一角。 如果您运行整个递归方法,那将是非常昂贵的。
Nevertheless, the recursive method we derived is not useless. We can apply a backtracking technique that helps us save steps in recursion. In the following, I use an array of integers, where each integer at index indicates if the index can reach the last index. There are three states represented by integers 0, 1, and 2.
但是,我们得出的递归方法并非没有用。 我们可以应用回溯技术来帮助我们节省递归步骤。 在下面,我使用一个整数数组,其中索引处的每个整数指示索引是否可以到达最后一个索引。 共有三个状态,分别由整数0、1和2表示。
0 = Unknown. Initial state.
1 = Positive. Able to reach last index.
2 = Negative. Unable to reach last index.
In every recursion, if we are able to determine whether an index is able to reach the final index, we store the result in the array. Next time if we need to find the result of an index, we first see if the array has its result before proceeding to further recursion. As a result, the runtime is reduced quite a lot.
在每次递归中,如果我们能够确定索引是否能够到达最终索引,则将结果存储在数组中。 下次,如果我们需要查找索引的结果,则在继续进行进一步递归之前,我们首先要查看数组是否具有其结果。 结果,运行时间大大减少了。
func canJump(_ nums: [Int]) -> Bool {
guard nums.count > 1 else { return true }
var tracking = Array(repeating: 0, count: nums.count)
tracking[tracking.count - 1] = 1
return canJump(nums, 0, &tracking)
}
func canJump(_ nums: [Int], _ start: Int, _ tracking: inout [Int]) -> Bool {
if tracking[start] != 0 {
return tracking[start] == 1 ? true : false
}
if nums[start] + start >= nums.count - 1 || start >= nums.count - 1 {
return true
}
var step = 1
while step <= nums[start] {
let jump = canJump(nums, start + step, &tracking)
tracking[start + step] = jump ? 1 : 0
tracking[start] = jump ? 1 : 0
if jump {
return true
}
step += 1
}
return false
}
Time complexity: O(N²). For each index, we explore whether it is able to reach the final index. The number at each index, aka jump, affects the runtime. The worst case is when jump at each index is equal to or greater than the array length N, which will result in O(N²) overall runtime.
时间复杂度: O(N²) 。 对于每个索引,我们探索它是否能够达到最终索引。 每个索引处的数字(也称为跳转)会影响运行时间。 最坏的情况是每个索引处的跳转等于或大于数组长度N ,这将导致O(N²)总体运行时间。
Although we have reduced the runtime to O(N²), it is not fast enough to pass LeetCode tests. There is another greedy approach provided here that can reduce the runtime to O(N). I suggest you try to understand that one as well.
尽管我们已将运行时缩减为O(N²) ,但它尚不足以通过LeetCode测试 。 这里提供了另一种贪婪方法,可以将运行时间减少到O(N) 。 我建议您也尝试理解这一点。
最长公共子序列(LCS) (Longest Common Subsequence (LCS))
🥉 Writer’s selection of the week
🥉 作家的每周精选
Description: Given two strings, return the length of their longest common subsequence. If there is no common subsequence, return 0.
描述 :给定两个字符串,返回它们最长的公共子序列的长度。 如果没有公共子序列,则返回0。
A subsequence of a string is a new string generated from the original string with some characters(can be none) deleted without changing the relative order of the remaining characters. (eg, “ace” is a subsequence of “abcde” while “aec” is not). A common subsequence of two strings is a subsequence that is common to both strings.
字符串的亚序列是从原来的字符串与一些字符产生一个新的字符串(可以是无)而不改变剩余的字符的相对顺序被删除。 (例如,“ ace ”是“ abcde ”的子序列,而“ aec ”则不是)。 两个字符串的共同子序列是两个字符串所共有的子序列。
Example 1Input: text1 = "abcde", text2 = "ace" Output: 3
The longest common subsequence is "ace" with a length of 3.
Example 2Input: text1 = "abc", text2 = "abc"Output: 3
The longest common subsequence is "abc" and its length is 3.Example 3Input: text1 = "abc", text2 = "def"Output: 0
There is no such common subsequence.
This is a typical Dynamic Programming question. It is easier for us to start with a recursion before optimizing it to a DP approach. The steps of recursion can be broken down into two conditions.
这是一个典型的动态编程问题。 对于我们来说,从递归开始更容易,然后再将其优化为DP方法。 递归的步骤可以分为两个条件 。
1. text1[0] == text2[0] then:newText1 = text1[1..<text1.count]newText2 = text2[1..<text2.count]return:1 + recursion(newText1, newText2)2. text1[0] != text2[0] then:newText1 = text1[1..<text1.count]newText2 = text2[1..<text2.count]return:
max(recursion(newText1, text2), recursion(text1, newText2))
In Example 1, the first letter in text1 and text2 is the same, therefore, we increment the count of LCS by one and move forward by passing “bcde” as newtext1 and “ce” as newtext2 to the same recursive function. In the next round, we find out the first letter of newtext1 — “b” isn’t equal to “c” of newtext2. In this situation, we have to compare the cases of either moving forward the letter in newtext1 or newtext2. Consequently, we want to output whichever that has a larger LCS value. The following is the recursive solution.
在实施例1中,在文本1和文本的第一个字母是相同的,因此,我们通过一个递增LCS的计数和通过使“BCDE”为newtext1和“CE”作为newtext2到相同的递归函数前进。 在下一轮中,我们发现newtext1的第一个字母- “ b ”不等于newtext2的 “ c ”。 在这种情况下,我们必须比较前进newtext1或newtext2中的字母的情况 。 因此,我们要输出具有更大LCS值的那个。 以下是递归解决方案。
func longestCommonSubsequence(_ text1: String, _ text2: String) -> Int {
if text1.count == 0 || text2.count == 0 {
return 0
}
var arr1 = Array(text1)
var arr2 = Array(text2)
if arr1[0] == arr2[0] {
return 1 + longestCommonSubsequence(String(arr1[1..<text1.count]), String(arr2[1..<text2.count]))
} else {
return max(longestCommonSubsequence(String(arr1), String(arr2[1..<text2.count])), longestCommonSubsequence(String(arr1[1..<text1.count]), String(arr2)))
}
}
Nevertheless, as I mentioned, the recursive method is taking too much time to be an acceptable method. Let’s devise our DP approach. By observing the mechanism of the recursive method, there are duplicated calculations considered redundant. Therefore, we want to use Memoization approach to save some time.
但是,正如我提到的,递归方法要花费太多时间才能成为可接受的方法。 让我们设计我们的DP方法。 通过观察递归方法的机制,有重复的计算被认为是多余的。 因此,我们想使用记忆化方法来节省一些时间。
First, create a 2D array (text1.count rows by text2.count columns) — dp filled with 0s. The same as the recursive method, there are two conditions that we have to handle. This time, I prefer to start with the condition where text1[idx1] is not equal to text2[idx2] because it affects the order of how we traverse inputs. Dealing with the condition in DP is similar to what we do in the recursive method, in which we rely on the later comparison result after moving cursors on the strings. That being said, we need idx1 + 1 and idx2 + 1 results before determining current output. In order to achieve this, we have to traverse the inputs in reverse order. Consequently, the calculation for the unequal condition becomes:
首先,创建一个2D数组( text1.count 行乘text2.count 列 )— dp填充0 s。 与递归方法相同,我们必须处理两个条件 。 这次,我更喜欢从text1 [ idx1 ] 不等于 text2 [ idx2 ]的条件开始,因为它会影响我们遍历输入的顺序。 在DP中处理条件类似于在递归方法中进行的处理,在递归方法中,我们将光标移到字符串上后依赖于以后的比较结果。 就是说,在确定当前输出之前,我们需要idx1 + 1和idx2 + 1结果。 为了实现这一点,我们必须以相反的顺序遍历输入。 因此,不等式的计算结果为:
if text1[idx1] != text2[idx2]:
let accum1 = 0
if idx1 > 0:accum1 = dp[idx1 + 1][idx2]
let accum2 = 0
if idx2 > 0:accum2 = dp[idx1][idx2 + 1]
dp[idx1][idx2] += max(accum1, accum2)
Handling the equal condition is more intuitive. We basically increment output by one when there is a match. Since we are finding a sequence, the count can be accumulated from the last comparisons.
处理同等条件更加直观。 匹配时,我们基本上将输出增加一。 由于我们正在查找序列,因此可以从最后的比较中累计计数。
if text1[idx1] == text2[idx2]:
dp[idx1][idx2] += 1
if idx1 > 0, idx2 > 0:
dp[idx1][idx2] += dp[idx1 + 1][idx2 + 1]
Note that ascending or descending order does not affect the result since we only need the count of the sequence, but not the exact sequence.
请注意, 升序或降序 不会影响结果,因为我们只需要对序列进行计数,而无需精确的序列。
The DP solution can be mapped to the below table, where we start from top-left and go all the way down to bottom-right after comparisons that try to get the maximum value.
DP解决方案可以映射到下表,在这里我们尝试进行尝试以获取最大值,然后从左上角开始一直向下到右下角。
e d c b a---------------
e | 1 1 1 1 1c | 1 1 2 2 2a | 1 1 2 2 3
func longestCommonSubsequence(_ text1: String, _ text2: String) -> Int {
guard text1.count > 0, text2.count > 0 else { return 0 }
let arr1 = Array(text1)
let arr2 = Array(text2)
var dp = Array(repeating: Array(repeating: 0, count: arr2.count), count: arr1.count)
for i in 0..<arr1.count {
for j in 0..<arr2.count {
let idx1 = arr1.count - 1 - i
let idx2 = arr2.count - 1 - j
if arr1[idx1] == arr2[idx2] {
if i > 0, j > 0 {
dp[idx1][idx2] += dp[idx1 + 1][idx2 + 1]
}
dp[idx1][idx2] += 1
} else {
let accum1 = i > 0 ? dp[idx1 + 1][idx2] : 0
let accum2 = j > 0 ? dp[idx1][idx2 + 1] : 0
dp[idx1][idx2] += max(accum1, accum2)
}
}
}
return dp[0][0]
}
Time complexity: O(NM), where N is the length of text1, and M is the length of text2. The Longest Common Subsequence (LCS) question is one of the popular topics in technical interviews. Though it sometimes appears in other forms, the concept to conquer the problem is pretty much the same. If you still have confusion, I found a U.C. Irvine website that did a great job explaining solutions of LCS that can help you understand it thoroughly.
时间复杂度: O(NM) ,其中N是text1的长度,而M是text2的长度。 最长公共子序列(LCS)问题是技术访谈中的热门话题之一。 尽管有时以其他形式出现,但解决问题的概念几乎相同。 如果您仍然感到困惑,我发现UC Irvine的网站做得很好,解释了LCS的解决方案,可以帮助您彻底了解它。
最大平方 (Maximal Square)
🥈 Writer’s selection of the week
🥈 作家的每周精选
Description: Given a 2D binary matrix filled with 0s and 1s, find the largest square containing only 1s and return its area.
描述 :给定一个填充有0 s和1 s的2D二进制矩阵,找到仅包含1 s的最大正方形并返回其面积。
ExampleInput:
1 0 1 0 0
1 0 1 1 1
1 1 1 1 1
1 0 0 1 0Output: 4
One idea is to go through the entire matrix, whenever a 1 is found, we do a depth-first search of its surroundings., which includes its right, bottom, and bottom-right side. If all of the three are 1s, we then move diagonally to bottom-right and continue the same search. Meanwhile, we increment the side length of the square by one. The process will be continued until any one of the directions has 0. When the exit condition is met, we compare the current length with the tracking length to get the longer one. This searching process takes O((NM)²) time where N is the row and M is the column of the matrix.
一个想法是遍历整个矩阵,每当找到1时 ,我们都会对其周围进行深度优先搜索 ,包括其右侧,底部和右下侧。 如果三个都为1 s,则我们将对角线移动到右下角并继续相同的搜索。 同时,我们将正方形的边长增加一。 该过程将继续进行,直到任一方向的值为0为止。 当满足退出条件时,我们将当前长度与跟踪长度进行比较,以获得更长的长度。 此搜索过程需要O((NM)²)时间,其中N是矩阵的行, M是矩阵的列。
On the other hand, you might have noticed this is another Dynamic Programming question. To start off, create a 2D array — dp with the same dimension as the input matrix and fill it with 0s. The array dp represents the side length of the maximum square. Whenever we find a 1 at (x, y), we check the values of its left, top, and top-left sides. The one has the smallest value is selected and incremented by one. The calculated value is the longest side length we have in the matrix starting with (0, 0) and ending in (x, y).
另一方面,您可能已经注意到这是另一个动态编程问题。 首先,创建一个二维数组— dp ,其维数与输入矩阵相同,并用0 s填充它。 数组dp表示最大正方形的边长。 每当我们在(x,y)处找到1时,我们都会检查其左侧,顶部和左上角的值。 一个具有最小值的值被选择并加一。 计算得出的值是矩阵中从(0,0)开始到(x,y)结束的最长边长。
dp[x][y] = min(dp[x - 1][y - 1], dp[x - 1][y], dp[x][y - 1] ) + 1
The DP solution is shown below. For detailed explanations and solutions, please view them via this link.
DP解决方案如下所示。 有关详细说明和解决方案,请通过此链接查看它们。
func maximalSquare(_ matrix: [[Character]]) -> Int {
guard matrix.count > 0, matrix[0].count > 0 else { return 0 }
var dp = Array(repeating: Array(repeating: 0, count: matrix[0].count), count: matrix.count)
var maxSideLength = 0
for x in 0..<matrix.count {
for y in 0..<matrix[x].count {
if matrix[x][y] == "1" {
if x > 0, y > 0 {
dp[x][y] = min(dp[x - 1][y - 1], dp[x - 1][y], dp[x][y - 1] ) + 1
} else {
dp[x][y] = Int(String(matrix[x][y]))!
}
maxSideLength = max(maxSideLength, dp[x][y])
}
}
}
return maxSideLength*maxSideLength
}
Time complexity: O(NM) where N and M are the dimensions of the matrix.
时间复杂度: O(NM) ,其中N和M是矩阵的维数。
Besides, this is our fourth DP question of the week 🤮. Hopefully, you won’t be asked this many brain-racking DP questions in your interview. Based on my experience, DP ones generally take about 20% of the interview coding questions. Even that, they are usually the toughest ones in the interview. Be sure to practice as many as you could before going into the battlefield.
此外,这是我们本周的第四个DP问题。 希望您在面试中不会被问到这么多令人费解的DP问题。 根据我的经验,DP考生通常会接受约20%的面试编码问题。 即便如此,他们通常是面试中最艰难的人。 在进入战场之前,请确保尽可能多地练习。
第一个唯一编号 (First Unique Number)
Description: You have a queue of integers, you need to retrieve the first unique integer for the queue. The following shows the constructor and functions that need to be implemented. See the comments for the guidance of implementation.
描述 :您有一个整数队列,您需要检索该队列的第一个唯一整数。 下面显示了需要实现的构造函数和函数。 请参阅注释以获取实施指导。
protocol FirstUniqueProtocol {
// Initializes the object with the numbers in the queue
init(_ nums: [Int])
// Returns the value of the first unique integer of the queue, and
// returns -1 if there is no such integer.
func showFirstUnique() -> Int
// Insert the value to the queue.
func add(_ val: Int)
}
firstUnique = FirstUnique([2, 3, 5])
firstUnique.showFirstUnique() // 2firstUnique.add(5)
firstUnique.showFirstUnique() // 2firstUnique.add(2)
firstUnique.showFirstUnique() // 3firstUnique.add(3)
firstUnique.showFirstUnique() // -1
First, in order to add and remove values fast, I propose to apply a Doubly-Linked List. Although the protocol does not require us to implement remove function, we still need it to remove duplicates from the list. In addition to the list, we need a Dictionary with an integer as a key and a list node as its value. This data structure will give us the ability to look up duplicates in constant time.
首先,为了快速添加和删除值,我建议应用一个双链表 。 尽管该协议不需要我们实现删除功能,但是我们仍然需要它从列表中删除重复项。 除了列表,我们还需要一个Dictionary ,它以整数作为键,并以list节点作为其值。 这种数据结构将使我们能够在恒定时间内查找重复项。
Having the data structures ready, we can think through our logic for those functions. Regarding add(_:), we want to check the dictionary to see if the value already exists. If it does, remove the value from the linked list. Otherwise, add the value to the dictionary and append it to the linked list. Since the order of the list is well maintained, we can simply return the head of the linked list when the showUnique() function is called. The full implementation is shown in the following.
准备好数据结构后,我们可以通过逻辑考虑这些功能。 关于add(_ :) ,我们要检查字典以查看值是否已经存在。 如果是这样,请从链接列表中删除该值。 否则,将值添加到字典并将其附加到链接列表。 由于列表的顺序得到了很好的维护,因此在调用showUnique()函数时,我们可以简单地返回链接列表的开头 。 完整的实现如下所示。
class FirstUnique {
var dict: [Int:DoublyListNode]
var head: DoublyListNode
var tail: DoublyListNode
required init(_ nums: [Int]) {
dict = [Int:DoublyListNode]()
head = DoublyListNode(-1)
tail = DoublyListNode(-1)
head.next = tail
tail.prev = head
for num in nums {
add(num)
}
}
func appendNode(_ node: DoublyListNode) {
tail.prev?.next = node
node.next = tail
node.prev = tail.prev
tail.prev = node
}
func removeNode(_ node: DoublyListNode) {
node.next?.prev = node.prev
node.prev?.next = node.next
}
func showFirstUnique() -> Int {
if let node = head.next {
return node.val
}
return -1
}
func add(_ value: Int) {
if dict[value] != nil {
removeNode(dict[value]!)
dict[value] = DoublyListNode(-1)
} else {
let newNode = DoublyListNode(value)
appendNode(newNode)
dict[value] = newNode
}
}
}
public class DoublyListNode {
public var val: Int
public var next: DoublyListNode?
public var prev: DoublyListNode?
public init(_ val: Int) {
self.val = val
self.next = nil
self.prev = nil
}
}
Time complexity: O(1) for both add(_:) and showUnique() functions. Just a side note that the use of the data structures is inspired by LRU cache question from the same week.
时间复杂度: add(_ :)和showUnique()函数均为O(1) 。 附带说明一下,数据结构的使用受到同一周LRU缓存问题的启发。
So far we have completed 28 coding questions. The last 2 will be finished up next week. Keep going! Again, the source code and unit tests are all up in my Github repo. Feel free to start a conversation or contribute your code there.
到目前为止,我们已经完成了28个编码问题。 最后两个将在下周完成。 继续! 同样,源代码和单元测试都在我的Github存储库中 。 随时开始对话或在此处贡献您的代码。
ios 面试题代码篇