leetcode - 数组和字符串

本文深入解析数组和字符串的基础概念及操作,涵盖中心索引查找、搜索插入位置、合并区间等经典算法,并探讨KMP算法在字符串匹配中的应用。同时,通过实战案例讲解旋转矩阵、零矩阵、对角线遍历、最长公共前缀等问题的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目的

  • 理解数组的 基本概念 及其 操作方式;
  • 理解 二维数组 的基本概念,熟悉二维数组的使用;
  • 了解 字符串的概念以及字符串所具有的不同特性;
  • 理解字符串匹配中的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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值