Python:算法题目

1,147 阅读42分钟

1 斐波那契(剑指offer10)

斐波那契数列指的是这样一个数列 0, 1, 1, 2, 3, 5, 8, 13,特别指出:第0项是0,第1项是第一个1。
从第三项开始,每一项都等于前两项之和。

写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下:
F(0) = 0,   F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契数列由 01 开始,之后的斐波那契数就是由之前的两数相加而得出。
答案需要取模 1e9+71000000007),如计算初始结果为:1000000008,请返回 1
方法1:递归
递归法:
原理: 把 f(n) 问题的计算拆分成f(n−1) 和f(n−2) 两个子问题的计算,并递归,以 f(0) 和 f(1) 为终止条件。
缺点: 大量重复的递归计算,例如 f(n) 和f(n−1) 两者向下递归需要 各自计算f(n−2) 的值。

class Solution:
    def fib(self, n: int) -> int:
        a,b=0,1
        for i in range(n):
            a,b=b,a+b
        return a

image.png

方法2:动态规划
动态规划解析:
1、状态定义: 设 dp 为一维数组,其中 dp[i] 的值代表 斐波那契数列第i个数字 。
2、转移方程: dp[i+1]=dp[i]+dp[i−1] ,即对应数列定义 f(n+1)=f(n)+f(n-1);
3、初始状态:dp[0]=0, dp[1]=1 ,即初始化前两个数字;
4、返回值:dp[n] ,即斐波那契数列的第 n 个数字。

空间复杂度降低:
1、若新建长度为 n 的 dp 列表,则空间复杂度为 O(N) 。
2、由于 dp 列表第 i 项只与第 i−1 和第 i−2 项有关,因此只需要初始化三个整形变量 sum, a, b ,利用辅助变量 sum 使 a, b 两数字交替前进即可 (具体实现见代码) 。
3、节省了 dp 列表空间,因此空间复杂度降至 O(1) 。

循环求余法:
大数越界:随着 n 增大, f(n) 会超过 Int32 甚至 Int64 的取值范围,导致最终的返回值错误。
求余运算规则:设正整数 x, y, p, 求余符号为⊙ ,则有(x+y)⊙p=(x⊙p+y⊙p)⊙p 。
解析: 根据以上规则,可推出f(n)⊙p=[f(n−1)⊙p+f(n−2)⊙p]⊙p ,从而可以在循环过程中每次计算sum=(a+b)⊙1000000007 ,此操作与最终返回前取余等价。

复杂度分析:
时间复杂度 O(n) : 计算 f(n) 需循环 n 次,每轮循环内计算操作使用 O(1) 。
空间复杂度 O(1) : 几个标志变量使用常数大小的额外空间。

class Solution:
    def fib(self, n: int) -> int:
        a, b = 0, 1
        for _ in range(n):
            a, b = b, (a + b) % 1000000007
        return a

2 二分查找

# 二分搜索是一种在有序数组中查找某一特定元素的搜索算法。搜索过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束;
# 如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。如果在某一步骤数组为空,则代表找不到。
# 这种搜索算法每一次比较都使搜索范围缩小一半。

def binarySearch(nums, target):
    if len(nums) == 0:
        return -1

    left, right = 0, 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  # 向左查找
    # 数据不存在,返回-1
    return -1

re = binarySearch(nums=[-1, 0, 3, 5, 9, 12], target=2)
print(re)

3 无重复字符的最长子串

给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:

输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

示例 2:

输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

示例3:

输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
     请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。

解法1:动态规划+哈希表

状态定义: 设动态规划列表 dp ,dp[j] 代表以字符 s[j] 为结尾的 “最长不重复子字符串” 的长度。

转移方程: 固定右边界 j ,设字符 s[j] 左边距离最近的相同字符为 s[i] ,即 s[i] = s[j]1.当 i < 0 ,即 s[j] 左边无相同字符,则 dp[j] = dp[j-1] + 12.当 dp[j-1]<j-i ,说明字符 s[i] 在子字符串 dp[j-1]区间之外 ,则 dp[j] = dp[j-1]+13.当 dp[j−1]≥j−i ,说明字符 s[i] 在子字符串 dp[j−1]区间之中 ,则 dp[j] 的左边界由 s[i] 决定,即 dp[j]=j−i ;
故:当 i < 0时,由于dp[j−1]≤j 恒成立,因而dp[j−1]<j−i 恒成立,因此分支 1. 和 2. 可被合并。
返回值:max(dp) ,即全局的 “最长不重复子字符串” 的长度。

image.png

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        dic = {}
        res = tmp = 0
        for j in range(len(s)):
            i = dic.get(s[j], -1) # 
            dic[s[j]] = j # 
            tmp = tmp + 1 if tmp < j - i else j - i # dp[j - 1] -> dp[j]
            res = max(res, tmp) # max(dp[j - 1], dp[j])
        return res

解法2:滑动窗口

class Solution2:
    def lengthOfLongestSubstring(self, s: str) -> int:
        # 哈希集合,记录每个字符是否出现过
        occ = set()
        n = len(s)
        # 右指针,初始值为 -1,相当于我们在字符串的左边界的左侧,还没有开始移动
        rk, ans = -1, 0
        for i in range(n):
            if i != 0:
                # 左指针向右移动一格,移除一个字符
                occ.remove(s[i - 1])
            while rk + 1 < n and s[rk + 1] not in occ:
                # 不断地移动右指针
                occ.add(s[rk + 1])
                rk += 1
            # 第 i 到 rk 个字符是一个极长的无重复字符子串
            ans = max(ans, rk - i + 1)
        return ans

解法3:动态规划+双指针

哈希表 dic 统计: 指针 j 遍历字符 s ,哈希表统计字符 s[j] 最后一次出现的索引 。
更新左指针 i : 根据上轮左指针 i和 dic[s[j]] ,每轮更新左边界 i ,保证区间 [i+1,j] 内无重复字符且最大。
i=max(dic[s[j]],i)

更新结果res :取上轮res和本轮双指针区间[i+1,j] 的宽度(即j−i)中的最大值。
res=max(res,j−i)

复杂度分析:
时间复杂度 O(N) : 其中 N 为字符串长度,动态规划需遍历计算 dp 列表。
空间复杂度 O(1) : 字符的 ASCII 码范围为 0 ~ 127 ,哈希表 dic 最多使用O(128)=O(1) 大小的额外空间。

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        dic, res, i = {}, 0, -1
        for j in range(len(s)):
            if s[j] in dic:
                i = max(dic[s[j]], i) # 更新左指针 i
            dic[s[j]] = j # 哈希表记录
            res = max(res, j - i) # 更新结果
        return res

4 整数反转

给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。
如果反转后整数超过 32 位的有符号整数的范围 [−231,  2311] ,就返回 0。
示例 1:
输入:x = 123
输出:321

示例 2:
输入:x = -123
输出:-321

示例 3:
输入:x = 120
输出:21

示例 4:
输入:x = 0
输出:0

class Solution:
    def reverse(self, x: int) -> int:
        str1 = str(x)

        if str1[0] == '-':
            str1 = str1[0] + str1[:0:-1]
            a = int(str1)
            if (1 << 31) < abs(a):
                return 0
        else:
            str1 = str1[::-1]
            a = int(str1)
            if a > (1 << 31) - 1:
                return 0
        return a

5 丑数问题:判断数据是否为丑数

丑数:编写一个程序判断给定的数是否为丑数。  丑数就是只包含质因数 2, 3, 5 的正整数。

class Soluation:
    def chouShu(self, num: int):
        if num <= 0:
            return False
        focues = [2, 3, 5]
        for focue in focues:
            while num % focue == 0:  # 取模 - 返回除法的余数
                num //= focue  # 取整除 - 向下取接近商的整数   c //= a 等效于 c = c // a
        return num == 1     

6 丑数问题:找出第几个丑数

编写一个程序,找出第 n 个丑数。
丑数就是质因数只包含 2, 3, 5 的正整数。

示例:
输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。
说明:  
1 是丑数。
n 不超过1690class Solution:
    def nthUglyNumber(self, n: int) -> int:
        dp, a, b, c = [1] * n, 0, 0, 0
        for i in range(1, n):
            n2, n3, n5 = dp[a] * 2, dp[b] * 3, dp[c] * 5
            dp[i] = min(n2, n3, n5)
            if dp[i] == n2: a += 1
            if dp[i] == n3: b += 1
            if dp[i] == n5: c += 1
        return dp[-1]

7 回文字符串

给定一个字符串,判断该字符串中是否可以通过重新排列组合,形成一个回文字符串。
示例 1:
输入: "code"
输出: false

示例 2:
输入: "aab"
输出: true

示例 3:
输入: "carerac"
输出: true

class Solution:
    def canPermutePalindrome(self, s: str) -> bool:
        dic = {} # 创建一个字典 {'a':2,'b':1}
        for c in s: # 值在字符串中循环
            if dic.get(c) == None:  # 如果指定的k-v值为空
                dic[c] = 1
            else:
                dic[c] += 1 # 将value值写入dic字典
        count = 0
        for num in dic.values():  #返回一个迭代器,可以使用 list() 来转换为列表
            if num % 2 == 0:   # 如果num值可以被2整除
                continue  
            else: # 如果num值不能被2整除
                count += 1 #循环+1
        if count > 1:
            return False
        return True

8 回文排列

给定一个字符串 s ,返回其通过重新排列组合后所有可能的回文字符串,并去除重复的组合。
如不能形成任何回文排列时,则返回一个空列表。

示例 1:
输入: "aabb"
输出: ["abba", "baab"]

示例 2:
输入: "abc"
输出: []

class Solution:
    def generatePalindromes(self, s: str) -> List[str]:
        dic = collections.Counter(s)
        odd, h = [], ''
        for k in dic:
            if dic[k]%2 == 1: odd.append(k)
            h += k*(dic[k]//2)
        if len(odd) > 1: return []
        
        def dfs(cur, path):
            if not cur: 
                res.add(path)
            for i in range(len(cur)):
                if i == 0 or cur[i] != cur[i-1]:
                    dfs(cur[:i] + cur[i+1:], path + cur[i])
        
        res = set()
        dfs(h, '')
        
        return [p + p[::-1] for p in res] if not odd else [p + odd[0] + p[::-1] for p in res]

9 寻找丢失的数字

给定一个包含 [0, n] 中 n 个数的数组 nums ,找出 [0, n] 这个范围内没有出现在数组中的那个数。

示例 1:
输入:nums = [3,0,1]
输出:2
解释:n = 3,因为有 3 个数字,所以所有的数字都在范围 [0,3] 内。2 是丢失的数字,因为它没有出现在 nums 中。

示例 2:
输入:nums = [0,1]
输出:2
解释:n = 2,因为有 2 个数字,所以所有的数字都在范围 [0,2] 内。2 是丢失的数字,因为它没有出现在 nums 中。

class Solution:
    def missingNumber(self, nums: List[int]) -> int:
        return int(len(nums)*(len(nums)+1)/2-sum(nums))

10 获取正整数在Excel中对应的列名称

给定一个正整数,返回它在 Excel 表中相对应的列名称。

示例 1:
输入: 1
输出: "A"

示例 2:
输入: 28
输出: "AB"

示例 3:
输入: 701
输出: "ZY"

class Solution:
    def convertToTitle(self, n: int) -> str:
        res=''
        while n:
            res=chr((n-1)%26+ord('A'))+res
            n=(n-1)//26
        return res

11 获取两数之间的所有组合

找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
说明:
所有数字都是正整数。
解集不能包含重复的组合。 

示例 1:
输入: k = 3, n = 7
输出: [[1,2,4]]

示例 2:
输入: k = 3, n = 9
输出: [[1,2,6], [1,3,5], [2,3,4]]

from itertools import combinations

class Solution:
    def combinationSum3(self, k: int, n: int) -> List[List[int]]:
        nums=[1,2,3,4,5,6,7,8,9]
        res=[]
        for i in range(len(nums)): 
            res += list(combinations(nums,i))
        res=[x for x in res if len(x) == k]
        a=[]
        for j in res:
            if sum(j) ==n :
                a.append(list(j))
        return a

12 数组题目1

给定一个二进制数组, 计算其中最大连续 1 的个数。
示例:
输入:[1,1,0,1,1,1]
输出:3
解释:开头的两位和最后的三位都是连续 1 ,所以最大连续 1 的个数是 3.
 
提示:
输入的数组只包含 01 。
输入数组的长度是正整数,且不超过 10,000class Solution:
    def findMaxConsecutiveOnes(self, nums: List[int]) -> int:
        count,result=0,0  # 计数器
        for i in range(len(nums)):
            if nums[i]==1:  # 遍历列表,当值为1,count的值加1
                count+=1
            else:
                result=max(count,result) # 当值不为1,获取最大的值
                count=0  # 将计数器赋值为0,从新计数
        return max(result,count)

13 数组题目2

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
示例:
输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
说明:
必须在原数组上操作,不能拷贝额外的数组。
尽量减少操作次数。

class Solution:
    def moveZeroes(self, nums: List[int]) -> None:
        index=0
        for i in range(len(nums)):
            if nums[i] !=0: # 当num[i]不为0
                nums[index]=nums[i] # num[index]赋值为num[i]
                index +=1 
        for j in range(len(nums)):  #遍历列表
            if j>=index: # 当j的值大于index的值,num[j]赋值为0
                nums[j]=0
        return nums

14 搜索插入位置

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

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

示例 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

解题方法1class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        if target in nums:
            for i in range(len(nums)):
                if nums[i]==target:
                    return i
        else:
            if target<nums[0]:
                return 0
            elif target>nums[-1]:
                return len(nums)
            else:
                for i in range(len(nums)-1):
                    if nums[i]<target<nums[i+1]:
                        return i+1
解题方法2class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        nums.append(target)  # 直接将元素加入到数组
        nums.sort()  # 对数组进行排序
        return nums.index(target)  #获取数据的元素的下标

15 移除链表元素

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。
示例 1:
输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]

image.png

示例 2:
输入:head = [], val = 1
输出:[]

示例 3:
输入:head = [7,7,7,7], val = 7
输出:[]
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next
        
class Solution:
    def removeElements(self, head: ListNode, val: int) -> ListNode:
        prev=temp=ListNode()  # prev为指针,temp为临时
        temp.next=head # 临时值的下一个为头节点

        while head is not None:  # 当head不为空时
            if head.val==val: #如果head的值等于val
                prev.next=head.next # prev的下一个值等于head的下一个值
            else: # 如果head的值不等于val
                prev=prev.next # prev赋值给prev的下一个值
            head=head.next
        return temp.next

16 反转链表

反转一个单链表。

示例:

输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
进阶:
你可以迭代或递归地反转链表。你能否用两种方法解决这道题?

class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next
class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        n1,n2=head,None

        while n1:
            temp=n1.next  # temp临时指针 赋值给head下个值
            n1.next=n2  # n1.next=n2
            n2=n1 # n2=n1
            n1=temp # n1=temp  temp: [2,3]
        return n2

17 双指针

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

示例1:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None

class Solution:
    def mergeTwoLists(self, L1: ListNode, L2: ListNode) -> ListNode:
        cur=tep=ListNode(0)
        while L1 and L2:
            if L1.val<L2.val:
                cur.next,L1=L1,L1.next
            else:
                cur.next,L2=L2,L2.next
            cur=cur.next
        cur.next = L1 if L1 else L2
        # Python 三元表达式写法 A if x else B ,代表当 x = True 时执行 A ,否则执行 B
        return tep.next

18 交替打印1-100之间的整数

# 交替打印1,2,3,4,5...100之间的整数

import threading
import time

def threadA():
    """打印奇数"""
    for i in range(1, 100 + 1):
        if i % 2 != 0:
            lockb.acquire()
            print(i),
            locka.release()
            time.sleep(0.1)

def threadB():
    """打印偶数"""
    for i in range(1, 100 + 1):
        if i % 2 == 0:
            locka.acquire()
            print(i),
            lockb.release()
            time.sleep(0.1)

if __name__ == "__main__":
    locka = threading.Lock()
    lockb = threading.Lock()

    ta = threading.Thread(None, threadA)
    tb = threading.Thread(None, threadB)

    locka.acquire()  # 保证a先执行

    ta.start()
    tb.start()

    ta.join()

19 股票的最大利润

假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?

示例 1:

输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
     注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。

示例 2:

输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

方法1:暴力法

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        ans=0
        for i in range(len(prices)):
            for j in range(i+1,len(prices)):
                ans=max(ans,prices[j]-prices[i])
        return ans

方法2:动态规划

设共有 n 天,第 a 天买,第 b 天卖,则需保证 a<b ;可推出交易方案数共有:
(n−1)+(n−2)+⋯+2+1=n(n−1)/2

因此,暴力法的时间复杂度为 O(n^2) 。考虑使用动态规划降低时间复杂度,以下按照流程解题。
动态规划解析:
状态定义: 设动态规划列表 dp ,dp[i] 代表以 prices[i] 为结尾的子数组的最大利润(以下简称为前 i 日的最大利润 )。
转移方程: 由于题目限定 “买卖该股票一次” ,
因此前 i 日最大利润 dp[i] 等于前i1 日最大利润 dp[i-1]和第 i 日卖出的最大利润中的最大值。
前i日最大利润=max(前(i1)日最大利润,第i日价格−前i日最低价格)

dp[i]=max(dp[i−1],prices[i]−min(prices[0:i]))

初始状态:dp[0]=0 ,即首日利润为 0 ;
返回值: dp[n−1] ,其中 n 为 dp 列表长度。

image.png

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        minprice,maxprofit= int(1e9),0  # 10的9次方
        for price in prices:
            minprice = min(price, minprice) # 获取最低价格
            maxprofit = max(price - minprice, maxprofit) #获取最大利润
        return maxprofit

20 股票最大利润II

给定一个数组 prices ,其中 prices[i] 是一支给定股票第 i 天的价格。 设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。 注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:

输入: prices = [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3

示例 2:

输入: prices = [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
     注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。

示例 3:

输入: prices = [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

贪心算法 股票买卖策略:

  • 单独交易日: 设今天价格为P1、明天价格为P2,则今天买入、明天卖出可赚取收益为P2-P1(负值代表亏损)。
  • 连续上涨交易日: 设此上涨交易日股票价格分别为P1、P2...Pn,则第一天买入最后一天卖出收益最大,即为:Pn-P1,等价于每天都买卖,即 Pn-P1=(P2-P1)+(P3-P2)+...+[Pn-P(n-1)]
  • 连续下降交易日: 则不买卖收益最大,即不会亏钱。
class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        profit = 0
        for i in range(1, len(prices)):
            tmp = prices[i] - prices[i-1]
            if tmp > 0: profit += tmp  
        return profit

21 面试题-最长的连续子串

"""
一个正数整数的数组,找出其中最长的连续子串
例如:数组[1,2,4,3,4,5,7]中最长连续子串是[3,4,5]
思路:
1、栈A:A用于存储原始元素
2、栈B:B中存储A中所有 严格升序的元素的子序列。则栈A中的最大元素始终对应栈B中栈顶元素,
   
     借助栈的思想:nums为数据栈A,stack为辅助栈B
    (1)当栈B为空时,将nums[i]写入栈B
    (2)栈B的栈顶元素+1等于nums[i],将nums[i]写入栈B
    (3)不相等时,清空栈B中的元素
    """
class StackNode:
    def maxSubArray(self, nums: list):
        stack, res = [], []
        for i in range(0, len(nums)):
            if not stack or stack[-1] + 1 == nums[i]:
                stack.append(nums[i])
            else:
                res = stack 
                stack = []
        return res
# 方法二:滑动窗口思想
def maxArray(nums: list):
    """滑动窗口"""
    if len(nums) <= 1: return nums
    left, right, win, count = 0, 1, [], 0  # 初始化窗口长度为1,保证left<right,win窗口,count窗口长度
    win.append(nums[left])  # 循环将left的值添加到窗口中
    while right < len(nums):
        if nums[left] + 1 == nums[right]:  # 循环,如果左边界的值+1=右边界的值,窗口中新增右边界的值
            win.append(nums[right])
            left, right = left + 1, right + 1
            if len(win) > count:  # 如果窗口的长度大于count,res等于win,count的值变化为win的窗口长度
                count, res = len(win), win.copy()
        else:  # 如果不等于,窗口置空,将有边界值加入到窗口,此时左边界等于右边界,右边界右移1位
            win = []
            win.append(nums[right])
            left, right = right, right + 1
    return res

22 三数之和(力扣15)

"""
给你一个包含 n 个整数的数组nums,判断nums中是否存在三个元素 a,b,c ,使得a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例 1:
    输入:nums = [-1,0,1,2,-1,-4]
    输出:[[-1,-1,2],[-1,0,1]]
思路:三数之和为0,即a+b+c=0,得c=-(a+b)
设最左边指针为k,双指针i,j分别为数组索引(k,len(nums)两端,双指针交替交替向中间移动。记录对于每个固定指针k的所有满足nums[k]+nums[i]+nums[j]==0的i,j组合
"""
class Solution:
    def threeSum(self, nums: list):
        nums.sort()  # 按照从大到小排序,保证左边指针k为最小
        k, res = 0, []  # 初始化为0,nums[0]
        for k in range(len(nums) - 2):  # 减掉2个元素
            if nums[k] > 0:
                break  # 如果最小的值大于0,则表示三个值都为正数,跳出
            if k > 0 and nums[k] == nums[k - 1]:  # 循环k且去重
                continue
            i, j = k + 1, len(nums) - 1
            while i < j:
                sum = nums[k] + nums[i] + nums[j]
                if sum < 0:
                    i += 1  # 如果三数之和小于0,则i的值递增1
                elif sum > 0:
                    j -= 1  # 如果三数之和大于0,则j的值递减1
                else:
                    res.append([nums[k], nums[i], nums[j]])
                    i, j = i + 1, j - 1
                    while i < j and nums[i] == nums[i - 1]: i += 1
                    while i < j and nums[j] == nums[j + 1]: j -= 1
        return res

23 四数之和(力扣18)


"""
给你一个由 n 个整数组成的数组nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组[nums[a], nums[b], nums[c], nums[d]](若两个四元组元素一一对应,则认为两个四元组重复):
0 <= a, b, c, d< n
a、b、c 和 d 互不相同
nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意顺序 返回答案 。
    输入:nums = [1,0,-1,0,-2,2], target = 0
    输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]
"""

class Solution:
    def fourSum(self, nums: list, target: int):
        nums.sort()
        m, res = 0, []
        for m in range(len(nums) - 3):
            if m > 0 and nums[m] == nums[m - 1]: continue
            for n in range(m + 1, len(nums) - 2):
                if n > m + 1 and nums[n] == nums[n - 1]: continue
                i, j = n + 1, len(nums) - 1
                while i < j:
                    su = nums[m] + nums[n] + nums[i] + nums[j]
                    if su < target:
                        i += 1
                    elif su > target:
                        j -= 1
                    else:
                        res.append([nums[m], nums[n], nums[i], nums[j]])
                        i, j = i + 1, j - 1
                        while i < j and nums[i] == nums[i - 1]: i += 1
                        while i < j and nums[j] == nums[j + 1]: j -= 1
        return res

24 合并2个有序数组(力扣88)

# 使用指针合并两个数组
arr1 = [1, 8, 4, 6, 7]
arr2 = [2, 5, 8, 9, 10]

class Solution:
    def solu1(self, arr1, arr2):
        ind = 0
        ans = arr1.copy()
        for i in range(0, len(arr2)):
            while ind < len(arr1):
                if arr2[i] <= arr1[ind]:  # 范围小于数组的元素下标的最大值
                    ans.insert(ind + i, arr2[i])  # 向第一非数组中插入第二个数组中的数字
                    break  # 跳出当前循环
                else:
                    ind += 1  # ind指向的数字小于i指向的数字,ind向后移动一位
            else:
                ans = ans + arr2[i:]  # ans加上arr2中剩余的元素
                break
        return ans

    def solu2(self, arr1, arr2):
        """循环"""
        an = arr2.copy()
        for i in range(0, len(arr1)):
            for j in range(0, len(arr2)):
                if arr1[i] <= arr2[j]:
                    an.insert(i + j, arr1[i])
                    break
                else:
                    j += 1
        # an.sort(reverse=False)  # 排序sort
        return an

    def merge(self, arr1, arr2):
        """双指针"""
        sorted, p1, p2, m, n = [], 0, 0, len(arr1), len(arr2)
        while p1 < m or p2 < n:
            if p1 == m:
                sorted.append(arr2[p2])
                p2 += 1
            elif p2 == n:
                sorted.append(arr1[p1])
                p1 += 1
            elif arr1[p1] < arr2[p2]:
                sorted.append(arr1[p1])
                p1 += 1
            else:
                sorted.append(arr2[p2])
                p2 += 1
        arr1[:] = sorted
        return arr1

25 根据前序和中序构建二叉树(力扣105)


class Node:
    def __init__(self, data, left, right):
        self.data = data
        self.left = left
        self.right = right

def buildTree(pre_order, mid_order):
    # 忽略参数合法性判断
    if len(pre_order) == 0:
        return None
    # 前序遍历的第一个结点一定是根结点
    root_data = pre_order[0]
    for i in range(0, len(mid_order)):
        if mid_order[i] == root_data:
            break
    # 递归构造左子树和右子树
    left = buildTree(pre_order[1: 1 + i], mid_order[:i])
    right = buildTree(pre_order[1 + i:], mid_order[i + 1:])
    return Node(root_data, left, right)

26 分发糖果(力扣135)


"""
n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。你需要按照以下要求,给这些孩子分发糖果:每个孩子至少分配到 1 个糖果。相邻两个孩子评分更高的孩子会获得更多的糖果。请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目 。
示例1:
    输入:ratings = [1,0,2]
    输出:5
    解释:你可以分别给第一个、第二个、第三个孩子分发 2、1、2 颗糖果。
示例2:
    输入:ratings = [1,2,2]
    输出:4
    解释:你可以分别给第一个、第二个、第三个孩子分发 1、2、1 颗糖果。
         第三个孩子只得到 1 颗糖果,这满足题面中的两个条件。

思路:以中间为准,每个孩子必须比左边的评分高,多分1个,每个孩子必须比右边的评分高,多分1个
"""
class Solution:
    def candy(self, ratings: list) -> int:
        n, ans, num = len(ratings), 0, [1] * len(ratings)
        for i in range(1, n):  # 顺时针循环,不包括第0位元素
            if ratings[i] > ratings[i - 1]:
                num[i] = num[i - 1] + 1
                # ans += num[i]
        for i in range(n - 1, 0, -1):  # 逆时针循环,不包括第0位元素
            if ratings[i - 1] > ratings[i]:
                num[i - 1] = max(num[i - 1], num[i] + 1)
            ans += num[i]
        return ans + num[0]  # 返回结果加上第0位元素

27 爱生气的书店老板(力扣1052)

"""
今天,书店老板有一家店打算试营业customers.length分钟。每分钟都有一些顾客(customers[i])会进入书店,所有这些顾客都会在那一分钟结束后离开。
在某些时候,书店老板会生气。 如果书店老板在第 i 分钟生气,那么 grumpy[i] = 1,否则 grumpy[i] = 0。 当书店老板生气时,那一分钟的顾客就会不满意,不生气则他们是满意的。
书店老板知道一个秘密技巧,能抑制自己的情绪,可以让自己连续X 分钟不生气,但却只能使用一次。请你返回这一天营业下来,最多有多少客户能够感到满意。
示例:
    输入:customers = [1,0,1,2,1,1,7,5], grumpy = [0,1,0,1,0,1,0,1], x = 3
    输出:16
    解释:
    书店老板在最后 3 分钟保持冷静。
    感到满意的最大客户数量 = 1 + 1 + 1 + 1 + 7 + 5 = 16.
"""
class Solution:
    def maxSatisfied(self, customers: list, grumpy: list, x: int):
        ret = 0  # 计数器
        # 遍历获得不生气时的客户数
        for i in range(len(grumpy)):
            if grumpy[i] == 0:
                ret += customers[i]
                customers[i] = 0  # customers = [0,0,0,2,0,1,0,5]
        sumCount = sum(customers[:x])
        res = sumCount
        # 滑动窗口获取最大的客户满意数
        for i in range(x, len(customers)):
            sumCount -= customers[i - x] - customers[i]
            res = max(res, sumCount)
        return (res + ret)

28 可获得的最大点数(力扣1423)

"""
几张卡牌 排成一行,每张卡牌都有一个对应的点数。点数由整数数组 cardPoints 给出。每次行动,你可以从行的开头或者末尾拿一张卡牌,最终你必须正好拿 k 张卡牌。你的点数就是你拿到手中的所有卡牌的点数之和。给你一个整数数组 cardPoints 和整数 k,请你返回可以获得的最大点数。
示例 1:
    输入:cardPoints = [1,2,3,4,5,6,1], k = 3
    输出:12
    解释:第一次行动,不管拿哪张牌,你的点数总是 1 。但是,先拿最右边的卡牌将会最大化你的可获得点数。最优策略是拿右边的三张牌,最终点数为 1 + 6 + 5 = 12 。
"""
class Solution:
    def maxPoints(self, cardPoints: list, k=3):
        n = len(cardPoints)
        # 滑动窗口大小为 n-k
        windowSize = n - k
        # 选前 n-k 个值作为窗口的初始值
        s = sum(cardPoints[:windowSize])
        minSum = s
        for i in range(windowSize, n):
            # 滑动窗口每向右移动一格,增加从右侧进入窗口的元素值,并减少从左侧离开窗口的元素值
            s += cardPoints[i] - cardPoints[i - windowSize]
            minSum = min(minSum, s)
        return sum(cardPoints) - minSum

29 定长子串中元音的最大数目(力扣1456)

"""
给你字符串 s 和整数 k 。请返回字符串 s 中长度为 k 的单个子字符串中可能包含的最大元音字母数。英文中的 元音字母 为(a, e, i, o, u)。
示例 1:
    输入:s = "abciiidef", k = 3
    输出:3
    解释:子字符串 "iii" 包含 3 个元音字母。
"""
class Solution:
    def maxVowels(self, s: str, k: int):
        if not s or len(s) < k: return -1
        vowels, count = {"a", "e", "i", "o", "u"}, 0
        for i in range(k):
            if s[i] in vowels:
                count += 1
        res = count

        for i in range(k, len(s)):
            if s[i - k] in vowels:
                count -= 1  # 去除重复部分的计数
            if s[i] in vowels:
                count += 1
                res = max(count, res)
        return res

30 给定行和列的和,求可行矩阵(力扣1605)


"""
给你两个非负整数数组rowSum 和colSum,其中rowSum[i]是二维矩阵中第 i行元素的和, colSum[j]是第 j列元素的和。换言之你不知道矩阵里的每个元素,但是你知道每一行和每一列的和。请找到大小为rowSum.length x colSum.length的任意 非负整数矩阵,且该矩阵满足rowSum 和colSum的要求。请你返回任意一个满足题目要求的二维矩阵,题目保证存在 至少一个可行矩阵。
示例 1:
    输入:rowSum = [3,8], colSum = [4,7]
    输出:[[3,0],
          [1,7]]
    解释:
    第 0 行:3 + 0 = 3 == rowSum[0]
    第 1 行:1 + 7 = 8 == rowSum[1]
    第 0 列:3 + 1 = 4 == colSum[0]
    第 1 列:0 + 7 = 7 == colSum[1]
    行和列的和都满足题目要求,且所有矩阵元素都是非负的。
    另一个可行的矩阵为:[[1,2],
                      [3,5]]
"""
class Solution:
    def restoreMatrix(self, rowSum: list, colSum: list):
        row, col = len(rowSum), len(colSum)
        matrix = [[0] * col for _ in range(row)]  # matrix=[行][列]
        for i in range(row):
            for j in range(col):
                min_num = min(rowSum[i], colSum[j])  # 获取最小值
                matrix[i][j] = min_num
                rowSum[i] -= min_num  # 获取每行最大的值
                colSum[j] -= min_num  # 获取每列最大的值,递减
                if rowSum[i] == 0:
                    break
        return matrix

31 卡车上的最大单元数(力扣1710)

"""
请你将一些箱子装在 一辆卡车 上。给你一个二维数组 boxTypes ,其中 boxTypes[i] = [numberOfBoxesi, numberOfUnitsPerBoxi] :
numberOfBoxesi 是类型 i 的箱子的数量。
numberOfUnitsPerBoxi 是类型 i每个箱子可以装载的单元数量。
整数 truckSize 表示卡车上可以装载 箱子 的 最大数量 。只要箱子数量不超过 truckSize ,你就可以选择任意箱子装到卡车上。
返回卡车可以装载单元 的 最大 总数。

示例 1:
    输入:boxTypes = [[1,3],[2,2],[3,1]], truckSize = 4
    输出:8
    解释:箱子的情况如下:
    - 1 个第一类的箱子,每个里面含 3 个单元。
    - 2 个第二类的箱子,每个里面含 2 个单元。
    - 3 个第三类的箱子,每个里面含 1 个单元。
    可以选择第一类和第二类的所有箱子,以及第三类的一个箱子。
    单元总数 = (1 * 3) + (2 * 2) + (1 * 1) = 8
示例 2:
    输入:boxTypes = [[5,10],[2,5],[4,7],[3,9]], truckSize = 10
    输出:91
    解释:
    - 5个第一类箱子,每个里面含有10个单元
    - 2个第二类箱子,每个里面含有5个单元
    - 4个第三类箱子,每个里面含有7个单元
    - 3个第四类箱子,每个里面含有9个单元
    (5*10)+(3*9)+((10-5-3)*7)=14+27+50=91
"""
class Solution:
    def maxUnits(self, boxTypes: list, truckSize: int):
        boxTypes.sort(key=lambda x: x[1], reverse=True)  # 按照二维数组的第二个元素进行排序,倒序
        mas = 0
        for i in range(len(boxTypes)):
            if boxTypes[i][0] <= truckSize:
                truckSize = truckSize - boxTypes[i][0]
                mas += boxTypes[i][0] * boxTypes[i][1]
            elif boxTypes[i][0] > truckSize and truckSize > 0:
                mas += truckSize * boxTypes[i][1]
                truckSize = 0
        return mas

32 最大公约数(力扣1979)

"""
给你一个整数数组 nums ,返回数组中最大数和最小数的 最大公约数 。两个数的最大公约数是能够被两个数整除的最大正整数。
示例 1:
输入:nums = [2,6,5,9,10]
输出:2
解释:
nums 中最小的数是 2
nums 中最大的数是 10
2 和 10 的最大公约数是 2
"""
import math
class Solution:
    def findGCD(self, nums: list):
        # 先找到最大数、最小数,再求最大公约数
        nums.sort()
        return math.gcd(nums[0], nums[-1])

    def findGCD2(self, nums: list):
        # 先找到最大数、最小数,再求最大公约数
        ma, mi = max(nums), min(nums)
        return math.gcd(ma, mi)

33 反转字符串(力扣344)

"""
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
示例 1:
    输入:s = ["h","e","l","l","o"]
    输出:["o","l","l","e","h"]
"""
class Solution:
    def reverseString(self, s: list):
        res = []
        for i in range(len(s)):
            res.insert(0, s[i])
        return res

    def reverseString1(self, s: list):
        left, right = 0, len(s) - 1
        while left < right:
            s[left], s[right] = s[right], s[left]
            left += 1
            right -= 1
        while left >= right:
            return s

34 删除链表的节点(剑指offer18)

"""
给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。返回删除后的链表的头节点。
输入: head = [4,5,1,9], val = 5
输出: [4,1,9]
解释: 给定你链表中值为5的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.

1、初始化:双指针,cur和tmp
2、循环逻辑:当head.val=val,执行移除操作,即改变指向
3、返回值:修改后的head.next
"""
class ListNode:
    def __init__(self, val):
        self.val = val
        self.next = None

class Solution:
    def deleteNode(self, head: ListNode, val: int) -> ListNode:
        if head.val == val: return head.next  # 如果相等,返回下一个值
        tmp, cur = head, head.next  # 双指针,tmp前驱节点,cur当前节点,cur.next后继节点
        while cur and cur.val != val:  # 循环,如果cur为真且cur的值不等于
            tmp, cur = cur, cur.next
        if cur:
            tmp.next = cur.next
        return head

35 调整数组顺序使奇数和偶数分开


"""
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数在数组的前半部分,所有偶数在数组的后半部分。
输入:nums = [1,2,3,4]
输出:[1,3,2,4]
注:[3,1,2,4] 也是正确的答案之一。
"""

"""
算法流程:
1、初始化:i、j为双指针,分别位于数组的左右两端
2、循环交换:当i=j跳出循环
    (1)指针i遇到奇数则执行i=i+1,直到找到偶数  (i为最大值,执行递增)
    (2)指针j遇到偶数则执行j=j-1,直到找到奇数  (j为最大值,执行递减)
    (3)交换nums[i]和nums[j]的值
3、返回值:修改后的nums
"""


class Solution:
    def exchange(self, nums: list):
        i, j = 0, len(nums) - 1
        while i < j:
            if i < j and nums[i] % 2 == 1: i += 1
            if i < j and nums[j] % 2 == 0: j -= 1
            nums[i], nums[j] = nums[j], nums[i]
        return nums

36 和为s的两个数字

"""
输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,则输出任意一对即可。
示例 1:
    输入:nums = [2,7,11,15], target = 9
    输出:[2,7] 或者 [7,2]
示例 2:
    输入:nums = [10,26,30,31,47,60], target = 40
    输出:[10,30] 或者 [30,10]
"""

"""
1、初始化:双指针i和j分别指向数组nums的左右两端(对撞双指针)
2、循环:
    (1)如果nums[i]+nums[j]值相等target,返回对应下标的值,
    (2)如果两数之和大于target,则表示j需要递减,即j-=1
    (3)如果两数之和小于target,则表示i需要递增,即i+=1
3、返回值:[nums[i],nums[j]]
"""


class Solution:
    def sumArray(self, nums: list, target: int):
        i, j = 0, len(nums) - 1
        while i < j:
            su = nums[i] + nums[j]
            if i < j and su == target:
                return [nums[i], nums[j]]
            elif i < j and su > target:
                j = j - 1
            else:
                i = i + 1
        return False  # 如果没有返回False

37 和为s的连续正数序列

"""
输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。
示例 1:
    输入:target = 9
    [1,2,3,4,5,6,7,8,9]
    输出:[[2,3,4],[4,5]]
示例 2:
    输入:target = 15
    输出:[[1,2,3,4,5],[4,5,6],[7,8]]
"""
"""
1、初始化:设置左边界i和右边界j,构建滑动窗口从左向右滑动
2、循环:每轮判断滑动窗口内元素和目标值大小关系对比
    (1)若相等,则记录,并向右移动左边界i=i+1
    (2)若大于,则移动左边界i(减少窗口内的元素和) i=i+1,更新sum元素和
    (3)若小于,则移动右边界j(增大窗口内的元素和) j=j+1,更新sum元素和
3、返回值:返回窗口win中记录的值
"""


class Solution:
    def findCount(self, target: int):
        """滑动窗口"""
        l, r, res, win = 1, 2, 3, []  # 初始值从1和2开始,元素和为3
        while l < r:
            if res == target:
                win.append(list(range(l, r + 1)))
            if res >= target:
                res = res - l
                l = l + 1
            else:
                r = r + 1
                res = res + r
        return win

38 翻转单词顺序

"""
输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。为简单起见,标点符号和普通字母一样处理。例如输入字符串"I am a student. ",则输出"student. a am I"。
示例 2:
    输入: " hello world! "
    输出:"world! hello"
    解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
"""

"""
0、特殊处理:
    (1)标点符号翻转,
    (2)空格需要处理,返回值不包含空格
1、初始化:
2、循环:
3、返回值:
"""


class Solution:
    def reverseWords(self, s: str):
        s = s.strip()  # 删除空格
        i = j = len(s) - 1
        res = []
        while i >= 0:
            while i >= 0 and s[i] != " ": i -= 1
            res.append(s[i + 1:j + 1])
            while i >= 0 and s[i] == " ": i -= 1
            j = i
        return ' '.join(res)

    def reverse(self, s: str):
        s = s.strip()
        se = s.split()  # 分割
        se.reverse()
        return ' '.join(se)

39 滑动窗口最大值(剑指offer59)

"""
给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。
示例:
输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7]
解释:
      滑动窗口的位置                最大值
    ---------------               -----
                                   队首 队尾
    [1  3  -1] -3  5  3  6  7       3  -1
     1 [3  -1  -3] 5  3  6  7       3  -1  -3
     1  3 [-1  -3  5] 3  6  7       5
     1  3  -1 [-3  5  3] 6  7       5   3
     1  3  -1  -3 [5  3  6] 7       6
     1  3  -1  -3  5 [3  6  7]      7
"""
import collections
import heapq

class Solution(object):
    def maxSlidingWindow(self, nums, k):
        if not nums or k == 0: return []  # 如果nums为空或者k为0,返回空列表
        deque = collections.deque()
        # 如果k的值存在,当窗口下标小于k
        for i in range(k):
            while deque and deque[-1] < nums[i]:  # 如果队列存在,队尾值小于当前值,删除队列中队尾值
                deque.pop()  # 删掉队尾值  [3,-1]
            deque.append(nums[i])  # 在队尾加入当前值nums[i]
        res = [deque[0]]  # 确保队列中队首的值为最大值
        # 如果k的值存在,当窗口下标大于等于k
        for i in range(k, len(nums)):  # 循环剩余部分
            if deque[0] == nums[i - k]:  # 如果队首的值等于循环体剩余部分值,删除队首的值
                deque.popleft()
            while deque and deque[-1] < nums[i]:  # 如果队列存在,队尾值小于当前值,删除队列中队尾值
                deque.pop()
            deque.append(nums[i])  # 在队尾加入当前值nums[i]
            res.append(deque[0])
        return res

    def maxSlidingWindow2(self, nums, k: int):
        n = len(nums)
        # 注意 Python 默认的优先队列是小根堆
        q = [(-nums[i], i) for i in range(k)]
        heapq.heapify(q)

        ans = [-q[0][0]]
        for i in range(k, n):
            heapq.heappush(q, (-nums[i], i))
            while q[0][1] <= i - k:
                heapq.heappop(q)
            ans.append(-q[0][0])
        return ans

40 二分插入排序(剑指offer68)

"""
给定一个排序的整数数组 nums和一个整数目标值 target ,请在数组中找到target,并返回其下标。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
输入: nums = [1,3,5,6], target = 5
输出: 2
"""


class Solution:
    def binarySearch(self, nums, target):
        """根据原始数据的位置进行替换"""
        left, right = 0, len(nums) - 1
        while left <= right:
            mid = (left + right) // 2
            if nums[mid] == target:
                return "mid值与target相同,mid为:", mid
            elif nums[mid] < target:
                left = mid + 1
            else:
                right = mid - 1
        nums.insert(left, target)
        return nums[target]

41 排序数组中只出现一次的数字(剑指offer70)

"""
给定一个只包含整数的有序数组 nums ,每个元素都会出现两次,唯有一个数只会出现一次,请找出这个唯一的数字。
输入: nums = [1,1,2,3,3,4,4,8,8]
输出: 2

输入: nums =  [3,3,7,7,10,11,11]
输出: 10
"""
import math


class Solution:
    def singOne(self, nums: list):
        left, right = 0, math.ceil((len(nums)) / 2)
        while left <= right:
            mid = (left + right) // 2
            i = mid * 2
            if i < len(nums) - 1 and nums[i] != nums[i + 1]:  # 循环,如果不相等
                if mid == 0 or nums[i - 2] == nums[i - 1]:  # 如果相等,返回值
                    return nums[i]
                left = mid + 1
            else:
                right = mid - 1
        return nums(len(nums) - 1)  # 如果没有返回False或者-1

42 最长连续序列(剑指offer119)

"""
给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
输入:nums = [100,4,200,1,3,2]
示例1:
    输出:4
    解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。
示例2:
    输入:nums = [0,3,7,2,5,8,4,6,0,1]
    输出:9
"""

class Solution:
    def maxLength(self, nums: list):
        nums.sort()
        res = ret = 1  # 初始化值为1
        for i in range(1, len(nums)):  # 循环,从第1位元素开始
            if nums[i] == nums[i - 1]:  # 如果相等,直接继续
                continue
            if nums[i] - nums[i - 1] == 1:
                ret += 1
                res = max(res, ret)
            else:
                ret = 1
        return res

43 子数组最大平均数1()

"""
给你一个由 n 个元素组成的整数数组 nums 和一个整数 k 。请你找出平均数最大且 长度为 k 的连续子数组,并输出该最大平均数。任何误差小于 10-5 的答案都将被视为正确答案。
示例 1:
    输入:nums = [1,12,-5,-6,50,3], k = 4
    输出:12.75
    解释:最大平均数 (12-5-6+50)/4 = 51/4 = 12.75


1:[1,12,-5,-6]
2:  [12,-5,-6,50]
3:      [-5,-6, 50, 3]
循环窗口:往右递增,窗口中元素和=sum-左边界值+右边界值
sum(nums[left:right])/k  窗口中的元素和,再除以K,

示例 2:
    输入:nums = [5], k = 1
    输出:5.00000
"""

class Solution:
    def findMaxAverage(self, nums: list, k: int):
        left, right, winsum, res = 0, k - 1, 0, 0
        for i in range(k):  # 未形成窗口,循环出第一个窗口   [1,12,-5,-6]
            winsum += nums[i]
            res = winsum
        for right in range(k, len(nums)):  # 从第一个窗口循环遍历出所有窗口
            winsum = winsum + nums[right] - nums[left]
            res = max(res, winsum)
            left, right = left + 1, right + 1
        return (res / k)

44 盛最多水的容器

"""
给你 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点(i,ai) 。在坐标内画 n 条垂直线,垂直线 i的两个端点分别为(i,ai) 和 (i, 0) 。找出其中的两条线,使得它们与x轴共同构成的容器可以容纳最多的水。
说明:你不能倾斜容器。
输入:[1,8,6,2,5,4,8,3,7]
输出:49
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为49。
"""

"""
思路:
横坐标x:nums中选择的某段长度
纵坐标y:nums中最大的两个数中最小的值
"""

class Solution:
    def maxArea(self, height: list) -> int:
        left, right = 0, len(height) - 1  # 定义纵坐标的值  nums[left],nums[right]取最大值,*(right-left)
        res = 0
        while left < right:
            x = min(height[left], height[right])  # 横坐标
            y = (right - left)  # 纵坐标
            res = max(res, x * y)
            if x == height[left]:  # 如果最小值为左边界,左边界右移1位
                left += 1
            else:  # 如果最小值为右边界,右边界左移1位
                right -= 1
        return res

45 两地调度

"""
公司计划面试 2n 人。给你一个数组 costs ,其中 costs[i] = [aCosti, bCosti] 。第 i 人飞往 a 市的费用为 aCosti ,飞往 b 市的费用为 bCosti 。返回将每个人都飞到 a 、b 中某座城市的最低费用,要求每个城市都有 n 人抵达。
示例 1:
    输入:costs = [[10,20],[30,200],[400,50],[30,20]]
    输出:110
    解释:
    第一个人去 a 市,费用为 10。
    第二个人去 a 市,费用为 30。
    第三个人去 b 市,费用为 50。
    第四个人去 b 市,费用为 20。
    最低总费用为 10 + 30 + 50 + 20 = 110,每个城市都有一半的人在面试。
"""

class Solution:
    def twoCity(self, costs: list):
        # costs.sort(key=lambda x: x[1], reverse=False)  # [[10, 20], [30, 20], [400, 50], [30, 200]]
        cost, nums, n = 0, list(), len(costs)
        for i in range(n):
            nums.append([costs[i][1] - costs[i][0], i])  # 将b城的费用减掉a城的费用,得到结果[[10, 0], [170, 1], [-350, 2], [-10, 3]]
        nums.sort(key=lambda x: x[0], reverse=False)  # [[-350, 2], [-10, 3], [10, 0], [170, 1]]
        for j in range(n):
            if j < n // 2:
                cost += costs[nums[j][1]][1]  # costs[2][1]  获得50
            else:
                cost += costs[nums[j][1]][0]
        return cost

46 数组变换

"""
首先,给你一个初始数组 arr。然后,每天你都要根据前一天的数组生成一个新的数组。
第i天所生成的数组,是由你对第i-1天的数组进行如下操作所得的:
假如一个元素小于它的左右邻居,那么该元素自增 1。
假如一个元素大于它的左右邻居,那么该元素自减 1。
首、尾元素 永不改变。
过些时日,你会发现数组将会不再发生变化,请返回最终所得到的数组。
输入:[1,6,3,4,3,5]
输出:[1,4,4,4,4,5]
解释:
第一天,数组从 [1,6,3,4,3,5] 变为 [1,5,4,3,4,5]。
第二天,数组从 [1,5,4,3,4,5] 变为 [1,4,4,4,4,5]。
无法再对该数组进行更多操作。
"""

class Solution:
    def nums(self, arr: list):
        while True:
            changed = False
            ans = arr[:]
            for i in range(1, len(ans) - 1):
                if ans[i] < ans[i - 1] and ans[i] < ans[i + 1]:
                    arr[i] += 1
                    changed = True
                elif ans[i] > ans[i - 1] and ans[i] > ans[i + 1]:
                    arr[i] -= 1
                    changed = True
            if changed == False:
                break
        return arr

47 给定行和列的和求可行矩阵

"""
给你两个非负整数数组rowSum 和colSum,其中rowSum[i]是二维矩阵中第 i行元素的和, colSum[j]是第 j列元素的和。换言之你不知道矩阵里的每个元素,但是你知道每一行和每一列的和。请找到大小为rowSum.length x colSum.length的任意 非负整数矩阵,且该矩阵满足rowSum 和colSum的要求。请你返回任意一个满足题目要求的二维矩阵,题目保证存在 至少一个可行矩阵。
示例 1:
    输入:rowSum = [3,8], colSum = [4,7]
    输出:[[3,0],
          [1,7]]
    解释:
    第 0 行:3 + 0 = 3 == rowSum[0]
    第 1 行:1 + 7 = 8 == rowSum[1]
    第 0 列:3 + 1 = 4 == colSum[0]
    第 1 列:0 + 7 = 7 == colSum[1]
    行和列的和都满足题目要求,且所有矩阵元素都是非负的。
    另一个可行的矩阵为:[[1,2],
                      [3,5]]
"""

class Solution:
    def restoreMatrix(self, rowSum: list, colSum: list):
        row, col = len(rowSum), len(colSum)
        matrix = [[0] * col for _ in range(row)]  # matrix=[行][列]
        for i in range(row):
            for j in range(col):
                min_num = min(rowSum[i], colSum[j])  # 获取最小值
                matrix[i][j] = min_num
                rowSum[i] -= min_num  # 获取每行最大的值
                colSum[j] -= min_num  # 获取每列最大的值,递减
                if rowSum[i] == 0:
                    break
        return matrix