【算法题】数据结构系列

数组排序:arr.sort()

将列表转化为字符串 “”.join(arr)

注:以下题号为leetcode题号,可以在leetcode上搜索找到原题

目录

数组

169. 多数元素

1.两数之和 +1

面试题03. 数组中重复的数字 

面试题42:连续子数组的最大和

21.调整数组顺序使奇数位于偶数前面

39.数组中出现次数超过一半的数字、 多数元素

40. 最小的k个数

41.数据流中的中位数-堆

45. 把数组排成最小的数

51. 数组中的逆序对

56 - I. 数组中数字出现的次数 

56 - II. 数组中数字出现的次数 II

59 - I. 滑动窗口的最大值

66 构建乘积数组   

67 把字符串转换成整数   

1013. 将数组分成和相等的三个部分

560. 和为K的子数组

448. 找到所有数组中消失的数字

真题:将一个数组拆分成两个差值最小的数组

575 分糖果——种类最多

1103 分糖果——依次加一

888 公平的糖果交换

61. 扑克牌中的顺子

链表

2 两数相加

445 两数相加II 

面试题 6.从尾到头打印链表——栈

18 删除链表的节点

22. 链表中倒数第k个节点

24. 反转链表 

25. 合并两个排序的链表

23. 合并K个排序链表 

35.复杂链表的复制

52. 两个链表的第一个公共节点 & 160. 相交链表

142 环形链表——快慢指针

146 LRU缓存机制——哈希表

148 排序链表——递归

25. K 个一组翻转链表 

234. 回文链表

栈和队列

9.用两个栈实现队列

30. 包含min函数的栈、最小栈

31.栈的压入、弹出序列

20. 有效的括号 

59.II队列的最大值

739. 每日温度

636. 函数的独占时间 

大鱼吃小鱼 牛客

字符串

1-2 判断是否互为字符重排

38.字符串排列

19 正则表达式匹配

20. 表示数值的字符串

48. 最长不含重复字符的子字符串 

50. 第一个只出现一次的字符

58 - I. 翻转单词顺序

58 - II. 左旋转字符串

5. 最长回文子串 

14. 最长公共前缀

1071. 字符串的最大公因子

其他

真题:坐板凳

1298. 你能从盒子里获得的最大糖果数


数组

169. 多数元素

给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。

如果将数组 nums 中的所有元素按照单调递增或单调递减的顺序排序,那么下标为n/2的元素(下标从 0 开始)一定是众数。

class Solution:
    def majorityElement(self, nums: List[int]) -> int:
        nums.sort()
        return nums[len(nums)//2]

BM算法(Boyer-Moore)摩尔投票算法

摩尔投票算法是一种使用线性时间和常数空间查找大部分元素序列的算法。

想法:

如果我们把众数记为 +1 ,把其他数记为 -1 ,将它们全部加起来,显然和大于 0 ,从结果本身我们可以看出众数比其他数多。

算法:

本质上, Boyer-Moore 算法就是找 nums 的一个后缀 suf,其中 suf[0]就是后缀中的众数。我们维护一个计数器,如果遇到一个我们目前的候选众数,就将计数器加一,否则减一。只要计数器等于 0 ,我们就将 nums 中之前访问的数字全部忘记 ,并把下一个数字当做候选的众数。直观上这个算法不是特别明显为何是对的,我们先看下面这个例子(竖线用来划分每次计数器归零的情况)

[7, 7, 5, 7, 5, 1 | 5, 7 | 5, 5, 7, 7 | 7, 7, 7, 7]

首先,下标为 0 的 7 被当做众数的第一个候选。在下标为 5 处,计数器会变回0 。所以下标为 6 的 5 是下一个众数的候选者。由于这个例子中 7 是真正的众数,所以通过忽略掉前面的数字,我们忽略掉了同样多数目的众数和非众数。因此, 7 仍然是剩下数字中的众数。

因此,上面的过程说明了我们可以放心地遗忘前面的数字,并继续求解剩下数字中的众数。最后,总有一个后缀满足计数器是大于 0 的,此时这个后缀的众数就是整个数组的众数。

class Solution:
    def majorityElement(self, nums):
        count = 0
        candidate = None
        for num in nums:
            if count == 0:
                candidate = num
            count += (1 if num == candidate else -1)
        return candidate

 

1.两数之和 +1

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。

示例:

给定 nums = [2, 7, 11, 15], target = 9

因为 nums[0] + nums[1] = 2 + 7 = 9

所以返回 [0, 1]

def twoSum(nums, target):
    hashmap={}
    for ind,num in enumerate(nums):
        hashmap[num] = ind
    for i,num in enumerate(nums):
        j = hashmap.get(target - num)
        if j and i!=j:
            return [i,j]

 

面试题03. 数组中重复的数字 

找出数组中重复的数字。

在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。

  • 哈希表的方法:

遍历整个数组,当这个数字没有出现过哈希表的时候将其加入进去,如果在哈希表中则直接返回即可,时间复杂度O(n),空间复杂度O(n)

class Solution(object):
    def findRepeatNumber(self, nums):
        dic = {}
        for i in nums:
            if i not in dic:
                dic[i] = 0
            else:
                return i
  • 排序法:时间复杂度为O(nlogn),空间复杂度为O(1)

将数组排好序,遍历数组,判断是否与相邻数字相同,相同则返回

class Solution:
    def findRepeatNumber(self,nums):
        nums.sort()
        pre = nums[0]
        for i in range(1,len(nums)):
            if nums[i]==pre: return pre
            pre = nums[i]
      

range(0, 5) 是[0, 1, 2, 3, 4]没有5

  • 下标定位法

时间复杂度为O(n),空间复杂度为O(1)

题目中指出 在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内

从头到尾扫描数组,当扫描到下标为i的数字时,首先比较这个数字(用m表示)是否等于下标i,如果等于就扫描下一个数字;如果不是,则将它和第m个数字进行比较.如果它和第m个数相等,那么出现了重复直接返回;如果不相等,则将它和第m个数进行交换,把m放到第m个位置上

class Solution:
  def find(self, nums):
    i = 0
    while i < len(nums):
      if nums[i] == i:
        i+=1
        continue
      if nums[nums[i]] == nums[i]: return nums[i]
      nums[nums[i]], nums[i] = nums[i], nums[nums[i]] #顺序不能反

a,b=b,a 这条指令,先是变量a被赋“b值”的地址,然后变量b被赋“a值”的地址,

 

面试题42:连续子数组的最大和

 输入一个整型数组,数组里有正数也有负数。数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。

要求时间复杂度为O(n)。

示例1:

输入: nums = [-2,1,-3,4,-1,2,1,-5,4]

输出: 6

解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

我们在原地修改数组,将数组每个位置的值更改为当前位置上的最大和。

时间复杂度:O(N),遍历了一次数组。

空间复杂度:O(1),使用了常数空间。

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        maxsum = nums[0]
        for i in range(1,len(nums)):
            if nums[i-1]>0:
                nums[i] += nums[i-1]
            maxsum = max(nums[i],maxsum)
        return maxsum

 

21.调整数组顺序使奇数位于偶数前面

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。 

示例:

输入:nums = [1,2,3,4]

输出:[1,3,2,4] 

注:[3,1,2,4] 也是正确的答案之一。

使用奇数和偶数两个数组,分别保存奇数和偶数,最后将两个数组合并起来。

时间复杂度:O(N)。遍历了一遍数组。

空间复杂度:O(N)。使用了额外数组 odd 和 even,它们的长度之和为输入数组的长度。

class Solution:
    def exchange(self, nums: List[int]) -> List[int]:
        odd = []  # 奇数
        even = [] # 偶数
        for i in nums:
            if i % 2 == 0:   # i 是偶数:
                even.append(i)
            else:
                odd.append(i)
        return odd + even

39.数组中出现次数超过一半的数字、 多数元素

class Solution:
    def majorityElement(self,nums):
        nums.sort()
        return nums[len(nums)//2]

40. 最小的k个数

class Solution:
    def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
        arr.sort()
        return arr[0:k]

41.数据流中的中位数-堆

我们将中位数左边的数保存在大顶堆中,右边的数保存在小顶堆中。这样我们可以在 
O(1)时间内得到中位数。
注意:Python 中没有大顶堆,只能将值取负保存在小顶堆来模拟。为了方便理解,将堆用优先队列表示,如下图。
时间复杂度:O(logn)。堆插入和删除需要 O(logn),查找中位数需要 O(1)。
空间复杂度:O(n)。
class MedianFinder:
   def __init__(self):
        """
        initialize your data structure here.
        """
        # 初始化大顶堆和小顶堆
        self.max_heap = []
        self.min_heap = []
    def addNum(self, num: int) -> None:
        if len(self.max_heap) == len(self.min_heap):# 先加到大顶堆,再把大堆顶元素加到小顶堆
            heapq.heappush(self.min_heap, -heapq.heappushpop(self.max_heap, -num))
        else:  # 先加到小顶堆,再把小堆顶元素加到大顶堆
            heapq.heappush(self.max_heap, -heapq.heappushpop(self.min_heap, num))

    def findMedian(self) -> float:
        if len(self.min_heap) == len(self.max_heap):
            return (-self.max_heap[0] + self.min_heap[0]) / 2
        else:
            return self.min_heap[0

45. 把数组排成最小的数

输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。

根据题目的要求,两个数字m和n能拼接成数字mn和nm,如果mn < nm那么现在他们的相对位置是正确的,如果mn > nm,那么就需要将n移到m的前面,根据这样的特性我们能将整个数组进行排列,得到最终的结果.
我们在比较的时候先将数据转换成str格式的,利用str格式的字符串直接比较就可以
这个题目要求我们转换成字符串的形式,是隐含了一个大数的问题
class Solution:
    def minNumber(self,nums):
        if not  nums:  return None
        for i in range(len(nums)):
            nums[i] = str(nums[i])
        for i in range(len(nums)):
              for j in range(i+1,len(nums));
                  if (nums[i]+nums[j])>(nums[j]+nums[i]):
                        nums[i],nums[j] = nums[j],nums[i]
         return ''.join(nums)

51. 数组中的逆序对

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。

归并排序

class Solution:
    def reversePairs(self, nums: List[int]) -> int:


        """利用归并排序merge思想,每个即将merge的数组是有序的来判断前大后小的种类个数"""
        self.res = 0
        self.merge_sort(nums, 0, len(nums) - 1)
        return self.res


    def merge_sort(self, nums, l, r):
        """左闭右闭"""
        if l >= r:
            return
        mid = l + (r - l) // 2
        self.merge_sort(nums, l, mid)
        self.merge_sort(nums, mid + 1, r)
        self.merge(nums, l, mid, r)


    def merge(self, nums, l, mid, r):
        aux = nums[l: r + 1]
        idx1, idx2 = l, mid + 1
        for k in range(l, r + 1):
            if idx1 > mid:
                nums[k] = aux[idx2 - l]
                idx2 += 1
            elif idx2 > r:
                nums[k] = aux[idx1 - l]
                idx1 += 1
            elif aux[idx1 - l] <= aux[idx2 - l]:
                nums[k] = aux[idx1 - l]
                idx1 += 1
            else:
                # 此时是大于的条件
                self.res += mid - idx1 + 1
                nums[k] = aux[idx2 - l]
                idx2 += 1
     

56 - I. 数组中数字出现的次数 

题目大意是除了两个数字出现一次,其他都出现了两次,让我们找到这个两个数。
我们进行一次全员异或操作,得到的结果就是那两个只出现一次的不同的数字的异或结果。
任何数和本身异或则为0,因此我们的思路是能不能将这两个不同的数字分成两组 A 和 B。
分组需要满足两个条件.
(1)两个独特的的数字分成不同组
(2)相同的数字分成相同组
这样每一组的数据进行异或即可得到那两个数字。
由于异或的性质是,同一位相同则为 0,不同则为 1. 我们将所有数字异或的结果一定不是 0,也就是说至少有一位是 1.
我们随便取一个, 分组的依据就来了, 就是你取的那一位是 0 分成 1 组,那一位是 1 的分成一组。
这样肯定能保证2. 相同的数字分成相同组, 不同的数字会被分成不同组么。 很明显当然可以, 因此我们选择是 1,也就是
说两个独特的的数字在那一位一定是不同的,因此两个独特元素一定会被分成不同组。
class Solution:
    def singleNumbers(self, nums: List[int]) -> List[int]:
        ret = 0  # 所有数字异或的结果
        a = 0
        b = 0
        for n in nums:
            ret ^= n
        # 找到第一位不是0的
        h = 1
        while(ret & h == 0):
            h <<= 1
        for n in nums:
            # 根据该位是否为0将其分为两组
            if (h & n == 0):
                a ^= n
            else:
                b ^= n

        return [a, b]

56 - II. 数组中数字出现的次数 II

class Solution:
    def singleNumber(self, nums: List[int]) -> int:
        res = 0
        for i in range(32):
            cnt = 0  # 记录当前 bit 有多少个1
            bit = 1 << i  # 记录当前要操作的 bit
            for num in nums:
                if num & bit != 0:
                    cnt += 1
            if cnt % 3 != 0:
                # 不等于0说明唯一出现的数字在这个 bit 上是1
                res |= bit

        return res - 2 ** 32 if res > 2 ** 31 - 1 else res

59 - I. 滑动窗口的最大值

class Solution(object):
    def maxSlidingWindow(self, nums, k):
        s=0
        ans=[]
        if len(nums)==0:
            return []
        if k==0:
            return []
        for i in range(len(nums)-k+1):
        #窗口开始位置的移动,截至位置要注意
           s=max(nums[i:i+k])
           ans.append(s)
        return ans

66 构建乘积数组   

题目没给全可能不好理解,也就是B[i]为除A[i]以外所有数的乘积:规定B[0] = A[1] * A[2] * ... * A[n-1],B[n-1] = A[0] * A[1] * ... * A[n-2];

class Solution(object):
    def constructArr(self, a):
        """
        :type a: List[int]
        :rtype: List[int]
        """
        if not a :
            return []
        b = [0] * len(a)
        temp = 1
        for i in range(len(a)):
            b[i] = temp
            temp *= a[i]
        temp = 1
        for i in range(len(a)-1,-1,-1):
            b[i] *= temp
            temp *= a[i]
        return b

67 把字符串转换成整数   

【算法题】数据结构系列_第1张图片

class Solution:
    def strToInt(self, str: str) -> int:
        ls = list(str.strip())
        if not ls: return 0
        flag = -1 if ls[0] == '-' else 1
        if ls[0] in ['+', '-']: del ls[0]
        ret, i = 0, 0
        while i < len(ls) and ls[i].isdigit():
            ret = ret * 10 + ord(ls[i]) - ord('0')
            i += 1
        return max(-2 ** 31, min(2 ** 31 - 1, flag * ret)) # 防止溢出

 

1013. 将数组分成和相等的三个部分

class Solution(object):
    def canThreePartsEqualSum(self, A):
        amount = sum(A)
        single = amount / 3 #先求和,判断和除3是否为整数
        if single % 1 == 0: 
            s = 0
            l = []
            for i in range(len(A)):
                s += A[i]
                if s == single:
                    l.append(s)  # 第一段求出后,加入数组,和清零
                    s = 0 
                    if len(l) == 2 and i != len(A)-1: # 求出前两部分和后,求第三部分     超出索引求和为0,我也不知道
                        c = sum(A[i+1:])
                        l.append(c)
                        return l[0] == l[2]
        return False

560. 和为K的子数组

给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。

示例 1 :

输入:nums = [1,1,1], k = 2

输出: 2 , [1,1] 与 [1,1] 为两种不同的情况。

采用哈希表
1.初始化哈希表hash={0:1},表示累加和为0,出现了1次。初始化累加和sum=0初始化结果count=0
2.遍历数组:
更新累加和sum+=nums[i]
若sum−k,存在于hash中,说明存在连续序列使得和为k。则令count+=hash[sum−k],表示sum−k出现几次,就存在几种子序列使得和为k。
若sum存在于hash中,将其出现次数加一。若不存在,将其加入hash
3.返回count
时间复杂度:
O(n)
空间复杂度:
O(n)
class Solution:
    def subarraySum(self, nums: List[int], k: int) -> int:
        hash={0:1}
        sum=0
        count=0
        for i in range(len(nums)):
            sum+=nums[i]
            if((sum-k) in hash):
                count+=hash[sum-k]
            if(sum in hash):
                hash[sum]+=1
            else:
                hash[sum]=1
        return count

448. 找到所有数组中消失的数字

给定一个范围在  1 ≤ a[i] ≤ n ( n = 数组大小 ) 的 整型数组,数组中的元素一些出现了两次,另一些只出现一次。

找到所有在 [1, n] 范围之间没有出现在数组中的数字。

您能在不使用额外空间且时间复杂度为O(n)的情况下完成这个任务吗? 你可以假定返回的数组不算在额外空间内。

示例:

输入:

[4,3,2,7,8,2,3,1]

输出:

[5,6]

 

我们需要知道数组中存在的数字,由于数组的元素取值范围是 [1, N],所以我们可以不使用额外的空间去解决它。

我们可以在输入数组本身以某种方式标记已访问过的数字,然后再找到缺失的数字。

  • 遍历输入数组的每个元素一次。
  • 我们将把 |nums[i]|-1 索引位置的元素标记为负数。即 nums[∣nums[i]∣−1]×−1。
  • 然后遍历数组,若当前数组元素 nums[i] 为负数,说明我们在数组中存在数字 i+1。

https://leetcode-cn.com/problems/find-all-numbers-disappeared-in-an-array/solution/zhao-dao-suo-you-shu-zu-zhong-xiao-shi-de-shu-zi-2/

 

class Solution(object):
    def findDisappearedNumbers(self, nums):
        for i in range(len(nums)):
            new_index = abs(nums[i]) - 1 
           
            if nums[new_index] > 0:
                nums[new_index] *=-1
        result = [] #算是返回数组,不属于额外空间   
        
        for i in range(1, len(nums) + 1):
            if nums[i - 1] > 0:
                result.append(i)
                
        return result    

 

真题:将一个数组拆分成两个差值最小的数组

思路:

  1. 把要被分割的已知列表sort()排序,并求出它的元素的和的一半,以及最后一个元素的索引length
  2. 新建一个空列表
  3. .将半和与该列表的最后一枚元素依次比较。如果半和大,就把列表最后一个元素删除,添加到新列表中,half也减去这个元素的值;如果是最后一个元素大,就跳过该元素。无论哪一个大,length都要-1
import math as Math
def doublesort(list):
    half = Math.fsum(list)/2
    length = len(list) - 1
    list1 = []
    while(length>1):
        if (half>list[length]):
            list1.append(list[length])
            half = half - list[length]
            list.pop(length)
            length -= 1
         else:
             length -= 1
             continue
     return list, list1

575 分糖果——种类最多

给定一个偶数长度的数组,其中不同的数字代表着不同种类的糖果,每一个数字代表一个糖果。你需要把这些糖果平均分给一个弟弟和一个妹妹。返回妹妹可以获得的最大糖果的种类数。

输入: candies = [1,1,2,2,3,3]
输出: 3
解析: 一共有三种种类的糖果,每一种都有两个。
     最优分配方案:妹妹获得[1,2,3],弟弟也获得[1,2,3]。这样使妹妹获得糖果的种类数最多。

判断种类和总数量的一半之间的关系

class Solution:
    def distributeCandies(self, candies: List[int]) -> int:
        type = len(set(candies))
        nums = len(candies)
        if type>nums/2: return nums//2   #如果用/2,会因为浮点数与输出不一致
        else: return type

1103 分糖果——依次加一

买了一些糖果 candies,打算把它们分给排好队的 n = num_people 个小朋友。

给第一个小朋友 1 颗糖果,第二个小朋友 2 颗,依此类推,直到给最后一个小朋友 n 颗糖果。

然后,我们再回到队伍的起点,给第一个小朋友 n + 1 颗糖果,第二个小朋友 n + 2 颗,依此类推,直到给最后一个小朋友 2 * n 颗糖果。

返回一个长度为 num_people、元素之和为 candies 的数组,以表示糖果的最终分发情况(即 ans[i] 表示第 i 个小朋友分到的糖果数)。

输入:candies = 7, num_people = 4
输出:[1,2,3,1]
解释:
第一次,ans[0] += 1,数组变为 [1,0,0,0]。
第二次,ans[1] += 2,数组变为 [1,2,0,0]。
第三次,ans[2] += 3,数组变为 [1,2,3,0]。
第四次,ans[3] += 1(因为此时只剩下 1 颗糖果),最终数组变为 [1,2,3,1]

ret[i%num_people] += min(candies,i+1)

class Solution:
    def distributeCandies(self, candies: int, num_people: int) -> List[int]:
    ret = [0]*num_people
    i = 0
    while candies !=0:
        ret[i%num_people] += min(candies,i+1)
        candies -= min(candies,i+1)
        i += 1
     return ret

 

888 公平的糖果交换

爱丽丝和鲍勃有不同大小的糖果棒:A[i] 是爱丽丝拥有的第 i 块糖的大小,B[j] 是鲍勃拥有的第 j 块糖的大小。

他们想交换一个糖果棒,这样交换后,他们都有相同的糖果总量。(一个人拥有的糖果总量是他们拥有的糖果棒大小的总和。)

返回一个整数数组 ans,其中 ans[0] 是爱丽丝必须交换的糖果棒的大小,ans[1] 是 Bob 必须交换的糖果棒的大小。

如果有多个答案,你可以返回其中任何一个。保证答案存在。

输入:A = [1,1], B = [2,2]

输出:[1,2]

集合去重,dif为总量差值的一半

class Solution:
    def fairCandySwap(self, A: List[int], B: List[int]) -> List[int]:
        dif = (sum(A)-sum(B))//2
        a = set(A)
        b = set(B)
        for i in a:
            if i-diff in b:
                return [i,i-diff]

 

61. 扑克牌中的顺子

class Solution:
    def isStraight(self, nums: List[int]) -> bool:
        zeroNum = 0
        store = [0 for _ in range(14)]
        for n in nums:
            if n==0:
                zeroNum+=1
            else:
                if store[n] == 0:
                    store[n] = 1
                else: return False
                
                
        curNum = 0
        for i in range(len(store)):
            if store[i] == 1:
                curNum  = sum(store[i:i+5])
                break
        return True if zeroNum+curNum==5 else False

 

链表

2 两数相加

class Solution:
    def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
        carry = 0
        re = ListNode(0)
        r = re
        while l1 or l2:
            a = l1.val if l1 else 0
            b = l2.val if l2 else 0
            s = a+b+carry
            carry = s//10
            r.next = ListNode(s%10)
            if l1: l1 = l1.next
            if l2: l2 = l2.next
            r = r.next
        if carry >0:
            r.next = ListNode(1)
        return re.next

 

445 两数相加II 

本题的主要难点在于链表中数位的顺序与我们做加法的顺序是相反的,为了逆序处理所有数位,我们可以使用栈:把所有数字压入栈中,再依次取出相加。计算过程中需要注意进位的情况。

 

class Solution:
    def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
        s1, s2 = [], []
        while l1:
            s1.append(l1.val)
            l1 = l1.next
        while l2:
            s2.append(l2.val)
            l2 = l2.next
        ans = None
        carry = 0
        while s1 or s2 or carry != 0:
            a = 0 if not s1 else s1.pop()
            b = 0 if not s2 else s2.pop()
            cur = a + b + carry
            carry = cur // 10
            cur %= 10
            curnode = ListNode(cur)
            curnode.next = ans
            ans = curnode
        return ans

面试题 6.从尾到头打印链表——栈

方法一:递归法

先走至链表末端,回溯时依次将节点值加入列表 ,这样就可以实现链表值的倒序输出。

Python 算法流程:

递推阶段: 每次传入 head.next ,以 head == None(即走过链表尾部节点)为递归终止条件,此时返回空列表 [] 。

回溯阶段: 利用 Python 语言特性,递归回溯时每次返回 当前 list + 当前节点值 [head.val] ,即可实现节点的倒序输出。

时间复杂度 O(N): 遍历链表,递归 N次。

空间复杂度 O(N): 系统递归需要使用 O(N)的栈空间。

class Solution:
    def reversePrint(self, head: ListNode) -> List[int]:
        
        return self.reversePrint(head.next)+[head.val] if head else []

方法二:辅助栈法

链表特点: 只能从前至后访问每个节点。

题目要求: 倒序输出节点值。

这种 先入后出 的需求可以借助 栈 来实现。

时间复杂度 O(N): 入栈和出栈共使用 O(N)时间。

空间复杂度 O(N): 辅助栈 stack 和数组 res 共使用 O(N) 的额外空间。

class Solution:
  def reversePrint(self, head):
    stack = []
    while head:
      stack.append(head.val)
      head = head.next
     return stack[::-1] # 直接返回 stack 的倒序列表

 

18 删除链表的节点

给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。

返回删除后的链表的头节点。

输入: head = [4,5,1,9], val = 5

输出: [4,1,9]

解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.

本题中,输入的类型为:head: ListNode, val: int,即 val 的类型是整形;

在《剑指offer》中,默认输入为 head: ListNode, val: ListNode,即 val 的类型是链表。

(1)val:int解法

class Solution:
    def deleteNode(self, head: ListNode, val: int) -> ListNode:
        d = ListNode(0)  # 设置伪结点
        d.next = head
        if head.val == val: return head.next # 头结点是要删除的点,直接返回
        while head and head.next:
            if head.next.val == val:   # 找到了要删除的结点,删除
                head.next = head.next.next
            head = head.next
        return dummy.next

时间复杂度:O(N)。N 为链表的长度,最坏情况下,要删除的结点位于链表末尾,这时我们需要遍历整个链表。

空间复杂度:O(1)。仅使用了额外的 d。

(2)val: ListNode 解法:信息交换法

class Solution:
    def deleteNode(self, head, val):
        if head is None or val is None:
            return None
        if val.next is not None:  # 待删除节点不是尾节点
            tmp = val.next
            val.val = tmp.val
            val.next = tmp.next
        elif head == val:  # 待删除节点只有一个节点,此节点为头节点
            head = None
        else:
            cur = head    # 待删除节点为尾节点
            while cur.next != val:
                cur = cur.next
            cur.next = None
        return head

22. 链表中倒数第k个节点

输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。例如,一个链表有6个节点,从头节点开始,它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个节点是值为4的节点。

示例:

给定一个链表: 1->2->3->4->5, 和 k = 2.

返回链表 4->5.

 

用快慢指针,快指针比慢指针快k步,到尾结点了慢指针就是倒数第k个结点。

class Solution(object):
    def getKthFromEnd(self, head, k):
          if not head: return None
          fast = head
          slow = head
          for _ in range(k):
              if fast:
                 fast = fast.next
               else:
                   return None
          while fast:
              fast = fast.next
              slow = slow.next
           return slow 

24. 反转链表 

定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。

示例:

输入: 1->2->3->4->5->NULL

输出: 5->4->3->2->1->NULL

class Solution:
    def reverseList(self, head):
        if not head or not head.next: return head
        # 这里的p就是最后一个节点
        p = self.reverseList(head.next)
        # 如果链表是 1->2->3->4->5,那么此时的p就是5
		# 而head是4,head的下一个是5,下下一个是空
		# 所以head.next.next 就是5->4
        head.next.next = head
       # 防止链表循环,需要将head.next设置为空 
        head.next = None
        # 每层递归函数都返回cur,也就是最后一个节点
        return p

25. 合并两个排序的链表

输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。

示例1:

输入:1->2->4, 1->3->4

输出:1->1->2->3->4

 

class Solution:
     def mergeTwoLists(self,l1,l2):
         if not l1: return l2
         if not l2: return l1
         if l1.val > l2.val:
             ret = ListNode(l2.val) 
             ret.next = self.mergeTwoLists(l1,l2.next) 
          else:
              ret = ListNode(l1.val)
              ret.Tnext =self.mergeTwoLists(l1.next,l2)
          return ret 
      

 

23. 合并K个排序链表 

https://leetcode-cn.com/problems/merge-k-sorted-lists/solution/xiong-mao-shua-ti-python3-3chong-jie-fa-bao-li-you/

 

暴力法:

遍历所有链表,将所有节点的值放到一个数组中。

将这个数组排序,然后遍历所有元素得到正确顺序的值。

用遍历得到的值,创建一个新的有序链表。

class Solution:
    def mergeKLists(self, lists: List[ListNode]) -> ListNode:
        nodes = []
        for l in lists:
            while l:
                nodes.append(l.val)
                l = l.next
        point = head = ListNode(0)
        for x in sorted(nodes):
            point.next = ListNode(x)
            point = point.next
        return head.next

 

35.复杂链表的复制

由于图的遍历方式有深度优先搜索和广度优先搜索,同样地,对于此链表也可以使用深度优先搜索和广度优先搜索两种方法进行遍历。
1.从头结点 head 开始拷贝;
2.由于一个结点可能被多个指针指到,因此如果该结点已被拷贝,则不需要重复拷贝;
3.如果还没拷贝该结点,则创建一个新的结点进行拷贝,并将拷贝过的结点保存在哈希表中;
4.使用递归拷贝所有的 next 结点,再递归拷贝所有的 random 结点。

【算法题】数据结构系列_第2张图片

class Solution:
    def copyRandomList(self,Head):
        def dfs(head):
            if not head: return None
            if head in visited:
                  return visited[head]
             #创建新节点
             copy= Node(head.val,None,None)
             visited[head] = copy
             copy.next = dfs(head.next)
             copy.random = dfs(head.random)
             return copy
         visited = {}
          return dfs(head)
   

52. 两个链表的第一个公共节点 & 160. 相交链表

双指针

我们使用两个指针 node1,node2 分别指向两个链表 headA,headB 的头结点,然后同时分别逐结点遍历,当 node1 到达链表 headA 的末尾时,重新定位到链表 headB 的头结点;当 node2 到达链表 headB 的末尾时,重新定位到链表 headA 的头结点。
这样,当它们相遇时,所指向的结点就是第一个公共结点。
class Solution:
    def func(headA,headB):
        ha, hb = headA, headB
        while ha != hb:
            ha = ha.next if ha else headB
            hb = hb.next if hb else headA
            return ha

142 环形链表——快慢指针

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

 

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。

说明:不允许修改给定的链表。

【算法题】数据结构系列_第3张图片

【算法题】数据结构系列_第4张图片

【算法题】数据结构系列_第5张图片

快慢指针(无额外空间)  先判断链表有没有环

初始时,快慢指针同时指向头结点。

若快指针且快指针的下一节点不为空,则慢指针走一步,快指针走两步;

若某时,快慢指针相遇,标明链表有环;快慢指针相遇时,头结点与快慢指针相遇节点同步(每次走一步)向前走,直至再次相遇,该相遇节点为入环的第一个节点。

若快指针或快指针的下一节点为空,标明链表无环,返回空。

 

如果链表有环,则让快指针回到链表的头重新出发,

当快慢指针相遇的那个节点即是链表环的起点。

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None




class Solution:
    def detectCycle(self, head: ListNode) -> ListNode:
        if not head or not head.next:
            return None
        slow, fast = head, head
        while fast and fast.next:
            slow, fast = slow.next, fast.next.next
            if slow == fast:
                while head != slow:  #注意
                    head = head.next
                    slow = slow.next
                return head
        return None

146 LRU缓存机制——哈希表

运用你所掌握的数据结构,设计和实现一个  LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。

获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。

写入数据 put(key, value) - 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。

进阶:你是否可以在 O(1) 时间复杂度内完成这两种操作?

思路:要O(1)的操作复杂度,暗示哈希表。

示例:

LRUCache cache = new LRUCache( 2 /* 缓存容量 */ );

cache.put(1, 1);

cache.put(2, 2);

cache.get(1);       // 返回  1

cache.put(3, 3);    // 该操作会使得密钥 2 作废

cache.get(2);       // 返回 -1 (未找到)

cache.put(4, 4);    // 该操作会使得密钥 1 作废

cache.get(1);       // 返回 -1 (未找到)

cache.get(3);       // 返回  3

cache.get(4);       // 返回  4

 

这个问题可以用哈希表,辅以双向链表记录键值对的信息。所以可以在 O(1)时间内完成 put 和 get 操作,同时也支持 O(1) 删除第一个添加的节点。

 

class ListNode:
    def __init__(self, key=None, value=None):
        self.key = key
        self.value = value
        self.prev = None
        self.next = None

class LRUCache:
    def __init__(self, capacity: int):
        self.capacity = capacity
        self.hashmap = {}
        # 新建两个节点 head 和 tail
        self.head = ListNode()
        self.tail = ListNode()
        # 初始化链表为 head <-> tail
        self.head.next = self.tail
        self.tail.prev = self.head


    # 因为get与put操作都可能需要将双向链表中的某个节点移到末尾,所以定义一个方法
    def move_node_to_tail(self, key):
            # 先将哈希表key指向的节点拎出来,为了简洁起名node
            #      hashmap[key]                          hashmap[key]
            #           |                                          |
            #           V              -->                         V
            # prev <-> node <-> next         pre <-> next   ...   node
            node = self.hashmap[key]
            node.prev.next = node.next
            node.next.prev = node.prev
            # 之后将node插入到尾节点前
            #                 hashmap[key]                hashmap[key]
            #                      |                            |
            #                      V        -->                 V
            # prev <-> tail  ...  node          prev <-> node <-> tail
            node.prev = self.tail.prev
            node.next = self.tail
            self.tail.prev.next = node
            self.tail.prev = node

    def get(self, key: int) -> int:
        if key in self.hashmap:
            # 如果已经在链表中了久把它移到末尾(变成最新访问的)
            self.move_node_to_tail(key)
        res = self.hashmap.get(key, -1)
        if res == -1:
            return res
        else:
            return res.value

    def put(self, key: int, value: int) -> None:
        if key in self.hashmap:
            # 如果key本身已经在哈希表中了就不需要在链表中加入新的节点
            # 但是需要更新字典该值对应节点的value
            self.hashmap[key].value = value
            # 之后将该节点移到末尾
            self.move_node_to_tail(key)
        else:
            if len(self.hashmap) == self.capacity:
                # 去掉哈希表对应项
                self.hashmap.pop(self.head.next.key)
                # 去掉最久没有被访问过的节点,即头节点之后的节点
                self.head.next = self.head.next.next
                self.head.next.prev = self.head
            # 如果不在的话就插入到尾节点前
            new = ListNode(key, value)
            self.hashmap[key] = new
            new.prev = self.tail.prev
            new.next = self.tail
            self.tail.prev.next = new
            self.tail.pre

 

148 排序链表——递归

在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。

示例 1:

输入: 4->2->1->3

输出: 1->2->3->4

示例 2:

输入: -1->5->3->4->0

输出: -1->0->3->4->5

 

递归排序,

1. 找中点,把链表一分为二

2. 递归处理左右半边

3. 合并排好序的部分

class Solution(object):
  def sortList(self, head):
    if not head or not head.next:
      return head
    pre, slow, fast = head, head, head
    while fast and fast.next:
      pre = slow
      slow = slow.next
      fast = fast.next.next      
    pre.next = None
    left, right = self.sortList(head), self.sortList(slow)
    return self.merge(left, right)
   def merge(self, l1, l2):
     if not l1: 
       return l2
      if not l2:
        return l1
      if l1.val < l2.val:
        head = ListNode(l1.val)
        head.next = self.merge(l1.next,l2)
       else:
         head = ListNode(l2.val)
         head.next = self.merge(l1, l2.next)
        return head

 

25. K 个一组翻转链表 

给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。

k 是一个正整数,它的值小于或等于链表的长度。

如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序

示例:

给你这个链表:1->2->3->4->5

当 k = 2 时,应当返回: 2->1->4->3->5

当 k = 3 时,应当返回: 3->2->1->4->5

说明:

你的算法只能使用常数的额外空间。

你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

 

递归

class Solution:
    def reverseKGroup(self, head: ListNode, k: int) -> ListNode:
        cur = head
        count = 0
        while cur and count!= k:
            cur = cur.next
            count += 1
        if count == k:
            cur = self.reverseKGroup(cur, k)
            while count:
                tmp = head.next
                head.next = cur
                cur = head
                head = tmp
                count -= 1
            head = cur   
        return head

234. 回文链表

请判断一个链表是否为回文链表。

示例 1:

输入: 1->2

输出: false

示例 2:

 

输入: 1->2->2->1

输出: true

进阶:

你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?

快慢指针 反转链表 空间复杂度O(1)

用快慢指针找中点

class Solution:
    def isPalindrome(self, head: ListNode) -> bool:
        if not head or not head.next: return True
        fast = slow = head
        newHead = None
        while fast and fast.next:
            temp = slow
            fast = fast.next.next
            slow = slow.next
            # 反转前半部分链表
            temp.next = newHead
            newHead = temp
        # 根据fast的位置(在最后还是为空)判断链表总节点数的奇偶性
        # 进而决定是否调节slow(使其成为后半段的起点)
        if fast:
            slow = slow.next
        # 遍历两段链表
        p, q = newHead, slow
        # 由上述,两段链表是等长的
        while p:
            if p.val != q.val:
                return False
            p = p.next
            q = q.next
        # 因为等长,所以一旦全部相等就可以判定为回文
        return True

 

栈和队列

9.用两个栈实现队列

用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )

示例 1:

输入:

["CQueue","appendTail","deleteHead","deleteHead"]

[[],[3],[],[]]

输出:[null,null,3,-1]

【算法题】数据结构系列_第6张图片

1. 栈无法实现队列功能: 栈底元素(对应队首元素)无法直接删除,需要将上方所有元素出栈。

2. 双栈实现列表倒序: 设有含三个元素的栈 A=[1,2,3] 和空栈 B=[]。若循环执行 A 元素出栈并添加入栈 B ,直到栈 A 为空,则 A=[], B=[3,2,1],即 栈 B 元素实现栈 A元素倒序 。

3. 栈 B可以实现队首元素删除: 因为 A 的栈底元素对应 B的栈顶元素, B执行出栈则相当于删除了队首元素(即 A 的栈底元素)。

加入队尾 appendTail()函数: 将整形 val 加入栈 A 即可。

删除队首deleteHead()函数: 有以下三种情况。 

  • 当栈 B 不为空: B中仍有完成倒序的元素,因此直接返回 B 的栈顶元素。
  • 否则,当 A 为空: 即两个栈都为空,无元素,因此返回 -1
  • 否则: 将栈 A 元素全部转移至栈 B 中,实现元素倒序,并返回 stack_pop 的栈顶元素。

时间复杂度: appendTail()函数为 O(1) ;deleteHead() 函数在 N次队首元素删除操作中总共需完成 N个元素的倒序。

空间复杂度 O(N) : 最差情况下,栈 A 和 B 共保存 N个元素。

class CQueue:
  def __init__(self):
    self.A, self.B = [],[]
  def appendTail(self, value):
     self.A.append(value)
  def deteleHead(self):
    if self.B: return self.B.pop()
    if not self.A: return -1
    while self.A:
      self.B.append(self.A.pop())
    return self.B.pop()
     

 

30. 包含min函数的栈、最小栈

定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。

class MinStack:
    def __init__(self):
        self.stack = []
        self.min_stack = [] 
    def push(self,x):
        self.stack.append(x)
        if self.min_stack and x>=self.min_stack[-1]:
            self.min_stack.append(self.min_stack[-1])
         else:
             self.min_stack.append(x)
    def min(self):
        return self.min_stack[-1]
    def pop(self):
        self.stack = self.stack[:-1]
        self.min_stack = self.min_stack[:-1]
    def top(self):  
        return self.stack[-1]

 

31.栈的压入、弹出序列

我们使用一个栈 st 来模拟该操作。将 pushed 数组中的每个数依次入栈,同时判断这个数是不是 popped 数组中下一个要 pop 的值,如果是就把它 pop 出来。最后检查栈是否为空。

class Solution:
    def validateStackSequeces(self,pushed,poped):
        stack = []
        j = 0
        for x in pushed:
            stack.append(x)
            while stack and j < len(popped) and stack[-1] == popped[j]:
                stack.pop()
                j += 1
         return not stack
         

20. 有效的括号 

如果 c 是左括号,则入栈 push;

否则通过哈希表判断括号对应关系,若 stack 栈顶出栈括号 stack.pop() 与当前遍历括号 c 不对应,则提前返回 false

算法流程

如果 c 是左括号,则入栈 push;

否则通过哈希表判断括号对应关系,若 stack 栈顶出栈括号 stack.pop() 与当前遍历括号 c 不对应,则提前返回 false。

提前返回优点: 在迭代过程中,提前发现不符合的括号并且返回,提升算法效率。

解决边界问题:

栈 stack 为空: 此时 stack.pop() 操作会报错;因此,我们采用一个取巧方法,给 stack 赋初值 ?,并在哈希表 dic 中建立 key: '?',value:'?'的对应关系予以配合。此时当 stack 为空且 c 为右括号时,可以正常提前返回 false;

字符串 s 以左括号结尾: 此情况下可以正常遍历完整个 s,但 stack 中遗留未出栈的左括号;因此,最后需返回 len(stack) == 1,以判断是否是有效的括号组合。

复杂度分析

时间复杂度 O(N):正确的括号组合需要遍历 11 遍 s;

空间复杂度 O(N):哈希表和栈使用线性的空间大小。

class Solution:
    def isValid(self, s: str) -> bool:
        dic = {'{':'}','[':']','(':')','?':'?'}
        stack = ['?']
        for c in s:
            if c in dic:
                stack.append(c)
            elif dic[stack.pop()] != c:
                return False
        return len(stack)==1

59.II队列的最大值

看到不少同学直接用max()函数,显然这是不符合题目中O(1)时间复杂度的。因为max()会在列表中检查每一个元素,从而返回最大值,所以max()是O(n)的时间复杂度。

class MaxQueue:
    def __init__(self):
        from collections import deque
        self.que = deque()
        self.sort_que = deque()


    def max_value(self) -> int:
        if not self.sort_que: return -1
        else:
            return self.sort_que[0]


    def push_back(self, value: int) -> None:
        self.que.append(value)
        while self.sort_que and self.sort_que[-1] < value:
            self.sort_que.pop()
        self.sort_que.append(value)


    def pop_front(self) -> int:
        if not self.que: return -1
        res = self.que.popleft()
        if res == self.sort_que[0]:
            self.sort_que.popleft()
        return res

739. 每日温度

根据每日 气温 列表,请重新生成一个列表,对应位置的输出是需要再等待多久温度才会升高超过该日的天数。如果之后都不会升高,请在该位置用 0 来代替。

例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。

维护递减栈,后入栈的元素总比栈顶元素小。
比对当前元素与栈顶元素的大小 
若当前元素 < 栈顶元素:入栈
若当前元素 > 栈顶元素:弹出栈顶元素,记录两者下标差值即为所求天数
这里用栈记录的是 T 的下标。
class Solution:
    def dailyTemperatures(self, T: List[int]) -> List[int]:
        stack = []
        n = len(T)
        res = [0]*n
        for key,value in enumerate(T):
            if stack:
                while stack and T[stack[-1]]

 

636. 函数的独占时间 

给出一个非抢占单线程CPU的 n 个函数运行日志,找到函数的独占时间。

 

每个函数都有一个唯一的 Id,从 0 到 n-1,函数可能会递归调用或者被其他函数调用。

日志是具有以下格式的字符串:function_id:start_or_end:timestamp。例如:"0:start:0" 表示函数 0 从 0 时刻开始运行。"0:end:0" 表示函数 0 在 0 时刻结束。

函数的独占时间定义是在该方法中花费的时间,调用其他函数花费的时间不算该函数的独占时间。你需要根据函数的 Id 有序地返回每个函数的独占时间。

示例 1:

 

输入:

n = 2

logs = 

["0:start:0",

 "1:start:2",

 "1:end:5",

 "0:end:6"]

输出:[3, 4]

说明:

函数 0 在时刻 0 开始,在执行了  2个时间单位结束于时刻 1。

现在函数 0 调用函数 1,函数 1 在时刻 2 开始,执行 4 个时间单位后结束于时刻 5。

函数 0 再次在时刻 6 开始执行,并在时刻 6 结束运行,从而执行了 1 个时间单位。

所以函数 0 总共的执行了 2 +1 =3 个时间单位,函数 1 总共执行了 4 个时间单位。

 

思路:

我们可以使用栈来模拟函数的调用,即在遇到一条包含 start 的日志时,我们将对应的函数 id 入栈;在遇到一条包含 end 的日志时,我们将对应的函数 id 出栈。在每一个时刻,栈中的所有函数均为被调用的函数,而栈顶的函数为正在执行的函数。

在每条日志的时间戳后,栈顶的函数会独占执行,直到下一条日志的时间戳,因此我们可以根据相邻两条日志的时间戳差值,来计算函数的独占时间。

我们依次遍历所有的日志,对于第 i 条日志,如果它包含 start,那么栈顶函数从其时间戳 time[i] 开始运行,即 prev = time[i];如果它包含 end,那么栈顶函数从其时间戳 time[i] 的下一个时间开始运行,即 prev = time[i] + 1。对于第 i + 1 条日志,如果它包含 start,那么在时间戳 time[i + 1] 时,有新的函数被调用,因此原来的栈顶函数的独占时间为 time[i + 1] - prev;如果它包含 end,那么在时间戳 time[i + 1] 时,原来的栈顶函数执行结束,独占时间为 time[i + 1] - prev + 1。在这之后,我们更新 prev 并遍历第 i + 2 条日志。在遍历结束后,我们就可以得到所有函数的独占时间。

class Solution:
    def exclusiveTime(self, n: int, logs: List[str]) -> List[int]:
        res = [0] * n
        stack = []
        for s in logs:
            temp = s.split(':')
            if temp[1] == 'start':
                stack.append(temp)
            else:
                time = int(temp[2]) - int(stack.pop()[2]) + 1
                res[int(temp[0])] += time
                if stack:
                    res[int(stack[-1][0])] -= time   
        return res

 

大鱼吃小鱼 牛客

牛牛最近喜欢上了俄罗斯套娃、大鱼吃小鱼这些大的包住小的类型的游戏。

于是牛爸给牛牛做了一个特别版的大鱼吃小鱼游戏,他希望通过这个游戏

能够近一步提高牛牛的智商。

游戏规则如下:

现在有N条鱼,每条鱼的体积为Ai,从左到右拍成一排。

A数组是一个排列。

牛牛每轮可以执行一次大鱼吃小鱼的操作

一次大鱼吃小鱼的操作:对于每条鱼,它在每一次操作时会吃掉右边比自己小的第一条鱼

值得注意的时,在一次操作中,每条鱼吃比自己小的鱼的时候是同时发生的。

举一个例子,假设现在有三条鱼,体积为分别[5,4,3],5吃4,4吃3,一次操作后就剩下[5]一条鱼。

牛爸问牛牛,你知道要多少次操作,鱼的数量就不会变了嘛?

输入:

给定N;

给定A数组,数组下标从0开始。

输出:

一行一个数字表示要多少次操作,鱼的数量就不会变了。

输入

6,[4,3,2,3,2,1]

输出

2

说明

[4,3,2,3,2,1]-->[4,3]-->[4]

# @param N int整型 N条鱼
# @param A int整型一维数组 每条鱼的体积为Ai
# @return int整型
#
class Solution:
    def solve(self , N , A ):
        li = []    # li这个栈只保留递减序列中最大的那个数
        res = 0
        for a in A[::-1]:
            times = 0
            while li:
                if a > li[-1][0]:
                    temp = li.pop()
                    times = max(times + 1, temp[1])
                else:
                    break
            res = max(res, times)
            li.append((a, times))
        return res

字符串

1-2 判断是否互为字符重排

给定两个字符串 s1 和 s2,请编写一个程序,确定其中一个字符串的字符重新排列后,能否变成另一个字符串。

输入: s1 = "abc", s2 = "bca"

输出: true 

1.分别计算s1和s2中每个字母的出现次数;

2.然后比较

class Solution:
    def CheckPermutation(self, s1: str, s2: str) -> bool:
        count1 = [0 for _ in range(26)]
        count2 = [0 for _ in range(26)]
        for s in s1:
            count1[ord(s)-ord('a')] += 1 #ord()函数主要用来返回对应字符的ascii码
        for s in s2:
            count2[ord(s)-ord('a')] += 1
        for i in range(26):
            if count1[i] != count2[i]:
                return False
        return True

38.字符串排列

回溯法解决:
    第一步,求所有可能出现在第一个位置的字符,即把第一个字符和后面所有的字符进行交换
    第二步,固定住第一个字符,求后面字符的全排列.这时候我们仍把后面的字符分成两部分: 后面字符的第一个字符以及这个字符后面的所有字符.
    然后把第一个字符逐一和它后面的字符交换.
    算法最后的结果可能会有重复的情况出现,我们使用python自带的set()函数来去重
class Solution:
    def permutation(self, s):
        if not s: return []
        result = []
        s = list(s)
        temp = []
        self.stringSort(s,temp,result)
        return list(set(result))
     def stringSort(self,s,temp,result):
         if len(s)==1: 
             temp.append(s[0])
             result.append(''.join(temp))
             temp.pop()
             return
          for i in range(0,len(s)):
              if i!=0 and s[i] == s[0]: continue
              s[0],s[i] = s[i],s[0]
              temp.append(s[0])
              self.stringSort(s[1:],temp,result)
              temp.pop()
              s[i],s[0] = s[0],s[i]
      

19 正则表达式匹配

请实现一个函数用来匹配包含'. '和'*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(含0次)。在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,但与"aa.a"和"ab*a"均不匹配。

class Solution:
    def isMatch(self, s: str, p: str) -> bool:
        if not p: return not s
        # 第一个字母是否匹配
        first_match = bool(s and p[0] in {s[0],'.'})
        # 如果 p 第二个字母是 *
        if len(p) >= 2 and p[1] == "*":
            return self.isMatch(s, p[2:]) or \
            first_match and self.isMatch(s[1:], p)
        else:
            return first_match and self.isMatch(s[1:], p[1:])

20. 表示数值的字符串

验证给定的字符串是否可以解释为十进制数字

请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100"、"5e2"、"-123"、"3.1416"、"0123"及"-1E-16"都表示数值,但"12e"、"1a3.14"、"1.2.3"、"+-5"及"12e+5.4"都不是。

有效数字,自动机

画出 状态转移表 ,结构为 states[n]存储 n 个状态;

states[i] 为一个哈希表,表示从当前状态允许跳转到的下一个状态。

主循环中遍历字符串,通过状态转移表判断结构是否成立:

若中途遇到无法跳转的状态,直接返回 False ;

若成功遍历完字符串,要判断结束状态是否在允许的结束状态内,本题为 [2,3,7,8]。

 

class Solution:
    def isNumber(self, s: str) -> bool:
        state = [
            {},
            # 状态1,初始状态(扫描通过的空格)
            {"blank": 1, "sign": 2, "digit": 3, ".": 4},
            # 状态2,发现符号位(后面跟数字或者小数点)
            {"digit": 3, ".": 4},
            # 状态3,数字(一直循环到非数字)
            {"digit": 3, ".": 5, "e": 6, "blank": 9},
            # 状态4,小数点(后面只有紧接数字)
            {"digit": 5},
            # 状态5,小数点之后(后面只能为数字,e,或者以空格结束)
            {"digit": 5, "e": 6, "blank": 9},
            # 状态6,发现e(后面只能符号位, 和数字)
            {"sign": 7, "digit": 8},
            # 状态7,e之后(只能为数字)
            {"digit": 8},
            # 状态8,e之后的数字后面(只能为数字或者以空格结束)
            {"digit": 8, "blank": 9},
            # 状态9, 终止状态 (如果发现非空,就失败)
            {"blank": 9}
        ]
        cur_state = 1
        for c in s:
            if c.isdigit():
                c = "digit"
            elif c in " ":
                c = "blank"
            elif c in "+-":
                c = "sign"
            if c not in state[cur_state]:
                return False
            cur_state = state[cur_state][c]
        if cur_state not in [3, 5, 8, 9]:
            return False
        return True

作弊法--

class Solution:
  def isNumber(self,s):
    try:
      num = float(s)
      return True
     except:
      return False

48. 最长不含重复字符的子字符串 

这道题主要用到思路是:滑动窗口
什么是滑动窗口?
其实就是一个队列,比如例题中的 abcabcbb,进入这个队列(窗口)为 abc 满足题目要求,当再进入 a,队列变成了 abca,这时候不满足要求。所以,我们要移动这个队列!
如何移动?
我们只要把队列的左边的元素移出就行了,直到满足题目要求!
一直维持这样的队列,找出队列出现最长的长度时候,求出解!
时间复杂度:O(n)
class Solution:
    def lengthOfLongestSubstring(self, s: str):
        if not s: return 0
        lookup = list()
        n = len(s)
        max_len, min_len = 0,0
        for i in range(n):
            val = s[i]
            if val in lookup:
                index = lookup.index(val)
                lookup = lookup[index+1:]
                lookup.append(val)
                cur_len = len(lookup)
             else:
                 lookup.append(val)
                 cur_len+=1
              if cur_len>max_len: 
                   max_len = cur_len
          return max_len

50. 第一个只出现一次的字符

哈希

class Solution:
    def firstUniqChar(self, s: str) -> str:
        dict = {} 
        for c in s:
            dict[c] =  dict.get(c,0)+1 #函数返回指定键的值,如果值不在字典中返回默认值0。
        for c in s:
             if dict[c]==1:
                 return c
        return " "

58 - I. 翻转单词顺序

遍历添加法

先处理字符串,将首尾空格都删除;
倒序遍历字符串,当第一次遇到空格时,添加s[i + 1: j](即添加一个完整单词);
然后,将直至下一个单词中间的空格跳过,并记录下一个单词尾部j;
继续遍历,直至下一次遇到第一个空格,回到1.步骤;
由于首部没有空格,因此最后需要将第一个单词加入,再return。

 

class Solution:
    def reverseWords(self, s: str) -> str:
        s = s.strip()
        res = ""
        i, j = len(s) - 1, len(s)
        while i > 0:
            if s[i] == ' ':
                res += s[i + 1: j] + ' '
                while s[i] == ' ': i -= 1
                j = i + 1
            i -= 1
        return res + s[:j]
          

python可一行实现。

def reverseWords1(self, s: str) -> str:
        return " ".join(s.split()[::-1]) #split拆分字符串。通过指定分隔符对字符串进行切片,并返回分割后的字符串列表
join方法用于将序列中的元素以指定的字符连接生成一个新的字符串。

 

58 - II. 左旋转字符串

python 切片

class Solution:
    def reverseLeftWords(self, s: str, n: int) -> str:
        return s[n:]+s[:n]

5. 最长回文子串 

回文字符:正读和反读都一样的字符串。当不存在时,返回字符串的最后一个字符;

根据字符串的长度,利用滑动窗口,每循环一次减少1位,得到所有的子串,且从最长子串开始,逐个进行反转对比,如果相等则直接返回,即为最大回文子串。该方法效率一般,容易想到,好理解;

class Solution:
    def longestPalindrome(self, s: str) -> str:
        m = len(s)
        if m == 0: return ""
        tmp = m
        while True:
            i = 0
            while i+tmp <= m:
                left = s[i:i+tmp]
                right = left[::-1]
                if left == right:
                    return left
                i = i+ 1
            tmp = tmp - 1

 

14. 最长公共前缀

先使用zip(*li)对字符串数组进行元素打包,然后使用set()函数去重,如果去重后的元素长度为1,则是公共前缀。

1、*xx 在调用函数的时候是解包作用,因此:*ll ==> "flower", "flow", "flqwe"

2、 zip(*ll) ==> zip("flower", "flow", "flqwe") ,拆开成每个字母,两两一组

class Solution:
    def longestCommonPrefix(self, strs: List[str]) -> str:
        s = ""
        for i in zip(*strs):
            #zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。
          
            if len(set(i)) == 1:
            #set() 函数创建集合
                s += i[0]
            else:
                break
        return s

 

1071. 字符串的最大公因子

如果 str1 和 str2 拼接后等于 str2和 str1 拼接起来的字符串(注意拼接顺序不同),那么一定存在符合条件的字符串 X。
gcd(a,b) 表示 a 和 b 的的最大公约数。
 先判断 str1 和 str2 拼接后是否等于 str2 和 str1 拼接起来的字符串,如果等于直接输出长度为 gcd(len1,len2)的前缀串即可,否则返回空串。
  
  class Solution:
    def gcdOfStrings(self, str1: str, str2: str) -> str:
        candidate_len = math.gcd(len(str1), len(str2))
        candidate = str1[: candidate_len]
        if str1 + str2 == str2 + str1:
            return candidate
        return ''

其他

真题:坐板凳

【算法题】数据结构系列_第7张图片

a = []
n = int(input())
m = int(input())
for i in range(n):
    a.append(int(input()))
kmax = max(a)
for i in range(m):
    a[a.index(min(a))] += 1
print(max(a),kmax+m) #最小的相当于填补人少的椅子,最大的相当于都坐在人多的椅子上

1298. 你能从盒子里获得的最大糖果数

给你 n 个盒子,每个盒子的格式为 [status, candies, keys, containedBoxes] ,其中:

  • 状态字 status[i]:整数,如果 box[i] 是开的,那么是 ,否则是
  • 糖果数 candies[i]: 整数,表示 box[i] 中糖果的数目。
  • 钥匙 keys[i]:数组,表示你打开 box[i] 后,可以得到一些盒子的钥匙,每个元素分别为该钥匙对应盒子的下标。
  • 内含的盒子 containedBoxes[i]:整数,表示放在 box[i] 里的盒子所对应的下标。

给你一个 initialBoxes 数组,表示你现在得到的盒子,你可以获得里面的糖果,也可以用盒子里的钥匙打开新的盒子,还可以继续探索从这个盒子里找到的其他盒子。

请你按照上述规则,返回可以获得糖果的 最大数目 。

输入:status = [1,0,1,0], candies = [7,5,4,100], keys = [[],[],[1],[]], containedBoxes = [[1,2],[3],[],[]], initialBoxes = [0]

输出:16

解释:

一开始你有盒子 0 。你将获得它里面的 7 个糖果和盒子 1 和 2。

盒子 1 目前状态是关闭的,而且你还没有对应它的钥匙。所以你将会打开盒子 2 ,并得到里面的 4 个糖果和盒子 1 的钥匙。

在盒子 1 中,你会获得 5 个糖果和盒子 3 ,但是你没法获得盒子 3 的钥匙所以盒子 3 会保持关闭状态。

你总共可以获得的糖果数目 = 7 + 4 + 5 = 16 个。

思想:bfs模拟拆盒子过程
维护几个数组,
status:开着的盒子或者有钥匙的盒子
closed_box:已经有但打不开的盒子
queue:准备要打开的盒子队列
每打开一个盒子都要做三件事:
1.取钥匙:更新status,开对应锁着的盒子
2.取盒子中包含的盒子:
(1)已经有钥匙或者开着的(status[i]),添加到队列里
(2)否则添加到closed_box中,等拿到钥匙再打开
3.取糖果
class Solution:
    def maxCandies(self, status: List[int], candies: List[int], keys: List[List[int]], containedBoxes: List[List[int]], initialBoxes: List[int]) -> int:
        res = 0
        closed_box = [0] * 1010
        q = []      


        for i in initialBoxes: #遍历初始盒子
            if status[i]: #初始盒子为1,则加入要开的队列里,否则放进关闭盒子里
                q.append(i)
            else: 
                closed_box[i] = 1
        
        while q: #遍历需要打开的箱子
            cur = q.pop(0)
            
            # 取钥匙
            for i in keys[cur]: 
                status[i] = 1
                # 手上有这个盒子, 且这个盒子是关着的, 添加到队列里
                if closed_box[i]:
                    q.append(i)
                    # 注意keys会有很多重复的, 盒子用完记得标记为0
                    closed_box[i] = 0
                    
            # 取糖果
            res += candies[cur]
   
            # 取盒子
            for i in containedBoxes[cur]:
                if status[i]:
                    q.append(i)
                else:
                    closed_box[i] = 1
        return res

你可能感兴趣的:(算法题,算法)