算法题:数组

文章介绍了多种使用快慢指针技巧在数组操作中的应用,包括删除重复元素、移除指定元素、移动数组中的零、滑动窗口算法、二分查找、两数之和、反转字符串以及寻找最长回文串,这些方法都强调了原地修改数组和优化时间复杂度的重要性。
摘要由CSDN通过智能技术生成

数组问题中比较常见的快慢指针技巧,是让你原地修改数组。

[26] 删除数组中的重复项

class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
    if len(List) ==0:
        return 0

    slow, fast = 0, 1
    while fast < len(nums):
        # 如果 slow 和 fast 值不同,slow往前走一步
        if nums[slow] != nums[fast]:
            slow += 1
            nums[slow] = nums[fast]
        # 每次 fast 都向后走一步
        fast += 1
    # 数组长度为索引 + 1
    return slow + 1

img

[27]移除元素

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

示例 1:

输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
class Solution:
    def removeElement(self, nums, val):
        slow, fast = 0, 0
        while fast < len(nums):
            if nums[fast] != val:
                nums[slow] = nums[fast]
                slow +=1
            fast += 1
        return slow

[283] 移动零

给你输入一个数组 nums,请你原地修改,将数组中的所有值为 0 的元素移到数组末尾,函数签名如

示例 1:

输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]
class Solution(object):
    def moveZeroes(self, nums):
        """
        :type nums: List[int]
        :rtype: None Do not return anything, modify nums in-place instead.
        """
        slow, fast = 0, 0
        while fast < len(nums):
            if nums[fast] != 0:
                # 交换元素
                nums[slow], nums[fast] = nums[fast], nums[slow]
                # slow 进一步,指向下一个 0
                slow += 1
            fast += 1
        return nums

滑动窗口算法

数组中另一大类快慢指针的题目就是「滑动窗口算法」。

基本框架

# 滑动窗口算法框架
def slidingWindow(s: str, t: str):
    from collections import defaultdict
    need = defaultdict(int)
    window = defaultdict(int)
    for c in t:
        need[c] += 1

    left, right = 0, 0
    valid = 0
    while right < len(s):
        c = s[right]
        # 右移(增大)窗口
        right += 1
        # 进行窗口内数据的一系列更新
        window[c] += 1
        
        while window needs shrink:
            d = s[left]
            # 左移(缩小)窗口
            left += 1
            # 进行窗口内数据的一系列更新

这里先简单记录一下,后面在会具体讲到。

二分查找

这里只写最简单的二分算法,旨在突出它的双指针特性,后面会有具体的查找算法合集:

class Solution(object):
    def search(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: int
        """
        left = 0
        right = len(nums)

        # 左闭右开 [left, right)
        while left < right:
            mid = left + (right - left) // 2

            if target == nums[mid]:
                return mid
            # 如果落在左区间
            elif target < nums[mid]:
                right = mid    # 右边界为开区间,不能取到右边界的值,所以不能减一缩小取值范围
            # 如果落在右区间
            else:
                left = mid + 1 # 左边界为闭区间,已经取到值了,所以可以进一步缩小范围
        # 如果没找到返回 -1
        return -1

[167] 两数之和

给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数 target 的两个数。

示例 1:

输入:numbers = [2,7,11,15], target = 9
输出:[1,2]
解释:2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。返回 [1, 2] 。

可能我们一下子就想到了 用两个循环,这种解法时间复杂度为O(n*n),这里我们需要更好的算法:

# 采用左右指针的方式向中间逼近,复杂度为 O(n)级别
class Solution(object):
    def twoSum(self, numbers, target):
        """
        :type numbers: List[int]
        :type target: int
        :rtype: List[int]
        """
        left, right = 0, len(numbers) - 1

        while left < right: # 注意这里不能用 <= , 等于的话一个元素可能用了两次
            sum = numbers[left] + numbers[right]
            if sum == target:
                return [left + 1, right + 1]
            elif sum < target:
               left += 1
            else:
                right -= 1
        return [-1, -1]

[344 ] 反转数组

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。

不要给另外的数组分配额外的空间,你必须**原地修改输入数组**、使用 O(1) 的额外空间解决这一问题。

示例 1:

输入:s = ["h","e","l","l","o"]
输出:["o","l","l","e","h"]
class Solution(object):
    def reverseString(self, s):
        """
        :type s: List[str]
        :rtype: None Do not return anything, modify s in-place instead.
        """
        left, right = 0, len(s)-1
        while left < right:
            s[left], s[right] = s[right], s[left]
            left += 1
            right -= 1

回文串

同样的左右指针还能解决回文串的问题:

首先明确一下,回文串就是正着读和反着读都一样的字符串。

比如说字符串 abaabba 都是回文串,因为它们对称,反过来还是和本身一样;反之,字符串 abac 就不是回文串。

def isPalindrome(s: str) -> bool:
    # 一左一右两个指针相向而行
    left, right = 0, len(s) - 1
    while left < right:
        if s[left] != s[right]:
            return False
        left += 1
        right -= 1
    return True

[5] 最长回文串

给你一个字符串 s,找到 s 中最长的回文子串。

如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。

示例 1:

输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。

找回文串的难点在于,回文串的的长度可能是奇数也可能是偶数,解决该问题的核心是从中心向两端扩散的双指针技巧
如果回文串的长度为奇数 例如 aba,则它有一个中心字符;如果回文串的长度为偶数abba,则可以认为它有两个中心字符。所以我们可以先实现这样一个函数:

# 在 s 中寻找以 s[l] 和 s[r] 为中心的最长回文串
def _palindrome (self, s, l, r):
    # 边界判断
    while (l >= 0 and r < len(s) and s[l] == s[r]):
        l -= 1
        r += 1
    return s[l + 1 : r]

这样,如果输入相同的 lr,就相当于寻找长度为奇数的回文串,如果输入相邻的 lr,则相当于寻找长度为偶数的回文串。
那么回到最长回文串的问题,解法的大致思路就是:

for 0 <= i < len(s):
    找到以 s[i] 为中心的回文串
    找到以 s[i] 和 s[i+1] 为中心的回文串
    更新答案
class Solution(object):
    def longestPalindrome(self, s):
        """
        :type s: str
        :rtype: str
        """
        res = ""
        for i in range(len(s)):
            # 以 s[i] 为中心的最长回文子串,例如 aba
            s1 = self._palindrome(s, i, i)
            # 以 s[i] 和 s[i+1] 为中心的最长回文子串 abba
            s2 = self._palindrome(s, i, i+1)

            res = res if len(res) > len(s1) else s1
            res = res if len(res) > len(s2) else s2
        return res

    # 在 s 中寻找以 s[l] 和 s[r] 为中心的最长回文串
    def _palindrome (self, s, l, r):
        # 边界判断
        while (l >= 0 and r < len(s) and s[l] == s[r]):
            l -= 1
            r += 1
        return s[l + 1 : r]

你应该能发现最长回文子串使用的左右指针和之前题目的左右指针有一些不同:之前的左右指针都是从两端向中间相向而行,而回文子串问题则是让左右指针从中心向两端扩展。不过这种情况也就回文串这类问题会遇到,所以我也把它归为左右指针了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值