目的
- 理解数组的 基本概念 及其 操作方式;
- 理解
二维数组
的基本概念,熟悉二维数组的使用; - 了解
字符串的概念
以及字符串所具有的不同特性; - 理解字符串匹配中的
KMP 算法
; - 能够运用
双指针
解决实际问题(迭代方向相同、相反两种情况)。
题目
1) 寻找数组的中心索引
定义: 数组中心索引的左侧所有元素相加的和等于右侧所有元素相加的和。(如果数组不存在中心索引,那么我们应该返回 -1。如果数组有多个中心索引,那么我们应该返回最靠近左边的那一个)
class Solution:
def pivotIndex(self, nums: List[int]) -> int:
# 计算总和
total_cnt = 0
for i in range(len(nums)):
total_cnt += nums[i]
# 从索引1开始计算左边的和,并用通过总和计算右边的和
left_cnt = 0
for i in range(len(nums)):
if i > 0:
left_cnt += nums[i-1]
right_cnt = total_cnt - nums[i] - left_cnt
if left_cnt == right_cnt:
return i
return -1
总结:需要注意的是,索引0 和 -1(这里表示为最后一个元素的索引) 也能做中心索引。题目似乎没有说清楚么~~~
2) 搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。你可以假设数组中无重复元素。
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
for i in range(len(nums)):
# 如果找到目标则返回索引, 或者大于目标则插入
if nums[i] >= target:
return i
return len(nums) # 否则在数组末尾追加
3) 合并区间
给出一个区间的集合,请合并所有重叠的区间。
class Solution:
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
res = [] # res保存了互不重叠的区间
intervals = sorted(intervals, key=lambda x: x[0])
for interval in intervals:
if res == [] or interval[0] > res[-1][1]:
res.append(interval)
else:
res[-1] = [min(res[-1][0],interval[0]),max(res[-1][1],interval[1])]
return res
总结:取值区间判断是否重叠有2种情况:a) 区间A和区间B是包含关系; b) 区间A和区间B含有交集 。解题思路为:
- 首先按区间的起始位置进行排序;
- 如果结果数组为空,或者当前区间的起始位置大于结果数组中最后区间的终止位置,则不合并; 否则合并结果数组的最后一个区间,合并后新的区间的取值范围是:[min(left_A, left_B) , max(rigth_A,right_B)]。
4) 旋转矩阵
给你一幅由 N × N 矩阵表示的图像,其中每个像素的大小为 4 字节。请你设计一种算法,将图像旋转 90 度。不占用额外内存空间能否做到?
首先能想到的是使用额外空间的做法,首先将矩阵按行逆序,之后将每一列调整成每一行即可。
class Solution:
def rotate(self, matrix: List[List[int]]) -> None:
"""
Do not return anything, modify matrix in-place instead.
"""
n = len(matrix)
res = [[0 for i in range(n)] for i in range(n)]
for i,x in enumerate(matrix[::-1]):
for j in range(n):
res[j][i] = x[j]
for i in range(n):
for j in range(n):
matrix[i][j] = res[i][j]
旋转方法: 顺时针90°旋转=对角线(左上-右下)翻转+左右翻转
class Solution:
def rotate(self, matrix: List[List[int]]) -> None:
"""
Do not return anything, modify matrix in-place instead.
"""
n = len(matrix)
# 对角线(左上-右下)翻转
for i in range(n-1):
for j in range(i,n):
matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]
# 左右翻转
j = 0
while j < (n-1)/2:
for i in range(n):
matrix[i][j], matrix[i][n-1-j] = matrix[i][n-1-j], matrix[i][j]
j += 1
5) 零矩阵
编写一种算法,若M × N矩阵中某个元素为0,则将其所在的行与列清零。
class Solution:
def setZeroes(self, matrix: List[List[int]]) -> None:
"""
Do not return anything, modify matrix in-place instead.
"""
m, n = len(matrix), len(matrix[0])
row_idxs, column_idxs = set(), set()
for i in range(m):
for j in range(n):
if matrix[i][j] == 0:
row_idxs.add(i)
column_idxs.add(j)
for i in range(m):
if i in row_idxs:
matrix[i] = [0]*n
for j in range(n):
if j in column_idxs:
matrix[i][j] = 0
6) 对角线遍历
给定一个含有 M x N 个元素的矩阵(M 行,N 列),请以对角线遍历的顺序返回这个矩阵中的所有元素,对角线遍历如下图所示。
class Solution:
def findDiagonalOrder(self, matrix: List[List[int]]) -> List[int]:
m, n = len(matrix), len(matrix[0])
res = []
for i in range(m+n-1): #遍历对角线(m+n-1)次
ln = []
for j in range(i+1): #遍历当前对角线的元素,横坐标+纵坐标=对角线索引
if j < m and i-j < n: # 判断是否超出矩阵索引
ln.append(matrix[j][i-j])
if i % 2 == 0: # 如果对角线索引为偶数,需要对当前对角线元素逆序
ln = ln[::-1]
res.extend(ln)
return res
7) 最长公共前缀
编写一个函数来查找字符串数组中的最长公共前缀。如果不存在公共前缀,返回空字符串 “”。
class Solution:
def longestCommonPrefix(self, strs: List[str]) -> str:
if strs == [] : return ''
res = ''
min_str, max_str = min(strs), max(strs) #比较ASCII
for i in range(len(min_str)):
if min_str[i] != max_str[i]:
return min_str[:i]
return min_str
总结: 利用ASCII码进行比较,找到ASCII最大和最小的字符串的公共部分。
反转字符串
要求O(1) 的额外空间。
(使用双指针, 迭代方向相反)
class Solution:
def reverseString(self, s: List[str]) -> None:
"""
Do not return anything, modify s in-place instead.
"""
i, j = 0, len(s)-1
while i < j:
s[i], s[j] = s[j], s[i]
i += 1
j -= 1
两数之和 II - 输入有序数组
返回的下标值(index1 和 index2)不是从零开始的。
(使用双指针, 迭代方向相反)
class Solution:
def twoSum(self, numbers: List[int], target: int) -> List[int]:
i, j = 0, len(numbers)-1
while i < j:
add_res = numbers[i]+numbers[j]
if add_res == target:
return i+1, j+1
elif add_res < target:
i += 1
else:
j -= 1
移除元素
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
(使用双指针,迭代方向相同)
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
i, j = 0, 0
while i < len(nums):
if nums[i] != val:
nums[j] = nums[i]
j += 1
i += 1
return j
最大连续1的个数
给定一个二进制数组, 计算其中最大连续1的个数。
方法1: 首先想到一种非双指针的方法,不断更新当前连续1 cnt、最大连续1 maxn的值。
class Solution:
def findMaxConsecutiveOnes(self, nums: List[int]) -> int:
maxn, cnt, i = 0, 0, 0
for i in range(len(nums)):
if nums[i] == 1:
cnt += 1
else:
maxn = max(maxn,cnt)
cnt = 0
return max(maxn,cnt)
方法2: 双指针,迭代方向相同,使用慢指针、快指针分别记录上一个不为0的位置、当前位置。
class Solution:
def findMaxConsecutiveOnes(self, nums: List[int]) -> int:
slow, fast, maxn = -1, 0, 0
for fast in range(len(nums)):
if nums[fast] != 1:
slow = fast
else:
maxn = max(maxn,fast-slow)
return maxn
长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的连续子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。
eg:
输入:s = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
方法1: 滑窗法, 时间复杂度O(n) 。
用指针left、right分别指向滑窗的左右两侧,当找到第一个滑窗的left、right时(滑窗内元素之和>=s) ,left向右移动,直到滑窗内元素之和刚好<s, 此时就找到了下一个滑窗的left, 接下来,right不断加1 ,直到满足滑窗内元素之和>=s, 就找到了下一个滑窗的right。
关于时间复杂度:虽然看起来是两层循环,外层循环的时间复杂度是O(n),但是仔细分析一下代码后会发现:内层循环的时间复杂度并不是O(n)。内层循环中指针start在整个程序中最多移动n次,内层循环的平均时间复杂度是O(1),因此程序整体的时间复杂度是O(n)。
class Solution:
def minSubArrayLen(self, s: int, nums: List[int]) -> int:
left, res, cur_add = 0, float('inf'), 0
for right in range(len(nums)):
cur_add += nums[right]
while cur_add >= s:
res = min(res,right-left+1)
cur_add -= nums[left]
left += 1
res = res if res != float("inf") else 0
return res