题目
只出现一次的数字
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例 1:
输入: [2,2,1]
输出: 1
示例 2:
输入: [4,1,2,1,2]
输出: 4
看到这道题目的时候,第一个naive的想法是:
用一个额外的list的index值来表示这个数的值,如果出现了就讲这个index的位置+1,然后最后遍历这个额外的数组,找到值为1的位置。
然鹅不行呀,首先这个额外数组得设置成多大呢?题目没有限制这个数组中的int有多大。其次,题目有一个很关键的信息
算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
于是我又想到了另外一种方法:
给这个数组排序,注意到“除了某个元素只出现一次以外,其余每个元素均出现两次”,那么排序后,数组的偶数位如果和奇数位不相等,那就是要的答案
这样做是可以AC的,但是其实还是不满足题目的要求:虽然没有用额外的空间,但是一个排序怎么说最少也是 O(nlogn) 的时间复杂度,严格来说还是不可以。
于是我的小脑瓜已经不能想到一种线性的算法了,这时候看看别人的算法也是可以
巧妙借助异或的方法。因为异或这种运算有两种性质
- 0和任何数异或都是这个数本身
- 一个数和自身异或为0
- (a ^ b) ^ c = a ^ (b ^ c)
所以呢,如果给数组中的所有数都异或,那么最终结果只剩下那个形单影只的数啦~
代码如下:
class Solution(object):
def singleNumber(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
nums.sort()
for i in range(0, len(nums)-1, 2):
if nums[i] != nums[i+1]:
return nums[i]
return (nums[len(nums)-1])
其实在知道算法之后,在coding上还出现了两个问题:
- nums.sort() 和 sorted(nums) ,前者是针对数组对象的方法,后者是针对数组的函数。sort和sorted要区别~
- 题目要输出的是这个形单影只的数,而不是这个数的位置,读题还是不仔细~
搜索二维矩阵 II
编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target。该矩阵具有以下特性:
- 每行的元素从左到右升序排列。
- 每列的元素从上到下升序排列。
示例:
现有矩阵 matrix 如下:
[
[1, 4, 7, 11, 15],
[2, 5, 8, 12, 19],
[3, 6, 9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]
]
给定 target = 5
,返回 true
。
给定 target = 20
,返回 false
。
这个题目深深诠释了我是如何自己作死的,想不出算法不要紧,陷入死循环的思路才是最可怕的!
我的思路如下
“错误”的根源:我将它看作成一个递归问题
简单的解释一下所以我认为,每次都取最中间的那一行那一列对应的数,与目标数做比较。如果当前数 < 目标数:那么只需要看两个绿色框里的内容,红色部分是不可能有的。
这种思路不是错误的,但使问题变得复杂了的。代码如下:
class Solution(object):
def searchMatrix(self, matrix, target):
"""
:type matrix: List[List[int]]
:type target: int
:rtype: bool
"""
if len(matrix) == 0 or len(matrix[0]) == 0:
return False
idx_0 = {
'left': 0,
'right': len(matrix[0]) - 1,
'low': len(matrix)-1,
'high': 0
}
return self.search(matrix, target, idx_0)
def search(self, matrix, target, idx):
"""
:type matrix: List[List[int]]
:type target: int
:type idx: dic
"""
mid_x = (idx['low'] + idx['high']) / 2
mid_y = (idx['left'] + idx['right']) / 2
#print idx
#print mid_x, mid_y
if (idx['left'] > idx['right'] or idx['high'] > idx['low']):
return False
if (idx['left'] == idx['right'] and
idx['low'] == idx['high'] and
matrix[mid_x][mid_y] != target):
return False
if (matrix[mid_x][mid_y] == target):
return True
if (matrix[mid_x][mid_y] > target):
return (self.search(matrix, target, {
'left': idx['left'],
'right': idx['right'],
'low': mid_x-1,
'high': idx['high']}) |
self.search(matrix, target, {
'left': idx['left'],
'right': mid_y-1,
'low': idx['low'],
'high': mid_x}))
else:
return (self.search(matrix, target, {
'left': mid_y + 1,
'right': idx['right'],
'low': mid_x,
'high': idx['high']}) |
self.search(matrix, target, {
'left': idx['left'],
'right': idx['right'],
'low': idx['low'],
'high': mid_x + 1}))
这个代码是调试了很久才出来的,毕竟递归的算法很容易在编写的时候出现漏洞。
同时在这个过程中也纠正了我对python字典使用方法对误区。可能是之前JS编多了吧,以为python字典的使用是这样的
创建:dic = {a: 1, b: 2} ❌ 调用:dic.a ❌
创建:dic = {'a': 1, 'b': 2} ✅ 调用:dic['a'] ✅
然后好不容易AC了,换来的确是:我的提交执行用时,已经战胜 12.42 % 的 python 提交记录???!!!(黑人问号❓)
实际上比较好想法是这样的
对于这个矩阵,先从左下角开始比较(第0列最下面),如果小于目标数,那第0列可以直接排除;然后看第1列最后一行的数,如果大于目标数,那么最后一行可以直接排除;从第1列倒数第二行的数开始比较, ....
看完为这个想法点?,看来自己的姿势水平还是不够!需要反思下自己的思维模式~
贴上更优方法的代码:
Python (100ms+)
class Solution(object):
def searchMatrix(self, matrix, target):
"""
:type matrix: List[List[int]]
:type target: int
:rtype: bool
"""
if len(matrix) == 0 or len(matrix[0]) == 0:
return False
m = len(matrix)
n = len(matrix[0])
i = m - 1
j = 0
while ( i >= 0 and j < n):
if matrix[i][j] == target:
return True
if matrix[i][j] < target:
j += 1
else:
i -= 1
return False
Java (13ms) ???? 手动?
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int row = matrix.length - 1, col = 0;
while (row < matrix.length && row >= 0 && col < matrix[0].length && col >= 0) {
if (matrix[row][col] == target) return true;
if (matrix[row][col] > target) row--;
else col++;
}
return false;
}
}
(?java代码转载自Leetcode此题的评论内容)
求众数
给定一个大小为 n 的数组,找到其中的众数。众数是指在数组中出现次数大于 ⌊ n/2 ⌋
的元素。
你可以假设数组是非空的,并且给定的数组总是存在众数。
示例 1:
输入: [3,2,3]
输出: 3
示例 2:
输入: [2,2,1,1,1,2,2]
输出: 2
题目比较重点的信息是: 众数是指在数组中出现次数大于 ⌊ n/2 ⌋
的元素。
我的naive思路:
排序 --> 从开始找,在n/2大小的窗口中,一定有首尾相同的,即答案
代码如下
class Solution(object):
def majorityElement(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
nums.sort()
n = len(nums)
for i in range(n/2+1):
if nums[i] == nums[i+n/2]:
return nums[i]
然后看了大神的解答后,发现是真的很naive的做法,问题有二:
1)算法题,尽量还是不要用内置的排序函数,这个我以后会注意
2)就算用了排序,实际上只要求排序后数组最中间的数就是答案了
更好的思路:
用一个cnt来代表积分,刚开始cnt = 1,并且ans = 数组中的第0位置的数。往后扫,
如果:积分没有了(cnt = 0),则cnt 重置为1,且ans变为当前位置的数,继续往后扫
如果:当前位置的数等于 ans:积分+1;当前位置的数不等于 ans:积分-1
其实这个思路理解比较抽象,可以具体化成这样一个例子:有很多个队伍互殴,两个人对战两个人都会死,一共有20个人,那么最坏的情况也就是我方的人有11个人,我们派出9人(敌人一共就这么多)去消灭他们,我们还剩下1个人呢✌️。
最终的代码如下:
class Solution(object):
def majorityElement(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
cnt = 1
ans = nums[0]
for i in range(1, len(nums)):
if cnt == 0:
cnt = 1
ans = nums[i]
continue
if ans == nums[i]:
cnt += 1
else:
cnt -= 1
return ans
合并两个有序数组
给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组。
说明:
- 初始化 nums1 和 nums2 的元素数量分别为 m 和 n。
- 你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。
示例:
输入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6], n = 3
输出: [1,2,2,3,5,6]
刚拿到题目的时候脑海里有两个思路
1. 两数组拼接 --> 排序
2. 开一个额外的数组,长度(m+n),然后通过比较两个数组的数字,从小到大把这个额外的数组填满
第一个解法的复杂性是 O( (n+m)log(n+m) ),而且题目已经说了两个数组是排好顺序的,所以这种算法绝对是一个费时费力不讨好的做法。
第二个解法的计算复杂性是 O(m+n),但是额外开了一个数组,题目说的是直接在nums1里面排序(Do not return anything, modify nums1 in-place instead),所以其实这个方法似乎也不是最好的,但是我没想到那个最好的,只能先实现这个笨方法:
class Solution(object):
def merge(self, nums1, m, nums2, n):
"""
:type nums1: List[int]
:type m: int
:type nums2: List[int]
:type n: int
:rtype: None Do not return anything, modify nums1 in-place instead.
"""
# 先处理一下特殊的情况,两个数列里面有一个是空的
if m == 0:
for i in range(n):
nums1[i] = nums2[i]
return
if n == 0:
return
# 创建一个额外的数组
ans = [0 for i in range(m+n)]
idx1 = 0 # nums1的index
idx2 = 0 # nums2的index
i = 0 # 额外数组的index
while i < m + n and idx2 < n and idx1 < m:
# 如果nums1当前位置的数小于nums2当前位置的数,把此值填到额外数组中,并做相应的位置移动
while i < m + n and nums1[idx1] <= nums2[idx2]:
ans[i] = nums1[idx1]
idx1 += 1
i += 1
# nums1已经填完了
if idx1 >= m:
while idx2 < n:
ans[i] = nums2[idx2]
i += 1
idx2 += 1
for i in range(m + n):
nums1[i] = ans[i]
return
while i < m + n and nums2[idx2] <= nums1[idx1]:
ans[i] = nums2[idx2]
idx2 += 1
i += 1
if idx2 >= n:
while idx1 < m:
ans[i] = nums1[idx1]
i += 1
idx1 += 1
for i in range(m + n):
nums1[i] = ans[i]
return
总体编下来的感觉是——不舒服,相当不舒服,循环真的难写!当一个优雅的程序员果然不是一件容易的事情sigh
submit之后还是通过啦,不过理所当然的只战胜了30%左右的人,看来还是不够优化。
看了评论区,才发现如何去实现 O(1) 的空间复杂度!!!!!其实很简单:
把nums1倒转一下,然后从首部开始填数字
如果coding的话和我的其实差不多,只不过这种呢没有利用额外的空间。
总结了一下,大概我没有想到这种方法的原因是:惯性思维让我潜意识以为需要把 [1, 3, 5] 和 [2,4] 这两个数组合并排序,而不是[1, 3, 5, 0, 0] 和 [2,4]合并排序,所以后边的空位我也想不到原来可以利用呀;其次就是倒转数组,这个思想我也是一时没想到~
第一个错误的版本
你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。
假设你有 n
个版本 [1, 2, ..., n]
,你想找出导致之后所有版本出错的第一个错误的版本。
你可以通过调用 bool isBadVersion(version)
接口来判断版本号 version
是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。
示例:
给定 n = 5,并且 version = 4 是第一个错误的版本。
调用 isBadVersion(3) -> false
调用 isBadVersion(5) -> true
调用 isBadVersion(4) -> true
所以,4 是第一个错误的版本。
class Solution(object):
def firstBadVersion(self, n):
left = 1
right = n
while left <= right:
mid = (left + right) / 2
if isBadVersion(mid):# 防止无限循环
if right == mid:
return right
else:
right = mid
else:
left = mid + 1
return left