LC打怪升级录-day1 binary search二分查找

目录

二分查找基础知识

leetcode 704

leetcode 27 

暴力解法 Brute Force method

双指针法


二分查找基础知识

01. 二分查找知识(一) | 算法通关手册(LeetCode)

二分查找的基本算法思想为:通过确定目标元素所在的区间范围,反复将查找范围减半,直到找到元素或找不到该元素为止。

  1. 初始化:首先,确定要查找的有序数据集合。可以是一个数组或列表,确保其中的元素按照升序或者降序排列。

leetcode 704 二分查找

题目:Given an array of integers nums which is sorted in ascending order, and an integer target, write a function to search target in nums. If target exists, then return its index. Otherwise, return -1.

You must write an algorithm with O(log n) runtime complexity.

Constraints:

  • 1 <= nums.length <= 104
  • -104 < nums[i], target < 104
  • All the integers in nums are unique.
  • nums is sorted in ascending order.

读题:target可以出现在任何一个位置上,也可以不存在于list中。 

需要双指针考虑全range:最左,最右和中间,各一个指针。

因为数组是从小到大排列好的,所以我们只需要对比middle number和target来移动整体调查阈值(是向左移动还是右移动)。

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        left,right = 0, len(nums)-1
        while left <= right:
            middle =left+(right-left)//2
            if nums[middle]>target:
                right = middle -1 # target在左区间,所以[left, middle - 1]

            elif nums[middle]<target:
                left=middle+1 # target在右区间,所以[middle + 1, right]
            else:
                return middle # 数组中找到目标值,直接返回下标
        return -1 # 未找到目标值
思路 1:复杂度分析
  • 时间复杂度:O(logn)
  • 空间复杂度:O(1)

leetcode 27 

Remove Element 

Given an integer array nums and an integer val, remove all occurrences of val in nums in-place. The order of the elements may be changed. Then return the number of elements in nums which are not equal to val.

Consider the number of elements in nums which are not equal to val be k, to get accepted, you need to do the following things:

  • Change the array nums such that the first k elements of nums contain the elements which are not equal to val. The remaining elements of nums are not important as well as the size of nums.
  • Return k.

暴力解法 Brute Force method

这个题目暴力的解法就是两层for循环,一个for循环遍历数组元素 ,第二个for循环更新数组。

def removeElement(self, nums: List[int], val: int) -> int:
        n = len(nums)
        i = 0
        while i < n:
            if nums[i] == val:
                for j in range(i + 1 , n):
                    nums[j - 1] = nums[j]
                i -= 1
                n -= 1
            i += 1
        return n

双指针法

双指针法(快慢指针法): 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。

定义快慢指针

  • 快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
  • 慢指针:指向更新 新数组下标的位置
def removeElement(self, nums: List[int], val: int) -> int:
        # 快指针遍历元素
        fast = 0
        # 慢指针记录位置
        slow = 0
        for fast in range(len(nums)):
            if nums[fast] != val:
                nums[slow] = nums[fast]
                slow += 1
        return slow

  • 关于二分法和移除元素的共性思考
    这两题之间有点类似的,他们都是在不断缩小 left 和 right 之间的距离,每次需要判断的都是 left 和 right 之间的数是否满足特定条件。对于「移除元素」这个写法本质上还可以理解为,我们拿 right 的元素也就是右边的元素,去填补 left 元素也就是左边的元素的坑,坑就是 left 从左到右遍历过程中遇到的需要删除的数,因为题目最后说超过数组长度的右边的数可以不用理,所以其实我们的视角是以 left 为主,这样想可能更直观一点。用填补的思想的话可能会修改元素相对位置,这个也是题目所允许的。

给自己的一个commitment: 希望2024年能越挫越勇,面对问题直面而上,克服对算法和DSA的恐惧,为之后求职必经的oa之路做好准备,加油!

33.搜索旋转排序数组

排序的时间复杂度就是O(n).

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        left,right=0,len(nums)-1
        while left <= right:
            mid = (left+right)//2
            if nums[mid]==target:
                return mid
            elif nums[left]<= nums[mid]:
                if nums[left]<=target < nums[mid]:
                    right = mid-1
                else:
                    left= mid+1
            else:
                    if nums[mid]<target <= nums[right]:
                        left=mid+1
                    else:
                        right=mid-1
        return -1 
        

5. 最长回文子串

LeetCode 第 5 题:“最长回文子串”题解 | LeetCode 题解

“最长回文子串”题解

方法一:暴力匹配 (Brute Force)

暴力解法虽然时间复杂度高,但是思路清晰、编写简单,因为编写的正确性高,完全可以使用暴力匹配算法检验我们编写的算法的正确性

class Solution:
    def longestPalindrome(self, s):
        size = len(s)
        if size == 0:
            return ''

        # 至少是 1
        longest_palindrome = 1
        longest_palindrome_str = s[0]

        for i in range(size):
            palindrome_odd, odd_len = self.__center_spread(s, size, i, i)
            palindrome_even, even_len = self.__center_spread(s, size, i, i + 1)

            # 当前找到的最长回文子串
            cur_max_sub = palindrome_odd if odd_len >= even_len else palindrome_even
            if len(cur_max_sub) > longest_palindrome:
                longest_palindrome = len(cur_max_sub)
                longest_palindrome_str = cur_max_sub

        return longest_palindrome_str

    def __center_spread(self, s, size, left, right):
        """
        left = right 的时候,此时回文中心是一条线,回文串的长度是奇数
        right = left + 1 的时候,此时回文中心是任意一个字符,回文串的长度是偶数
        """
        l = left
        r = right

        while l >= 0 and r < size and s[l] == s[r]:
            l -= 1
            r += 1
        return s[l + 1:r], r - l - 1

复杂度分析:

  • 时间复杂度:O(N​2​​)
  • 空间复杂度:O(1)

方法三:动态规划(推荐)

推荐理由:暴力解法太 naive,中心扩散不普适,Manacher 就更不普适了,是专门解这个问题的方法。而用动态规划是可以帮助你举一反三的方法。

class Solution:
    def longestPalindrome(self, s: str) -> str:
        size = len(s)
        if size <= 1:
            return s
        # 二维 dp 问题
        # 状态:dp[l,r]: s[l:r] 包括 l,r ,表示的字符串是不是回文串
        # 设置为 None 是为了方便调试,看清楚代码执行流程
        dp = [[False for _ in range(size)] for _ in range(size)]

        longest_l = 1
        res = s[0]

        # 因为只有 1 个字符的情况在最开始做了判断
        # 左边界一定要比右边界小,因此右边界从 1 开始
        for r in range(1, size):
            for l in range(r):
                # 状态转移方程:如果头尾字符相等并且中间也是回文
                # 在头尾字符相等的前提下,如果收缩以后不构成区间(最多只有 1 个元素),直接返回 True 即可
                # 否则要继续看收缩以后的区间的回文性
                # 重点理解 or 的短路性质在这里的作用
                if s[l] == s[r] and (r - l <= 2 or dp[l + 1][r - 1]):
                    dp[l][r] = True
                    cur_len = r - l + 1
                    if cur_len > longest_l:
                        longest_l = cur_len
                        res = s[l:r + 1]
            # 调试语句
            # for item in dp:
            #     print(item)
            # print('---')
        return res

动态规划是解决某些类型问题的一种方法,特别是那些可以分解为重复子问题的问题。动态规划通过组合子问题的解来解决主问题,避免了重复计算子问题,从而提高了效率。

在这个“最长回文子串”问题中,动态规划的思想被用来逐步构建回文子串的解决方案。下面我会逐步解释代码中的动态规划过程:

1. 状态定义

在这个问题中,动态规划表 dp 是一个二维数组,其中 dp[l][r] 表示字符串 s 从索引 l 到索引 r(包含 lr)的子串是否是回文串。True 表示是回文串,False 表示不是。

2. 初始化

动态规划表 dp 的所有元素最初都被设置为 False。单个字符总是回文串,但在这个问题的代码中,单字符回文是通过初始化 longest_l1ress[0] 来处理的,而不是在 dp 表中明确设置。

3. 状态转移方程

dp[l][r] = True 的条件是:

  • s[l]s[r] 必须相等,即子串的头尾字符必须相同。
  • 子串去掉头尾字符后(即 s[l+1:r]),这个新子串要么是一个长度不超过 1 的字符串(这意味着它不需要进一步检查就是回文),要么是另一个回文串(即 dp[l+1][r-1]True)。

4. 遍历顺序

这里的遍历是按右边界 r1 开始逐渐扩大,左边界 l 在每个右边界 r 的情况下从 0 遍历到 r。这保证了在计算 dp[l][r] 时,所有需要的 dp[l+1][r-1] 已经被计算过了,满足了动态规划的“无后效性”,即当前状态只依赖于之前的状态。

5. 更新结果

在每次发现一个更长的回文子串时,更新 longest_lres 来记录当前最长的回文子串的长度和内容。

通过上述步骤,这段代码能够找到并返回输入字符串 s 中的最长回文子串。动态规划在这个问题中的妙处在于它避免了重复检查子串是否是回文,从而大大提高了效率。

  • 9
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值