算法练习第48题-寻找比目标字母大的最小字母

576 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第3天,点击查看活动详情

一、题目

给你一个排序后的字符列表 letters ,列表中只包含小写英文字母。另给出一个目标字母 target,请你寻找在这一有序列表里比目标字母大的最小字母。

在比较时,字母是依序循环出现的。举个例子:

如果目标字母 target = 'z' 并且字符列表为 letters = ['a', 'b'],则答案返回 'a'  

示例 1:

输入: letters = ["c", "f", "j"],target = "a"
输出: "c"
示例 2:

输入: letters = ["c","f","j"], target = "c"
输出: "f"
示例 3:

输入: letters = ["c","f","j"], target = "d"
输出: "f"

作者:力扣 (LeetCode) 链接:leetcode.cn/leetbook/re… 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

二、思路

暴力解法

  • 由题意可知,给到一个list,判断target在list中比target大的最小字母
  • 字符可以直接判断,例如 a < b => true
  • 简单的做法就是直接一个for循环,循环出的每个参数,与target判断,如果当前i 大于target,那么就返回这个值
  • 同时要注意到比较时字母是循环的,例如题目举的例子,
  • 因此如果没有返回值,则返回list的第一个值
  • 代码上的优化问题,这里有一个next()函数
  • 两个参数一个是可迭代对象,一个是默认兑现,如果迭代对象没有返回值的时候,返回默认值

二分思路

  • 首先,定义左右区间, left 等于0 ,right等于 len - 1,中间值是mid = left + ((right - left)//2)
  • 循环条件是left 小于 right
  • 先寻找区间,是在左区间还是在右区间
  • 如果 letters[mid] > target 那么说明值存在left 到mid之间
  • 那么就重新定义右区间,right = mid
  • 如果 letters[mid] > target 为 False,那么说明左区间的值小了,要缩小区间, left = mid + 1
  • 当 left 小于right 不成立的时候, letters[left]就是最接近的值
  • 同时要考虑一个问题,就是如果如题目上举的例子的情况,那么就要再判断一下,
  • 判断如果target 大于等于 letters[right], 返回 letters[0]

二分查找

三、代码

暴力解法

class Solution:
    def nextGreatestLetter(self, letters: List[str], target: str) -> str:
        """"
            暴力解法
            由题意可知,给到一个list,判断target在list中比target大的最小字母
            字符可以直接判断,例如 a < b => true
            简单的做法就是直接一个for循环,循环出的每个参数,与target判断,如果当前i 大于target,那么就返回这个值
            同时要注意到比较时字母是循环的,例如题目举的例子,
            因此如果没有返回值,则返回list的第一个值

            代码上的优化问题,这里有一个next()函数
            两个参数一个是可迭代对象,一个是默认兑现,如果迭代对象没有返回值的时候,返回默认值
            

        """
        // 常规写法
        for i in letters:
             if i > target:
                 return i
        return letters[0]
        // 优化后的写法
        return next((i for i in letters if i > target), letters[0])

二分解法

class Solution:
    def nextGreatestLetter(self, letters: List[str], target: str) -> str:
        """"
        首先,定义左右区间, left 等于0 ,right等于 len - 1,中间值是mid = left + ((right - left)//2)
        循环条件是left 小于 right
        
        先寻找区间,是在左区间还是在右区间
        如果 letters[mid] > target 那么说明值存在left 到mid之间 
        那么就重新定义右区间,right = mid
        如果  letters[mid] > target 为 False,那么说明左区间的值小了,要缩小区间, left = mid + 1
        当 left 小于right 不成立的时候,  letters[left]就是最接近的值
        
        同时要考虑一个问题,就是如果如题目上举的例子的情况,那么就要再判断一下,
        判断如果target 大于等于 letters[right], 返回 letters[0]
        """
        left = 0
        right = len(letters) - 1
        if target >= letters[right]:
            return letters[0]
        while left < right:
            mid = left + ((right-left)//2)
            if letters[mid] > target:
                right = mid
            else:
                left = mid + 1
        return letters[left]

四、测试结果

暴力解法

image.png

二分查找

image.png

五、番外-bisect使用

在二分查找中常用bisect库

文档地址:docs.python.org/3.10/librar…

bisect

bisect.bisect(列表, 元素): 返回元素在列表中顺序插入的位子,如果list中已存在元素,则返回在该元素的最后一个位置之后

import bisect
list1, num1 = [1,3,5,7,9], 2

// 例子1
int1 = bisect.bisect(list1,num1)
print(int1, type(int1), list1)

// 例子2
list2, num2= [1,2,3,5,7,9], 2
int2 = bisect.bisect_right(list2, num2)
print(int2, type(int2), list2)

image.png

image.png

对比两张图可看到结果

bisect_left

bisect.bisect_left(列表, 元素): 返回元素在列表中顺序插入的位子,如果list中已存在元素,则返回在该元素的第一个位置

// 例子1
list1, num1 = [1,3,5,7,9], 2
int1 = bisect.bisect_left(list1,num1)
print(int1, type(int1), list1)

// 例子2
list2, num2= [1,2,3,5,7,9], 2
int2 = bisect.bisect_left(list2, num2)
print(int2, type(int2), list2)

image.png 看到下标,能看出,插入的位置的区别

bisect_right

bisect_right(列表, 元素): 返回元素在列表中顺序插入的位子,如果list中已存在元素,则返回在该元素的最后一个位置之后

// 例子1
list1, num1 = [1, 3, 5, 7, 9], 2
int1 = bisect.bisect_right(list1, num1)
print(int1, type(int1), list1)

// 例子2
list2, num2 = [1, 2, 3, 5, 7, 9], 2
int2 = bisect.bisect_right(list2, num2)
print(int2, type(int2), list2)

image.png 依然是对比他们的下标位置

insort

bisect.insort(列表, 元素): 无返回值,在列表中插入元素

image.png

image.png

image.png 第二张图,告诉了当前是没有返回值的,第三张图说明了插入的位置

insort_left

bisect.insort_left(列表, 元素): 无返回值,在列表中插入元素

// 例子1
list1, num1 = [1, 3, 5, 7, 9], 2
int1 = bisect.insort_left(list1, num1)
print(int1, list1)
// 例子2
list2, num2 = [1, 2, 3, 5, 7, 9], 2
int2 = bisect.insort_left(list2, num2)
print(int2, list2)

image.png

insort_right

bisect.insort_right(列表, 元素): 无返回值,在列表中插入元素

image.png

image.png

总结

返回元素要插入位置中:

  • bisect与bisect_right返回的位置相同,如果存在元素中,返回的是该元素的最后一个位置之后
  • bisect_left 返回的位置,如果存在元素,返回的是该元素的第一个位置
  • insort_left与insort_right:插入的位置,left在前,right在后
  • insort_right与insort的插入方式相同

二分查找优化

  • 两种情况的判断,
  • 第一种是target在list中,那么就通bisect_right找到要插入的位置,然后在通过返回的这个下标,拿到相邻的最新小字母
  • 第二种是target不再list中,那么就直接的返回letters[0]
left, right = 0, len(letters) - 1
print(letters[-1])
if target < letters[-1]:
    print(letters[bisect.bisect_right(letters, target)])
    return letters[bisect.bisect_right(letters, target)]
else:
    print(letters[0])
    return letters[0]

优化的写法

return letters[bisect.bisect_right(letters, target)] if target < letters[-1] else letters[0]

测试结果

image.png