leetcode Day4

leetcode刷题笔记Day4

1、搜索插入位置

题目描述:

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

请必须使用时间复杂度为 O(log n) 的算法。

示例:

输入: nums = [1,3,5,6], target = 7
输出: 4

解法一:直接法

思路:

1、判断数组是否为空,为空,则不论target是什么,都直接插入索引为0;

2、数组非空,用 i 指针遍历数组,有4种情况:

  • target比数组第一个元素都小,插入位置为0
  • target与数组中 i 位置元素相等,插入位置为 i
  • target比数组 i 位置元素大,比 i+1 位置(未溢出)元素小,插入位置为 i
  • target比数组最后一个元素 i 大,则插入数组末端,插入位置为 i+1

3、前三种情况可合并判断,target >= nums[i],只要满足这个条件,插入位置就是 i ,之所以不会出现重复判断,是因为,每次找到位置就会直接结束进程。

4、但这种方法的平均时间复杂度为O(n)

代码:

class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        if not nums:# 数组为空
            return 0
        for i in range(len(nums)):# 数组非空,遍历数组
            if nums[i] >= target:
                return i
            elif i+1 == len(nums) and target > nums[i]:
                return i+1

结果:

在这里插入图片描述

解法二、二分法

思路:

1、有序数组查找元素可以使用二分法;

2、题目可以看做:我们需要找到第一个大于或者等于target的元素

3、mid = (left + right) // 2,target小于等于nums[mid],表示该元素位于左半部分,否则,target位于右半部分

4、循环,直至找到符合条件的元素;

5、根据if的判断条件,left左边的值一直保持小于target,right右边的值一直保持大于等于target,而且left最终一定等于right+1,这么一来,循环结束后,在left和right之间画一条竖线,恰好可以把数组分为两部分:left左边的部分和right右边的部分,而且left左边的部分全部小于target,并以right结尾;right右边的部分全部大于等于target,并以left为首。所以最终答案一定在left的位置。

代码:

class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        if not nums:
            return 0
        left = 0
        mid = 0
        right = len(nums)-1
        while left <= right :# 循环停止的条件
            mid = (left + right) // 2 # python中除法向下取整的操作符是//
            if target <= nums[mid]:
                right = mid-1
            else:
                left = mid+1
        return left

结果:

在这里插入图片描述

相关知识介绍

查找算法
顺序查找sequential_searchO(n)

算法思路:

对于任意一个序列,从一端开始,顺序扫描,依次将扫描到的结点关键字与给定值k相比较,若相等则表示查找成功;若扫描结束仍没有找到关键字等于k的结点,表示查找失败。

代码:

def sequential_search(arr, target):       
     """ 顺序查找算法    
         arr: 无序数组      
         target: 目标元素     
         return: 目标元素的索引,如果不存在则返回-1"""
     for i in range(len(arr)):           
          if arr[i] == target:  # 如果当前元素等于目标元素,查找成功               
              return i          
          return -1  # 目标元素不存在    

改良优化:

思路:

设置“前哨站”。所谓“前哨站”,就是把要查找的关键字在查找之前插入到数列的尾部。时间复杂度不变,但减少了if条件的判断。

def line_find_(nums, target):
    i = 0
    while nums[i] != key:
        i += 1
    return -1 if i == len(nums)-1 else i
# 测试
nums = [4, 1, 8, 10, 3, 5]
target = 9
nums.append(key)
二分查找binary_search(折半查找)O(logn)

算法思路:

  1. 确定查找范围left=0,right=N-1,计算中项mid=(left+right)//2。
  2. 若mid==target或left>=right,则结束查找;否则,向下继续。
  3. 若mid<target,说明待查找的元素值只可能在比中项元素大的范围内,则把mid+1的值赋给left,并重新计算mid,转去执行步骤2;若mid>target,说明待查找的元素值只可能在比中项元素小的范围内,则把mid-1的值赋给right,并重新计算mid,转去执行步骤2。

注意:

查找元素必须是有序的,如果是无序的则要先进行排序操作。

代码:

arr = [2,5,6,8,12,15,17,23,27,31,39,40,45,56,79,90]
l,r = 0,len(arr)-1  #初始化左右指针
target = 12   #输入关键字
while l <= r:     #循环条件,判断是否存在合理的查找范围
    mid = (l+r)//2   #求出左右指针的平均数
    if arr[mid] < target:  #折半缩小查找范围
       l = mid+1
    elif arr[mid] > target:
        r = mid-1
    else: #如果mid指向的元素与关键字相等,直接输出下标并跳出循环
        print(mid)
        break
#while循环自然结束,说明没有查找到与关键字相等的元素
print(-1)

递归实现二分查找:

def binary_find_dg(nums, key, l_idx, r_ldx):
    if l_idx > r_ldx:
        # 出口一:没有查找到给定关键字
        return -1
    # 计算出中间位置
    mid_pos = (r_ldx + l_idx) // 2
    # 计算中间位置的值
    mid_val = nums[mid_pos]
    # 与关键字比较
    if mid_val == key:
        # 出口二:比较相等,有此关键字,返回关键字所在位置
        return mid_pos
    elif mid_val > key:
        # 说明查找范围应该缩少在原数的左边
        r_ldx = mid_pos - 1
    else:
        l_idx = mid_pos + 1
    return binary_find_dg(nums, key, l_idx, r_ldx)
插值查找Interpolation_search

插值查找基于二分查找算法,将查找点的选择改进为自适应选择,可以提高查找效率。当然,差值查找也属于有序查找。

插值查找,本质上也是二分查找,只是mid的计算方式不一样。

算法描述:

  1. 确定查找范围left=0,right=N-1,计算中项mid=(left+right)//2。
  2. 若mid==target或left>=right,则结束查找;否则,向下继续。
  3. 若mid<target,说明待查找的元素值只可能在比中项元素大的范围内,则把mid+1的值赋给left,并重新计算mid,转去执行步骤2;若mid>target,说明待查找的元素值只可能在比中项元素小的范围内,则把mid-1的值赋给right,并重新计算mid,转去执行步骤2。

代码:

arr = [2,5,6,8,12,15,17,23,27,31,39,40,45,56,79,90]
l,r = 0,len(arr)-1  #初始化左右指针
target = 12   #输入关键字
while l <= r:     #循环条件,判断是否存在合理的查找范围
    mid = l + (target - nums[l])//(nums[r]-nums[l])*(r-l)   #求出左右指针的平均数
    if arr[mid] < target:  #折半缩小查找范围
       l = mid+1
    elif arr[mid] > target:
        r = mid-1
    else: #如果mid指向的元素与关键字相等,直接输出下标并跳出循环
        print(mid)
        break
#while循环自然结束,说明没有查找到与关键字相等的元素
print(-1)
分块查找Block Search(索引查找)O(log2n)~O(n)

算法描述:

与二分法类似,二分法是分两块,而分块查找则是分为k块。

  1. 先将排好序的原始数据分为 k 块,k 的大小随意决定;
  2. 每块中取出一个最大值,构成一个索引表;
  3. 通过比较,判断目标元素属于哪一块,这里我们选用顺序查找/二分查找;
  4. 在该块内,再进一步进行顺序查找,确定目标元素的位置;
  5. 返回目标元素的位置。

说明:

分块查找对于整体趋向有序的数列,其查找性能较好。但如果原始数列整体不是有序,则需要提供块排序算法,时间复杂度没有二分查找算法好。

代码:

'''
分块:建立索引表
参数:
    nums 原始数列
    blocks 块大小
每一项:
	[max,左索引,右索引]
'''
def create_index_table(nums, blocks):
    # 索引表使用列表保存
    index_table = []
    # 每一块的数量
    n = len(nums) // blocks
    for i in range(0, len(nums), n):# 分块
        tmp_lst = []
        tmp_lst.append(max(nums[i:i + n - 1]))# 用每一块的最大值做索引表的索引项
        tmp_lst.append(i)
        tmp_lst.append(i + n - 1)
        index_table.append(tmp_lst)
    return index_table

'''
使用线性查找算法在对应的块中查找
'''
def lind_find(nums, start, end):
    for i in range(start, end):
        if key == nums[i]:
            return i
            break
    return -1

'''
测试分块
'''
nums = [5, 1, 9, 11, 23, 16, 12, 18, 24, 32, 29, 25]
key = 16
# 索引表
it = create_index_table(nums, 3)
# 索引表的记录编号
pos = -1
# 在索引表中查询
for n in range(len(it) - 1):
    # 是不是在第一块中
    if key <= it[0][0]:
        pos = 0
    # 其它块中
    if it[n][0] < key <= it[n + 1][0]:
        pos = n + 1
        break
if pos == -1:
    print("{0} 在 {1} 数列中不存在".format(key, nums))
else:
    idx = lind_find(nums, it[pos][1], it[pos][2] + 1)
    if idx != -1:
        print("{0} 在 {1} 数列的 {2} 位置".format(key, nums, idx))
    else:
        print("{0} 在 {1} 数列中不存在".format(key, nums))
'''
输出结果
16 在 [5, 1, 9, 11, 23, 16, 12, 18, 24, 32, 29, 25] 数列的第 5 位置
'''
哈希查找O(1)

算法描述:

由于数据项都保存到散列表后, 查找就无比简单,要查找某个数据项是否存在于表中, 我们只需要使用同一个散列函数, 对查找项进行计算, 测试下返回的槽号所对应的槽中是否有数据项即可, 实现了O(1)时间复杂度的查找算法。

哈希表(hash table, 又称散列表) 是一种数据集, 其中数据项的存储方式尤其有利于将来快速的查找定位。

哈希表中的每一个存储位置, 称为槽(slot) , 可以用来保存数据项, 每个槽有一个唯一的名称。

如果两个数据项被哈希映射(取余法)到同一个槽,需要一个系统化的方法在散列表中保存第二个数据项, 这个过程称为“解决冲突” 。

开放寻址法(线性探测法):解决哈希冲突的一种方法就是为冲突的数据项,再找一个开放的空槽来保存。最简单的就是从冲突的槽开始往后扫描,直到碰到一个空槽,如果到散列表尾部还未找到,则从首部接着扫描。这种寻找空槽的技术称为“开放定址法“。向后逐个槽寻找的方法则是开放定址技术中的“线性探测”。我们把44、 55、 20逐个插入到散列表(大小11)中
h(44)=0,但发现0#槽已被77占据,向后找到第一个空槽1#,保存
h(55)=0,同样0#槽已经被占据,向后找到第一个空槽2#,保存

代码:

class HashTable:
    def __init__(self):
        self.size = 11# 初始化哈希表大小
        self.slots = [None] * self.size# 哈希表key
        self.data = [None] * self.size# 哈希表value
        
    def put(self,key,data):# 构建哈希表
      hashvalue = self.hashfunction(key,len(self.slots))

      if self.slots[hashvalue] == None:# 哈希表这一项为空,直接插入数据
        self.slots[hashvalue] = key
        self.data[hashvalue] = data
      else:# 哈希表非空
        if self.slots[hashvalue] == key:# 替换值
          self.data[hashvalue] = data  #replace
        else:
          nextslot = self.rehash(hashvalue,len(self.slots))# 目前槽被占用,寻找下一个空槽
          while self.slots[nextslot] != None and \
                          self.slots[nextslot] != key:# 槽被占用,且key值不同,寻找下一个空槽
            nextslot = self.rehash(nextslot,len(self.slots))

          if self.slots[nextslot] == None:# 找到空槽
            self.slots[nextslot]=key
            self.data[nextslot]=data
          else:
            self.data[nextslot] = data #replace

    def hashfunction(self,key,size):# 取余法作为哈希函数,得到哈希表索引
         return key%size

    def rehash(self,oldhash,size):# 解决冲突,寻找下一个空槽
        return (oldhash+1)%size
        
    def get(self,key):
      startslot = self.hashfunction(key,len(self.slots))

      data = None
      stop = False
      found = False
      position = startslot
      while self.slots[position] != None and  \
                           not found and not stop:
         if self.slots[position] == key:
           found = True
           data = self.data[position]
         else:
           position=self.rehash(position,len(self.slots))
           if position == startslot:
               stop = True
      return data

    def __getitem__(self,key):
        return self.get(key)

    def __setitem__(self,key,data):
        self.put(key,data)
        
H=HashTable()
H[54]="cat"
H[26]="dog"
H[93]="lion"
H[17]="tiger"
H[77]="bird"
H[31]="cow"
H[44]="goat"
H[55]="pig"
H[20]="chicken"
print(H.slots)# [77, 44, 55, 20, 26, 93, 17, None, None, 31, 54]
print(H.data)# ['bird', 'goat', 'pig', 'chicken', 'dog', 'lion', 'tiger', None, None, 'cow', 'cat']

print(H[20])# chicken

print(H[17])# tiger
H[20]='duck'
print(H[20])# duck
print(H[99])# None

2、加一

题目描述:

给定一个由 整数 组成的 非空 数组所表示的非负整数,在该数的基础上加一。

最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。

你可以假设除了整数 0 之外,这个整数不会以零开头。

示例:

输入:digits = [1,2,3]
输出:[1,2,4]
解释:输入数组表示数字 123

解法一、直接法

思路:

一共可能出现三种情况:

  • 从右往左依次看,没有9,则直接数组最后一位加1,数组长度不变;
  • 从右往左依次看,有连续的n个9,则从右往左n+1个数直接加1,后面n个位全部为1.数组长度不变;
  • 从右往左依次看,全部为9,则需要在数组最前面加1位,为1,其余均为0。

代码:

class Solution:
    def plusOne(self, digits: List[int]) -> List[int]:
        l = len(digits)
        i = l-1
        while i >= 0:
            if digits[i] != 9:
                digits[i] += 1
                for j in range(i+1,l):
                    digits[j] = 0
                return digits
            i -= 1
        data = [1] + [0] * l
        return data

结果:

在这里插入图片描述

解法二、优化

思路:

解法一中,存在双循环,导致时间过长,所以要想办法去掉内循环。

1、其实,数字加1,我们可看作从个位数开始加1,直至不用进位为止;

2、转化为代码思想,就是:

  • 逆序遍历数组,每一项加1对10取模,若为0,代表产生进位,指针前移,继续加1取模运算,直至无需进位为止,直接输出结果;
  • 若最后,进位超出数组长度,此时原数组都变为0,结果只要在数组首部增加一位就是结果。

代码:

class Solution:
    def plusOne(self, digits: List[int]) -> List[int]:
        l = len(digits)
        i = l-1
        while i >= 0:
            digits[i] = (digits[i] + 1)%10
            if digits[i] != 0:# 是否需要产生进位
                return digits
            i -= 1
        return [1] + digits

结果:

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值