剑指 Offer 04. 二维数组中的查找
解题思路1:遍历二分查找
class Solution:
def findNumberIn2DArray(self, matrix: List[List[int]], target: int) -> bool:
n = len(matrix)
if n == 0: return False
m = len(matrix[0])
if m == 0: return False
for i in range(n):
res = matrix[i]
left, right = 0, m-1
while left <= right:
mid = (left+right)//2
if res[mid] == target: return True
elif res[mid] > target: right = mid - 1
else : left = mid + 1
return False
解题思路2:
若使用暴力法遍历矩阵 matrix ,则时间复杂度为 O(NM)。暴力法未利用矩阵 “从上到下递增、从左到右递增” 的特点,显然不是最优解法。
如下图所示
我们将矩阵逆时针旋转 45° ,并将其转化为图形式,发现其类似于 二叉搜索树 ,即对于每个元素,其左分支元素更小、右分支元素更大。因此,通过从 “根节点” 开始搜索,遇到比 target 大的元素就向左,反之向右,即可找到目标值 target 。
“根节点” 对应的是矩阵的 “左下角” 和 “右上角” 元素,本文称之为 标志数 ,以 matrix 中的 左下角元素 为标志数 flag ,则有:
1.若 flag > target ,则 target 一定在 flag 所在行的上方 ,即 flag 所在行可被消去
2.若 flag < target ,则 target 一定在 flag 所在列的右方 ,即 flag 所在列可被消去
算法流程:
1、从矩阵 matrix 左下角元素(索引设为 (i, j) )开始遍历,并与目标值对比:
当 matrix[ i ][ j ] > target 时,执行 i-- ,即消去第 i 行元素;
当 matrix[ i ] [j ] < target 时,执行 j++ ,即消去第 j 列元素;
当 matrix[ i ][ j ] = target 时,返回 true ,代表找到目标值。
2、若行索引或列索引越界,则代表矩阵中无目标值,返回 false
(每轮 i 或 j 移动后,相当于生成了“消去一行(列)的新矩阵”, 索引(i,j) 指向新矩阵的左下角元素(标志数),因此可重复使用以上性质消去行(列))
class Solution:
def findNumberIn2DArray(self, matrix: List[List[int]], target: int) -> bool:
i, j = len(matrix) - 1, 0
while i >= 0 and j < len(matrix[0]):
if matrix[i][j] == target: return True
elif matrix[i][j] > target: i -= 1
else: j += 1
return False
剑指 Offer 11. 旋转数组的最小数字
解题思路1:最简单容易想到的思路:历遍
class Solution:
def minArray(self, numbers: List[int]) -> int:
i = 0
while i < len(numbers) - 1:
if numbers[i] > numbers[i+1]: return numbers[i+1]
else: i += 1
return numbers[0]
解题思路2:二分查找
如下图所示,寻找旋转数组的最小元素即为寻找右排序数组的首个元素 nums[x] ,称 x 为 旋转点 。
算法流程:
1.初始化: 声明 i, j 双指针分别指向 nums 数组左右两端
。。。。。。。。。。。。。。。。。。。。。。。。。
2.循环二分: 设 m = (i + j) / 2 为每次二分的中点( “/” 代表向下取整除法,因此恒有 i ≤ m < j ),可分为以下三种情况:
当 nums[m] > nums[j]时: m 一定在左排序数组中,即旋转点 x 一定在 [m + 1, j] 闭区间内,因此执行 i = m + 1
当 nums[m] < nums[j] 时: m 一定在右排序数组中,即旋转点 x 一定在[i, m] 闭区间内,因此执行 j = m
当 nums[m] = nums[j] 时: 无法判断 mm 在哪个排序数组中,即无法判断旋转点 x 在 [i, m] 还是 [m + 1, j] 区间中。解决方案: 执行 j = j - 1 缩小判断范围
。。。。。。。。。。。。。。。。。。。。。。。。
3.返回值: 当 i = j 时跳出二分循环,并返回旋转点的值 nums[i]
注:左端点往右区间第一位即旋转点靠,右端点往右区间最左侧靠即也是往旋转点靠,最终两个端点重合,循环停止
class Solution:
def minArray(self, numbers: List[int]) -> int:
left, right = 0, len(numbers)-1
while left < right:
m = left + (right-left)//2
if numbers[m] > numbers[right]:
left = m + 1
elif numbers[m] < numbers[right]:
right = m
else:
right -= 1
return numbers[left]
剑指 Offer 50. 第一个只出现一次的字符
解题思路:哈希表
1.遍历字符串 s ,使用哈希表统计各字符数量是否 > 1
2.再遍历字符串 s ,在哈希表中找到首个数量为 1 的字符并返回
算法流程:
1.初始化,字典记为dic
。。。。。。。。。。。。。。。。。。。。。。。。。。
2.字符统计: 遍历字符串 s 中的每个字符 i:
a.若 dic 中不包含键(key) i :则向 dic 中添加键值对 (i, True) ,代表字符 i 的数量为 1
b.若 dic 中包含键(key) i :则修改键 i 的键值对为 (i, False) ,代表字符 i 的数量 > 1
。。。。。。。。。。。。。。。。。。。。。。。。。。
3.查找数量为 1 的字符: 遍历字符串 s 中的每个字符 i:
若 dic中键 i 对应的值为 True ,则返回 i
4.若算法没结束,返回 ’ ’ ,代表字符串无数量为 1 的字符
class Solution:
def firstUniqChar(self, s: str) -> str:
dic = {}
for i in s:
dic[i] = i not in dic
for j in s:
if dic[j]:
return j
return ' '