题目来源:https://leetcode-cn.com/explore/interview/card/top-interview-questions-easy/1/array/
给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
示例 1:
给定数组 nums = [1,1,2],
函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。
你不需要考虑数组中超出新长度后面的元素。
示例 2:
给定 nums = [0,0,1,1,1,2,2,3,3,4],
函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。
你不需要考虑数组中超出新长度后面的元素。
说明:
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以“引用”方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝
int len = removeDuplicates(nums);
// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。
for (int i = 0; i < len; i++) {
print(nums[i]);
}
very easy,用了双指针
class Solution(object):
def removeDuplicates(self, nums):
# 处理类似 [1], []
if len(nums) <= 1:
return len(nums)
idx = 0
for i in range(1, len(nums)):
if nums[i] != nums[idx]:
idx += 1
nums[idx] = nums[i]
return idx+1
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入: [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
示例 2:
输入: [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。
因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
示例 3:
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
开始的思路:每找到一个升序的子数组就将数组首尾差值(首:买入;尾:卖出)加到总数中;
但是这个程序的鲁棒性并不好,首先需要判断数组是不是为空,其次还需要在最后去处理【1,2,3】和【2,1,3,4,5】这种升序子数组在末端的例子。
class Solution(object):
def maxProfit(self, prices):
if len(prices) == 0:
return 0
idx1 = 0
sum = 0
n = len(prices)
for idx2 in range(0, n - 1):
if prices[idx2] >= prices[idx2 + 1]:
sum += (prices[idx2] - prices[idx1])
idx1 = idx2 + 1
if idx1 != n - 1:
sum += (prices[n - 1] - prices[idx1])
return sum
好的解法是:只要后面的比前面的大就把他们的差值加入到总数中;
思路的受限主要来源于这个问题的背景,想着要找到买入和卖出的时间,然后计算出这两个位置股票的差值,然而忘记了其实他们的差值就正好=这个升序数组中每两个相邻数的差的总和;这样程序的鲁棒性也会增强!
class Solution(object):
def maxProfit(self, prices):
sum = 0
n = len(prices)
for i in range(0, n -1):
if prices[i] < prices[i+1]:
sum += prices[i+1] - prices[i]
return sum
给定一个整数数组,判断是否存在重复元素。
如果任何值在数组中出现至少两次,函数返回 true。如果数组中每个元素都不相同,则返回 false。
示例 1:
输入: [1,2,3,1]
输出: true
示例 2:
输入: [1,2,3,4]
输出: false
示例 3:
输入: [1,1,1,3,3,4,3,2,4,2]
输出: true
直接用set的特性
直接用
class Solution(object):
def containsDuplicate(self, nums):
s = set()
for num in nums:
s.add(num)
if len(s) != len(nums):
return True
else:
return False
给定两个数组,编写一个函数来计算它们的交集。
示例 1:
输入: nums1 = [1,2,2,1], nums2 = [2,2]
输出: [2,2]
示例 2:
输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出: [4,9]
说明:
输出结果中每个元素出现的次数,应与元素在两个数组中出现的次数一致。
我们可以不考虑输出结果的顺序。
思路:
将数组1中的数存到字典里面:key是这个数,value是它出现的次数
然后遍历数组2,如果这个数在字典里面,而且其对应的value不是0(比如 【1,2,2】,nums2 = 【2,2,2,3】那么到第三个2的时候就不会append了,因为此时dic[2] = 0),就在我们最后把这个数append到要返回的数组
计算复杂度:o(n),空间复杂度:o(n)
class Solution(object):
def intersect(self, nums1, nums2):
dic = {}
# create a dictionary where
# key: number in a list
# value: ocurring times of this number
for n in nums1:
if n in dic:
dic[n] += 1
else:
dic[n] = 1
res_list = []
for n in nums2:
if n in dic and dic[n] != 0:
res_list.append(n)
dic[n] -= 1
return res_list
进阶:
如果给定的数组已经排好序呢?你将如何优化你的算法?
如果 nums1 的大小比 nums2 小很多,哪种方法更优?
如果 nums2 的元素存储在磁盘上,磁盘内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办?
给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一。
最高位数字存放在数组的首位, 数组中每个元素只存储一个数字。
你可以假设除了整数 0 之外,这个整数不会以零开头。
示例 1:
输入: [1,2,3]
输出: [1,2,4]
解释: 输入数组表示数字 123。
示例 2:
输入: [4,3,2,1]
输出: [4,3,2,2]
解释: 输入数组表示数字 4321。
思路: 考虑到类似19,99这样的数就可以了
class Solution(object):
def plusOne(self, digits):
fw = 1
for i in range(len(digits)-1, -1, -1):
if digits[i]+fw == 10:
digits[i] = 0
fw = 1
else:
digits[i] += fw
return digits
return [1] + digits
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
示例:
输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
说明:
必须在原数组上操作,不能拷贝额外的数组。
尽量减少操作次数。
思路: 把0想象成一个小泡泡不断往后冒~~~(实际上,不用这么麻烦,可以更简单)
class Solution(object):
def moveZeroes(self, nums):
# 找到第一个零的位置
idx = 0
while idx < len(nums):
if nums[idx] == 0:
break
idx += 1
if idx >= len(nums)-1:
return
for i in range(idx+1, len(nums)):
if nums[i] != 0:
# 0和非0数互换位置
nums[idx] = nums[i]
nums[i] = 0
idx += 1
更优雅的思路(没必要去找第一0所在的位置):
class Solution(object):
def moveZeroes(self, nums):
idx = 0
for i in range(len(nums)):
if nums[i] != 0:
# 0和非0数互换位置
tmp = nums[idx]
nums[idx] = nums[i]
nums[i] = tmp
idx += 1
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。
示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
思路:
一开始的思路是,遍历两遍,第一遍将数组中的数和它的index存到字典中,然后第二遍看有没有key = target - 当前数
但是被自己否决了,认为如果存在相同的数,那么没办法存,可是却忽略了重要的条件:每种输入只会对应一个答案这也就意味着,就算有相同的,如果它是答案的话,那只有可能是类似于target = 8,数组中有两个4这种情况;
然而,其实这种担忧是不必的,因为我们用一遍遍历就可以完成了,时间复杂度为o(n)(上述提到的特殊情况也得到了解决)
class Solution(object):
def twoSum(self, nums, target):
dic = {}
for i in range(len(nums)):
if target - nums[i] in dic:
return [dic[target - nums[i]]] + [i]
else:
dic[nums[i]] = i
给定一个 n × n 的二维矩阵表示一个图像。
将图像顺时针旋转 90 度。
说明:
你必须在原地旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要使用另一个矩阵来旋转图像。
示例 1:
给定 matrix =
[
[1,2,3],
[4,5,6],
[7,8,9]
],
原地旋转输入矩阵,使其变为:
[
[7,4,1],
[8,5,2],
[9,6,3]
]
示例 2:
给定 matrix =
[
[ 5, 1, 9,11],
[ 2, 4, 8,10],
[13, 3, 6, 7],
[15,14,12,16]
],
原地旋转输入矩阵,使其变为:
[
[15,13, 2, 5],
[14, 3, 4, 1],
[12, 6, 8, 9],
[16, 7,10,11]
]
解法一(也是我自己想到的思路): 通过找规律发现就是四个元素的链子顺时针旋转一次
即,原来位置在 (i, j) 的元素转到了 (j, N-1-i) 的位置
用公式表示就是 Matrix[j, N-1-i] <= Matrix [i, j]
用图表示就是这样滴:
于是我就编了下面的代码,一看就很不美观? 主要原因是我利用了集合来存还没有被修改过的节点;在赋值的时候不够简单有效
class Solution(object):
def rotate(self, matrix):
N = len(matrix)
notVisited = set(range(N*N))
for iter in range(N * N // 4):
# 所有的都被访问过了
if not notVisited:
return
n = notVisited.pop()
i, j = n//N, n - (n//N) * N
# step 1
tmp1 = matrix[j][N-1-i]
matrix[j][N - 1 - i] = matrix[i][j]
i, j = j, N - 1 - i
notVisited.remove(i * N + j)
# step 1
tmp2 = matrix[j][N-1-i]
matrix[j][N - 1 - i] = tmp1
i, j = j, N - 1 - i
notVisited.remove(i * N + j)
# step 1
tmp1 = matrix[j][N-1-i]
matrix[j][N - 1 - i] = tmp2
i, j = j, N - 1 - i
notVisited.remove(i * N + j)
# step 1
matrix[j][N - 1 - i] = tmp1
i, j = j, N - 1 - i
整理过后,更优化的代码如下:
说明: Matrix[j, N-1-i] <= Matrix [i, j] 可以看作是 Matrix[i, j] <= Matrix [N-1-j, i]
为什么设定i和j的取值分别在[0, n/2), [i, n-i-1] 呢??????
class Solution(object):
def rotate(self, matrix):
N = len(matrix)
for i in range(N//2):
for j in range(i, N-i-1):
tmp = matrix[i][j]
matrix[i][j] = matrix[N-j-1][i]
matrix[N-j-1][i] = matrix[N-i-1][N-j-1]
matrix[N-i-1][N-j-1] = matrix[j][N-i-1]
matrix[j][N-i-1] = tmp
但这个不是最优雅的方法,还有 解法2和 解法3,都是利用了矩阵的旋转,非常elegant!!!!!!!!感觉是一道数学题没错了
/*
* clockwise rotate
* first reverse up to down, then swap the symmetry
* 1 2 3 7 8 9 7 4 1
* 4 5 6 => 4 5 6 => 8 5 2
* 7 8 9 1 2 3 9 6 3
*/
void rotate(vector<vector<int> > &matrix) {
reverse(matrix.begin(), matrix.end());
for (int i = 0; i < matrix.size(); ++i) {
for (int j = i + 1; j < matrix[i].size(); ++j)
swap(matrix[i][j], matrix[j][i]);
}
}
/*
* anticlockwise rotate
* first reverse left to right, then swap the symmetry
* 1 2 3 3 2 1 3 6 9
* 4 5 6 => 6 5 4 => 2 5 8
* 7 8 9 9 8 7 1 4 7
*/
void anti_rotate(vector<vector<int> > &matrix) {
for (auto vi : matrix) reverse(vi.begin(), vi.end());
for (int i = 0; i < matrix.size(); ++i) {
for (int j = i + 1; j < matrix[i].size(); ++j)
swap(matrix[i][j], matrix[j][i]);
}
}
代码来源:https://leetcode.com/problems/rotate-image/discuss/18872/A-common-method-to-rotate-the-image
判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。
数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
数独部分空格内已填入了数字,空白格用 ‘.’ 表示。
示例 1:
输入:
[
[“5”,“3”,".",".",“7”,".",".",".","."],
[“6”,".",".",“1”,“9”,“5”,".",".","."],
[".",“9”,“8”,".",".",".",".",“6”,"."],
[“8”,".",".",".",“6”,".",".",".",“3”],
[“4”,".",".",“8”,".",“3”,".",".",“1”],
[“7”,".",".",".",“2”,".",".",".",“6”],
[".",“6”,".",".",".",".",“2”,“8”,"."],
[".",".",".",“4”,“1”,“9”,".",".",“5”],
[".",".",".",".",“8”,".",".",“7”,“9”]
]
输出: true
示例 2:
输入:
[
[“8”,“3”,".",".",“7”,".",".",".","."],
[“6”,".",".",“1”,“9”,“5”,".",".","."],
[".",“9”,“8”,".",".",".",".",“6”,"."],
[“8”,".",".",".",“6”,".",".",".",“3”],
[“4”,".",".",“8”,".",“3”,".",".",“1”],
[“7”,".",".",".",“2”,".",".",".",“6”],
[".",“6”,".",".",".",".",“2”,“8”,"."],
[".",".",".",“4”,“1”,“9”,".",".",“5”],
[".",".",".",".",“8”,".",".",“7”,“9”]
]
输出: false
解释: 除了第一行的第一个数字从 5 改为 8 以外,空格内其他数字均与 示例1 相同。
但由于位于左上角的 3x3 宫内有两个 8 存在, 因此这个数独是无效的。
说明:
一个有效的数独(部分已被填充)不一定是可解的。
只需要根据以上规则,验证已经填入的数字是否有效即可。
给定数独序列只包含数字 1-9 和字符 ‘.’ 。
给定数独永远是 9x9 形式的。
思路: 因为这道题不需要验证数独是不是可解的,所以用上面的三条规则实现简单的逻辑即可,代码如下:
class Solution(object):
def isValidSudoku(self, board):
"""
:type board: List[List[str]]
:rtype: bool
"""
check = set()
# check rows
for i in range(9):
check.clear()
for j in range(9):
if board[i][j] != '.':
if board[i][j] in check:
return False
else:
check.add(board[i][j])
# check cols
for j in range(9):
check.clear()
for i in range(9):
if board[i][j] != '.':
if board[i][j] in check:
return False
else:
check.add(board[i][j])
# check 3x3 box
for bx in range(3):
for by in range(3):
check.clear()
for i in range(bx*3, bx*3+3):
for j in range(by*3, by*3+3):
if board[i][j] != '.':
if board[i][j] in check:
return False
else:
check.add(board[i][j])
return True
自此,初级算法的数组部分全部结束。
总结一下,题目并不算特别难,主要考察的是逻辑的实现,以及边界值是否有考虑。
同时,还训练了如何去优化算法,以获得更优的计算复杂性&空间复杂性;这些优化主要需要从数据结构中找一些思路,比如利用集合、字典等的实现;还有一些优化是基于对这个问题的更一般性的规律来说,从而找到更好的算法~
Keep Fighting!!!