Task03查找1

搜索插入位置

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

你可以假设数组中无重复元素。

示例 1:

输入: [1,3,5,6], 5
输出: 2
示例 2:

输入: [1,3,5,6], 2
输出: 1
示例 3:

输入: [1,3,5,6], 7
输出: 4
示例 4:

输入: [1,3,5,6], 0
输出: 0

解题思路:二分查找
定义左侧下标left,右侧下标right,计算中间下标mid
每次根据nums[mid]和target之间的大小进行判断,相等则直接返回下标,小于则left右移,大于则right左移
查找结束如果没有相等则返回left作为插入位置

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

二分查找模板

class Solution:
    def firstBadVersion(self, arr):
        # 第一点
        lo, hi = 0, len(arr)-1
        while lo < hi:
            # 第二点
            mid = (lo+hi) // 2
            # 第三点
            if f(x):
                lo = mid + 1
            else:
                hi = mid
        return lo

快乐数

编写一个算法来判断一个数 n 是不是快乐数。

「快乐数」定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。如果 可以变为 1,那么这个数就是快乐数。

如果 n 是快乐数就返回 True ;不是,则返回 False 。

示例:
输入:19
输出:true
解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1

解题思路:没啥思路呢,感觉比较需要数学思维。
可能需要一个循环求解,那什么时候循环结束呢,想到三种情况:
1.过程转换后一个数变为1,这个数是快乐数,返回True
2.过程转换后又出现相同的数,说明进入了死循环,返回False;至于检查是否有相同的数可以想到使用集合set()
3.一直运行,直到无穷大;这一步比较麻烦,好像没什么方法可以知道这个数最终是快乐数,还是会一直计算下去,查阅题解后发现不用考虑这种情况,理由如下:
我们思考:每一位数的最大数字的下一个数字是多少?比如说,9的下一个数为81(9的平方),99的下一个数位162(9的平方+9的平方),999的下一个数为243(9的平方+9的平方+9的平方),也就是说,对于一个三位数的数字,它不可能大于243,就算是最坏的情况,也是在小于243的所有数字上循环,最终重复到一个数字本身,并不会无限期进行下去,因此可以排除第三种情况。
下面思考转换过程,题目要求将每一位数字平方后相加,也就是数位分离后求平方和,这里可以直接使用divmod(a,b),返回的两个数值分别为a//b,a%b;然后再使用一个哈希集合判断一个数字是否出现过;最后过程转换结束时判断结果是否等于1。

class Solution:
    def isHappy(self, n: int) -> bool:
        #获取下一个数字
        def get_next(n):
            next_num = 0
            while n > 0:
                n, mod = divmod(n, 10)  # 数位分离
                next_num += mod ** 2
            return next_num

        #定义一个哈希集
        seen = set()
        while n != 1 and n not in seen:
            seen.add(n)
            n = get_next(n)
        
        return n == 1

同构字符串

给定两个字符串 s 和 t,判断它们是否是同构的。

如果 s 中的字符可以被替换得到 t ,那么这两个字符串是同构的。

所有出现的字符都必须用另一个字符替换,同时保留字符的顺序。两个字符不能映射到同一个字符上,但字符可以映射自己本身。

示例 1:
输入: s = “egg”, t = “add”
输出: true
示例
输入: s = “foo”, t = “bar”
输出: false
示例 3:
输入: s = “paper”, t = “title”
输出: true

解题思路:需要保证两个字符串中,字符重复出现的次数相同,可以使用索引,判断两者索引是否相同
s.index(str)方法,检测字符串s中是否包含子字符串str,返回第一次出现的位置,我们将其作为索引
map()方法,根据函数对指定序列做映射,可以将字符映射为索引,map是通过hash存储的,不能直接进行比较,需要转换为list比较

class Solution:
    def isIsomorphic(self, s: str, t: str) -> bool:
        return list(map(s.index, s)) == list(map(t.index,t))

单词规律

给定一种规律 pattern 和一个字符串 str ,判断 str 是否遵循相同的规律。

这里的 遵循 指完全匹配,例如, pattern 里的每个字母和字符串 str 中的每个非空单词之间存在着双向连接的对应规律。

示例1:
输入: pattern = “abba”, str = “dog cat cat dog”
输出: true
示例 2:
输入:pattern = “abba”, str = “dog cat cat fish”
输出: false
示例 3:
输入: pattern = “aaaa”, str = “dog cat cat dog”
输出: false
示例 4:
输入: pattern = “abba”, str = “dog dog dog dog”
输出: false
说明:
你可以假设 pattern 只包含小写字母, str 包含了由单个空格分隔的小写字母。

解题思路:和上一题一模一样。需要将单词字符串拆分为字符列表。

class Solution:
    def wordPattern(self, pattern: str, str: str) -> bool:
        str = str.split()
        return list(map(pattern.index, pattern)) == list(map(str.index,str))

有效的字母异位词

给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。

示例 1:
输入: s = “anagram”, t = “nagaram”
输出: true
示例 2:
输入: s = “rat”, t = “car”
输出: false
说明:
你可以假设字符串只包含小写字母。

进阶:
如果输入字符串包含 unicode 字符怎么办?你能否调整你的解法来应对这种情况?

解题思路:如果只包含26个小写字符,可以开一个数组保存[a-z],每次遍历s和t,遍历s时数组对应字符的位置+1,表示该字符出现了一次;遍历t时数组对应字符的位置-1,最后判断结果数组是否都为0。

class Solution:
    def isAnagram(self, s: str, t: str) -> bool:
        if len(s) != len(t):
            return False   
        res = [0] * 26

        for i in range(len(s)):
            res[ord(s[i]) - 97] += 1
            res[ord(t[i]) - 97] -= 1

        for i in range(26):
            if res[i] != 0:
                return False
        
        return True

两个数组的交集

给定两个数组,编写一个函数来计算它们的交集。

示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]
示例 2:
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[9,4]

说明:
输出结果中的每个元素一定是唯一的。
我们可以不考虑输出结果的顺序。

解题思路:两个set()搞定,如果在两个set()都出现,说明是交集,如果没保存过则保存

class Solution:
    def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
        res = []
        for i in set(nums1):
            if i in set(nums2) and i not in res:
                res.append(i)
        
        return res

两个数组的交集||

给定两个数组,编写一个函数来计算它们的交集。

示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2,2]
示例 2:
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[4,9]

说明:
输出结果中每个元素出现的次数,应与元素在两个数组中出现次数的最小值一致。
我们可以不考虑输出结果的顺序。
进阶:
如果给定的数组已经排好序呢?你将如何优化你的算法?
如果 nums1 的大小比 nums2 小很多,哪种方法更优?
如果 nums2 的元素存储在磁盘上,磁盘内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办?

解题思路:和上一题不一样的是,上一题的答案唯一,而这道题有多个交集。
方法一:哈希表
同一个数字在两个数组中都可能出现多次,可以用哈希表存储每个数字出现的次数。对于一个数字,其在交集中出现的次数等于该数字在两个数组中出现次数的最小值。
首先遍历第一个数组,并在哈希表中记录第一个数组中的每个数字以及对应出现的次数,然后遍历第二个数组,对于第二个数组中的每个数字,如果在哈希表中存在这个数字,则将该数字添加到答案,并减少哈希表中该数字出现的次数。这里第一个数组应为较短的数组。

class Solution:
    def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
        #将较短字符遍历后将数字和频率存入哈希表
        if len(nums1) > len(nums2):
            return self.intersect(nums2, nums1)

        m = collections.Counter()   # 计数器
        for num in nums1:
            m[num] += 1
        
        intersection = []
        for num in nums2:
            if (count := m.get(num, 0)) > 0:    #count要么为key是num的value,要么为0
                intersection.append(num)
                m[num] -= 1
                if m[num] == 0:
                    m.pop(num)
        
        return intersection

这里使用了计数器collections.Counter(),以及海象赋值法:=,在循环过程进行赋值。
复杂度分析
时间复杂度:O(m+n),其中 m 和 n 分别是两个数组的长度。需要遍历两个数组并对哈希表进行操作,哈希表操作的时间复杂度是 O(1),因此总时间复杂度与两个数组的长度和呈线性关系。
空间复杂度:O(min(m,n)),其中 m和 n 分别是两个数组的长度。对较短的数组进行哈希表的操作,哈希表的大小不会超过较短的数组的长度。为返回值创建一个数组 intersection,其长度为较短的数组的长度。

方法二:排序,如果两个数组是有序的,则可以便捷地计算两个数组的交集。
首先对两个数组进行排序,然后使用两个指针遍历两个数组。
初始时,两个指针分别指向两个数组的头部。每次比较两个指针指向的两个数组中的数字,如果两个数字不相等,则将指向较小数字的指针右移一位,如果两个数字相等,将该数字添加到答案,并将两个指针都右移一位。当至少有一个指针超出数组范围时,遍历结束。

class Solution:
    def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
        nums1.sort()
        nums2.sort()

        res = []
        i, j = 0, 0
        while i < len(nums1) and j < len(nums2):
            if nums1[i] == nums2[j]:
                res.append(nums1[i])
                i += 1
                j += 1
            elif nums1[i] < nums2[j]:
                i += 1
            elif nums1[i] > nums2[j]:
                j += 1
            
        return res

复杂度分析
时间复杂度:O(m log m+n log n),其中 m 和 n 分别是两个数组的长度。对两个数组进行排序的时间复杂度是 O(m log m+n log n),遍历两个数组的时间复杂度是 O(m+n),因此总时间复杂度是 O(m log m+n log n)。
空间复杂度:O(min(m,n)),其中 m 和 n 分别是两个数组的长度。为返回值创建一个数组 intersection,其长度为较短的数组的长度。不过在 C++ 中,我们可以直接创建一个 vector,不需要把答案临时存放在一个额外的数组中,所以这种实现的空间复杂度为 O(1)。

结语
如果nums 2的元素存储在磁盘上,磁盘内存是有限的,并且你不能一次加载所有的元素到内存中。那么就无法高效地对nums 2进行排序,因此推荐使用方法一而不是方法二。在方法一中,nums2 只关系到查询操作,因此每次读取nums 2中的一部分数据,并进行处理即可。

分割数组的最大值

给定一个非负整数数组和一个整数 m,你需要将这个数组分成 m 个非空的连续子数组。设计一个算法使得这 m 个子数组各自和的最大值最小。

注意:
数组长度 n 满足以下条件:

1 ≤ n ≤ 1000
1 ≤ m ≤ min(50, n)
示例:
输入:
nums = [7,2,5,10,8]
m = 2
输出:
18
解释:
一共有四种方法将nums分割为2个子数组。
其中最好的方式是将其分为[7,2,5] 和 [10,8],
因为此时这两个子数组各自的和的最大值为18,在所有情况中最小。

解题思路:贪心+二分查找
根据题意,最大值一定在区间 [max(nums),sum(nums)] 之中。
7,2,5,10,8中max=10,sum=32
在…中找到最大/最小值可以使用二分,需要问题具有二段性
在范围10 11 12 …17 18…32中,可以通过二分查找找到答案18,因此上界为数组的sum,下界为数组的max
二分查找时,通过mid是否满足条件判断是在左区间还是右区间,这就需要一个额外的判断函数
这里使用了贪心的思想,模拟分割的过程,从前往后遍历,如果当前分割子数组的和sum加上当前值超过了用于判断的mid,则把当前值作为开头,新开一个分割子数组,并且将子数组数cnt+1,结束后验证cnt是否不超过n

class Solution:
    def splitArray(self, nums: List[int], m: int) -> int:
        def check(x):
            #total表示当前分割子数组之和,cnt表示已经分割出的子数组数量
            total, cnt = 0, 1
            for num in nums:
                #当total加上当前值超过x,把当前值作为新的分割子数组的开头,再一次分段,目的是要保证当前子数组的和不能超过假设的值x
                if total + num > x:
                    cnt += 1
                    total = num
                else:
                    total += num
            #最后验证cnt是否不超过m
            #如果cnt的值比m大,就表示分组的太多,也就是代表当初假设的太小了,需要在mid与数组元素和之间继续二分。
            #如果cnt的值比m小,就表示结果值不是最优的,因为还可以继续多分些组,找到那个最小值。因此需要在数组元素的最大值与mid之间继续二分。
            return cnt <= m
        #二分查找 上界为所有元素之和,下界为所有元素最大值,因为结果值一定出现在数组中的最大值与数组元素和之间。 
        left = max(nums)
        right = sum(nums)
        while left < right:
            mid = (left + right) // 2
            if check(mid):
                right = mid
            else:
                left = mid + 1
        return left

根据字符出现频率排序

给定一个字符串,请将字符串里的字符按照出现的频率降序排列。

示例 1:
输入:
“tree”
输出:
“eert”
解释:
'e’出现两次,'r’和’t’都只出现一次。
因此’e’必须出现在’r’和’t’之前。此外,"eetr"也是一个有效的答案。
示例 2:
输入:
“cccaaa”
输出:
“cccaaa”
解释:
'c’和’a’都出现三次。此外,"aaaccc"也是有效的答案。
注意"cacaca"是不正确的,因为相同的字母必须放在一起。
示例 3:
输入:
“Aabb”
输出:
“bbAa”
解释:
此外,"bbaA"也是一个有效的答案,但"Aabb"是不正确的。
注意’A’和’a’被认为是两种不同的字符。

解题思路:collections.Counter()计数器

class Solution:
    def frequencySort(self, s: str) -> str:
        return ''.join(i * j for i,j in collections.Counter(s).most_common())

collections.Counter(s).most_common()返回一个字典,key为字符,value为出现次数,从大到小排列
i为字符,j为该字符出现的次数,使用join()方法将字符拼接为字符串

Counter(计数器):用于追踪值的出现次数
Counter类继承dict类,所以它能使用dict类里面的方法
创建一个Counter类

import collections
obj = collections.Counter('aabbccc')
print(obj)

#输出:Counter({'c': 3, 'a': 2, 'b': 2})

elements()获取元素

import collections
obj = collections.Counter('aabbccc')
print(sorted(obj.elements()))
#输出:['a', 'a', 'b', 'b', 'c', 'c', 'c']

most_common(指定一个参数n,按出现次数从大到小列出前n个元素,不指定参数,则列出所有)

import collections
obj = collections.Counter('aabbbcccc')
print(obj.most_common(2))
#输出:[('c', 4), ('b', 3)]

有序数组中的单一元素

给定一个只包含整数的有序数组,每个元素都会出现两次,唯有一个数只会出现一次,找出这个数。

示例 1:

输入: [1,1,2,3,3,4,4,8,8]
输出: 2
示例 2:

输入: [3,3,7,7,10,11,11]
输出: 10

解题思路:二分查找
由于每个元素会出现两次,只有一个数字出现一次,那么该单次数字之前要么没数字,要么是2n个数字,由于索引从0开始,所以单次数字的索引要么为0要么为2n,因此我们只需检索偶数索引即可
检查mid时,我们判断mid与其之后的数字是否相同
如果相同,由于mid是偶数索引,前面有偶数个数字(索引从0开始),所以mid之前不可能有单次数字;所以我们跳过mid和mid之后那个数字,将mid+2置为下界,left=mid+2
如果不同,说明单次数字可能是mid或者在mid之前的数字,我们将mid置为上界,rigth=mid
最后只能检测一个数字,即left=right时,该数即为单次数字

class Solution:
    def singleNonDuplicate(self, nums: List[int]) -> int:
        left = 0
        right = len(nums) - 1
        while left < right:
            mid = (left + right) // 2
            #只检查偶数索引
            if mid % 2 == 1:
                mid -= 1
            if nums[mid] == nums[mid+1]:
                left = mid + 2
            else:
                right = mid
        return nums[left]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值