为了能够实现进大厂被资本家无情压迫的梦想,坚持每天一道题。🤑

先用 Python 写,学了 C++ 之后再刷一遍。😇

这里是我的代码仓库

不保证缩进正确,复制粘贴时候小心一点👀

主要是给我自己看的,所以解题思路我就不写了,我相信我能看懂自己的代码。😝

简单记一下做题感受。😶

✔️

1–两数之和

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

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

你可以按任意顺序返回答案。

示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

示例 2:

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

示例 3:

输入:nums = [3,3], target = 6
输出:[0,1]

提示:

2 <= nums.length <= 103
-109 <= nums[i] <= 109
-109 <= target <= 109
只会存在一个有效答案

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        '''
        就挺暴力的
        '''
        for i in range(len(nums)):
            for j in range(i+1,len(nums)):
                if target == nums[i] + nums[j]:
                    return [i,j]
        return []
    
    def twoSum_2(self, nums: List[int],target: int) -> List[int]:
        '''
        空间换时间
        '''
    	hashmap = {}
    	for i, num in enumerate(nums):
    		if target - num in hashmap:
    			return [hashmap[target - num] , i]
    		hashmap[num] = i

    	return []

两层循环是最好想的,第二种方法也不是很难,只是我还不习惯。
嗯… 应该努力让自己习惯。

2–两数相加

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

示例 1:

输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.

示例 2:

输入:l1 = [0], l2 = [0]
输出:[0]

示例 3:

输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
输出:[8,9,9,9,0,0,0,1]

提示:

每个链表中的节点数在范围 [1, 100] 内
0 <= Node.val <= 9
题目数据保证列表表示的数字不含前导零

class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next
class Solution:
    def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
    	'''
    	提交成功  时空性能较差
    	'''
        jinwei = 0
        head = ListNode(0)
        p = head
        head.next = p
        if l1 == None or l2 == None:
            return l1 or l2
        while l1 and l2:
            tmp = l1.val + l2.val + jinwei
            val = tmp % 10
            q = ListNode(val)
            p.next = q
            p = q
            jinwei = tmp // 10
            l1 = l1.next
            l2 = l2.next
        if l1 == None:l1 = l2
        while l1:
            tmp = l1.val +jinwei
            val = tmp%10
            q = ListNode(val)
            p.next = q
            p = q
            jinwei = tmp // 10
            l1 = l1.next
        if jinwei:
            q = ListNode(jinwei)
            p.next = q
            p = q
        p.next = None
        return head.next

和考研的数据结构题挺像的,我也是那个思想解的,肯定打不过他们的代码。

3–无重复最长子串

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

示例 1:

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

示例 2:

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

示例 3:

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

示例 4:

输入: s = “”
输出: 0

提示:

0 <= s.length <= 5 * 104
s 由英文字母、数字、符号和空格组成

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
    	'''
    	背的答案  效果一般
    	'''
        n = len(s)
        if n < 2 :return n
        tmp = set(s[0])
        res = 0
        r = 0
        for i in range(n):
            while r + 1 < n and s[r+1] not in tmp:
                tmp.add(s[r+1])
                r = r + 1
            res = max(res,r+1 - i)
            tmp.remove(s[i])

        return res

这个题乍一看还挺简单的,但是我思考了好久,尝试了好多次都没做出来。
最后还是选择看了答案。
希望下次见到能做出来吧。

7–整数反转

给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。

注意:

假设我们的环境只能存储得下 32 位的有符号整数,则其数值范围为 [−231, 231 − 1]。请根据这个假设,如果反转后整数溢出那么就返回 0。

示例 1:

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

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

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

输入:x = 0
输出:0

提示:

-231 <= x <= 231 - 1

class Solution:
    def reverse(self, x: int) -> int:
    	'''
    	做一道简单的放松一下
    	'''

        tag = 0
        if x < 0 :
            x = -x
            tag = 1

        tmp = []
        while x != 0:
            tmp.append(x % 10)
            x = x // 10
        n = len(tmp)
        for i in range(n):
            x = x + tmp[i]* (10 ** (n-i-1))
        if x > 2 ** 31 - 1:
            return 0
        if tag == 1:
            x = -x
        return x

这个题挺简单的,但是注意审题!第一次提交的时候忘记了判断反转之后是否越界。

~~我做题就是眼瞎,考研时候就是。~~😞

86–分隔链表

给你一个链表和一个特定值 x ,请你对链表进行分隔,使得所有小于 x 的节点都出现在大于或等于 x 的节点之前。

你应当保留两个分区中每个节点的初始相对位置。

示例:

输入:head = 1->4->3->2->5->2, x = 3
输出:1->2->2->4->3->5

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

class Solution:
    def partition(self, head: ListNode, x: int) -> ListNode:
        if head == None: return None
        xiaoyu = ListNode(head.val)
        p = xiaoyu
        dayu = ListNode(head.val)
        q = dayu
        h = head
        while h:
            if h.val < x:
                p.next = h
                p = p.next
            else:
                q.next = h
                q = q.next
            h = h.next
        p.next = dayu.next
        q.next = None
        return xiaoyu.next

嗯… 这题好像没啥说的,就… 挺简单的。

189–移动数组

给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。

示例 1:

输入: [1,2,3,4,5,6,7] 和 k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右旋转 1 步: [7,1,2,3,4,5,6]
向右旋转 2 步: [6,7,1,2,3,4,5]
向右旋转 3 步: [5,6,7,1,2,3,4]

示例 2:

输入: [-1,-100,3,99] 和 k = 2
输出: [3,99,-1,-100]
解释:
向右旋转 1 步: [99,-1,-100,3]
向右旋转 2 步: [3,99,-1,-100]

说明:

尽可能想出更多的解决方案,至少有三种不同的方法可以解决这个问题。
要求使用空间复杂度为 O(1) 的 原地 算法。

class Solution:
    def rotate(self, nums: List[int], k: int) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        n = len(nums)
        k = k % n
        temp = nums[n-k:]
        for i in range(n-k):
            nums[n-i-1] = nums[n-i-k-1]
        nums[0:k] = temp

    def rotate_2(self, nums: List[int], k: int) -> None:
    	'''
    	在数据结构的书上见过这种题:大概是三次 逆置 操作
    	'''
        n = len(nums)
        k %= n
        nums[:] = nums[::-1]
        nums[:k] = nums[:k][::-1]
        nums[k:] = nums[k:][::-1]

这题我就感觉在王道那个书上见过,三次逆置操作。

但是还是没有那么写,选了一个比较直观的思路。

我就是这么直,要改改了。😅

228–汇总区间

给定一个无重复元素的有序整数数组 nums 。

返回 恰好覆盖数组中所有数字 的 最小有序 区间范围列表。也就是说,nums 的每个元素都恰好被某个区间范围所覆盖,并且不存在属于某个范围但不属于 nums 的数字 x 。

列表中的每个区间范围 [a,b] 应该按如下格式输出:

“a->b” ,如果 a != b
“a” ,如果 a == b

示例 1:

输入:nums = [0,1,2,4,5,7]
输出:[“0->2”,“4->5”,“7”]
解释:区间范围是:
[0,2] --> “0->2”
[4,5] --> “4->5”
[7,7] --> “7”

示例 2:

输入:nums = [0,2,3,4,6,8,9]
输出:[“0”,“2->4”,“6”,“8->9”]
解释:区间范围是:
[0,0] --> “0”
[2,4] --> “2->4”
[6,6] --> “6”
[8,9] --> “8->9”

示例 3:

输入:nums = []
输出:[]

示例 4:

输入:nums = [-1]
输出:["-1"]

示例 5:

输入:nums = [0]
输出:[“0”]

提示:

0 <= nums.length <= 20
-231 <= nums[i] <= 231 - 1
nums 中的所有值都 互不相同
nums 按升序排列

class Solution:
    def summaryRanges(self, nums: List[int]) -> List[str]:
        result = []
        n = len(nums)
        if n == 0:return result
        if n == 1:return ["%s"%nums[0]]

        k = nums[0]
        for i in range(1,n):
            if nums[i] > nums[i-1]+1 :
                if nums[i-1] - k >0:
                    result.append("%s->%s"%(k,nums[i-1]))
                else:
                    result.append("%s"%k)
                k = nums[i]
    
        if nums[-1] - k >0:
            result.append("%s->%s"%(k,nums[-1]))
        else:
            result.append("%s"%nums[-1])
        
        return result

这题好像反复调了好几次,这种题就是思路是挺简单的,但是想一次把代码写对,还是挺难的。

435–无重叠区间

给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。
注意: 可以认为区间的终点总是大于它的起点。
区间 [1,2] 和 [2,3] 的边界相互“接触”,但没有相互重叠。

示例 1:
输入: [ [1,2], [2,3], [3,4], [1,3] ]
输出: 1
解释: 移除 [1,3] 后,剩下的区间没有重叠。

示例 2: 输入: [ [1,2], [1,2], [1,2] ]
输出: 2
解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。

示例 3:
输入: [ [1,2], [2,3] ]
输出: 0
解释: 你不需要移除任何区间,因为它们已经是无重叠的了。

class Solution:     
	def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:         
		num = 0         
		thr = float("-inf")        
		while intervals:            
		    min_index = 0            
		    for i in range(1,len(intervals)):                 
		        if intervals[i][1] < intervals[min_index][1]:                 
		            min_index = i            
		    tmp = intervals.pop(min_index)            
		    if tmp[0] < thr:              
		        num += 1             
		    else:          
		        thr = tmp[1]         
		return num

这题和算法课上的一个例子很像。

用贪心策略,优先找先结束的。在这里就是 每一对数中第二个数比较小的一定在最优解中,与之矛盾的区间弹出,再找下一个这样的数对。

509–斐波那契数

斐波那契数,通常用 F(n) 表示,形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:

F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1
给你 n ,请计算 F(n) 。

示例 1:

输入:2
输出:1
解释:F(2) = F(1) + F(0) = 1 + 0 = 1

示例 2:

输入:3
输出:2
解释:F(3) = F(2) + F(1) = 1 + 1 = 2

示例 3:

输入:4
输出:3
解释:F(4) = F(3) + F(2) = 2 + 1 = 3

提示:

0 <= n <= 30

class Solution:
    def fib(self, n: int) -> int:
    	if n < 2:
    		return n

    	p , q , r = 0 , 0 , 1
    	for i in range(2,n+1):
    		p , q = q , r
    		r = p + q

    	return r

哎,怎么说呢,下次遇到这种题可别用递归了,像是大一新生。

547–省份数量

有 n 个城市,其中一些彼此相连,另一些没有相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c 直接相连,那么城市 a 与城市 c 间接相连。

省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。

给你一个 n x n 的矩阵 isConnected ,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,而 isConnected[i][j] = 0 表示二者不直接相连。

返回矩阵中 省份 的数量。

示例 1:
输入:isConnected = [[1,1,0],[1,1,0],[0,0,1]]
输出:2

示例 2:
输入:isConnected = [[1,0,0],[0,1,0],[0,0,1]]
输出:3

提示:

1 <= n <= 200
n == isConnected.length
n == isConnected[i].length
isConnected[i][j] 为 1 或 0
isConnected[i][i] == 1
isConnected[i][j] == isConnected[j][i]

class Solution:
    def findCircleNum(self, isConnected: List[List[int]]) -> int:
        n = len(isConnected)
        label = [0] * n 
        queue = []
        result = 0
        if isConnected:
            for i in range(n):
                if label[i] == 0:
                    queue.append(i)
                    result += 1
                    while queue:
                        tmp = queue.pop(0)
                        for j in range(n):
                            if isConnected[tmp][j] == 1 and label[j] == 0:
                                label[j] = 1
                                queue.append(j)
        

        return result

能自主做上一道图论的题,太不容易了。😪

虽然只是个广度优先遍历而已。

605–种花问题

假设你有一个很长的花坛,一部分地块种植了花,另一部分却没有。可是,花卉不能种植在相邻的地块上,它们会争夺水源,两者都会死去。

给定一个花坛(表示为一个数组包含0和1,其中0表示没种植花,1表示种植了花),和一个数 n 。能否在不打破种植规则的情况下种入 n 朵花?能则返回True,不能则返回False。

示例 1:

输入: flowerbed = [1,0,0,0,1], n = 1
输出: True

示例 2:

输入: flowerbed = [1,0,0,0,1], n = 2
输出: False

注意:

数组内已种好的花不会违反种植规则。
输入的数组长度范围为 [1, 20000]。
n 是非负整数,且不会超过输入数组的大小。

class Solution:
    def canPlaceFlowers(self, flowerbed: List[int], n: int) -> bool:
        if n == 0: return True
        if len(flowerbed) < 2 * n - 1:
            return False
        if len(flowerbed) > 2:
            if flowerbed[0] == 0 and flowerbed[1] == 0:
                flowerbed[0] = 1
                n -= 1
            for i in range(1,len(flowerbed) - 1):
                if flowerbed[i-1] == 0 and flowerbed[i] == 0 and flowerbed[i+1] == 0:
                    flowerbed[i] = 1
                    n -= 1
            if flowerbed[-1] == 0 and flowerbed[-2] == 0:
                flowerbed[-1] = 1
                n -= 1
            return n <= 0
        else:
            return True if (n == 1 and max(flowerbed) == 0) else False

这题好像也根据不过的测试用例反复调了几次。

也是贪心策略吧,题解懒得看,大体上差不多。

617–合并二叉树

给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。

你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。

示例 1:

输入:
Tree 1 Tree 2
1 2
/ \ / \
3 2 1 3
/ \ \
5 4 7
输出:
合并后的树:
3
/
4 5
/ \ \
5 4 7
注意: 合并必须从两个树的根节点开始。

# Definition for a binary tree node.
class TreeNode:
    def __init__(self, val, left = None,right = None):
        self.val = val
        self.left = left
        self.right = right

class Solution:
    def mergeTrees(self, t1: TreeNode, t2: TreeNode) -> TreeNode:
    	if not t1:
    		return t2
    	if not t2:
    		return t1

    	merged = TreeNode(t1.val + t2.val)
    	merged.left = self.mergeTrees(t1.left,t2.left)
    	merged.right = self.mergeTrees(t1.right.t2.right)

    	return merged

这个好像是前几个遇到的题,不算难。但是我好像还是参考答案了。

830–寻找字符串中较大分组

在一个由小写字母构成的字符串 s 中,包含由一些连续的相同字符所构成的分组。

例如,在字符串 s = “abbxxxxzyy” 中,就含有 “a”, “bb”, “xxxx”, “z” 和 “yy” 这样的一些分组。

分组可以用区间 [start, end] 表示,其中 start 和 end 分别表示该分组的起始和终止位置的下标。上例中的 “xxxx” 分组用区间表示为 [3,6] 。

我们称所有包含大于或等于三个连续字符的分组为 较大分组 。

找到每一个 较大分组 的区间,按起始位置下标递增顺序排序后,返回结果。

示例 1:

输入:s = “abbxxxxzzy”
输出:[[3,6]]
解释:“xxxx” 是一个起始于 3 且终止于 6 的较大分组。

示例 2:

输入:s = “abc”
输出:[]
解释:“a”,“b” 和 “c” 均不是符合要求的较大分组。

示例 3:

输入:s = “abcdddeeeeaabbbcd”
输出:[[3,5],[6,9],[12,14]]
解释:较大分组为 “ddd”, “eeee” 和 “bbb”

示例 4:

输入:s = “aba”
输出:[]

class Solution:
    def largeGroupPositions(self, s: str) -> List[List[int]]:
        result = []
        if len(s) < 3:
            return result
        i = 0
        while i < len(s) - 2:
            j = i + 1
            while j < len(s):
                if s[j] != s[i]:
                    break
                j += 1
            if j - i > 2:
                result.append([i,j - 1])
                i = j
            else:
                i += 1        
        return result

大概就是遍历试,记录下标。

1018–可被 5 整除的二进制前缀

给定由若干 0 和 1 组成的数组 A。我们定义 N_i:从 A[0] 到 A[i] 的第 i 个子数组被解释为一个二进制数(从最高有效位到最低有效位)。

返回布尔值列表 answer,只有当 N_i 可以被 5 整除时,答案 answer[i] 为 true,否则为 false。

示例 1:

输入:[0,1,1]
输出:[true,false,false]
解释:
输入数字为 0, 01, 011;也就是十进制中的 0, 1, 3 。只有第一个数可以被 5 整除,因此 answer[0] 为真。
示例 2:

输入:[1,1,1]
输出:[false,false,false]
示例 3:

输入:[0,1,1,1,1,1]
输出:[true,false,false,false,true,false]
示例 4:

输入:[1,1,1,0,1]
输出:[false,false,false,false,false]

提示:

1 <= A.length <= 30000
A[i] 为 0 或 1

class Solution:
    def prefixesDivBy5(self, A: List[int]) -> List[bool]:
    	'''
    	一开始暴力解题  超时了
    	后来看了答案  发现每次增加一个数只是上一个数的左移加当前数值 也就是2倍加当前数值  快了一些
    	再后来发现 被 5 整除只需要记录个位数就行了 也就是除 10 取余数
    	但是,对于个位数 6 ,6 = 1 + 5, 6 * 2 = (1 + 5)* 2,6 * 2 % 10 = (1 + 5) * 2 % 10 = 2 
    	因此每次记录除以 5 的余数即可。 
    	'''
        n = len(A)
        ans = [False] * n
        tmp = 0
        for i in range(n):
            tmp = (tmp * 2 + A[i]) % 5
            if tmp == 0:
                ans[i] = True
        return ans 

一开始暴力解题 超时了 🤕
后来看了答案 发现每次增加一个数只是上一个数的左移加当前数值 也就是2倍加当前数值 快了一些
再后来发现 被 5 整除只需要记录个位数就行了 也就是除 10 取余数
又因为,对于个位数 6 ,6 = 1 + 5, 6 * 2 = (1 + 5)* 2,6 * 2 % 10 = (1 + 5) * 2 % 10 = 2
因此每次记录除以 5 的余数即可。

1046–最后一块石头的重量

有一堆石头,每块石头的重量都是正整数。

每一回合,从中选出两块 最重的 石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:

如果 x == y,那么两块石头都会被完全粉碎;
如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。
最后,最多只会剩下一块石头。返回此石头的重量。如果没有石头剩下,就返回 0。

示例:

输入:[2,7,4,1,8,1]
输出:1
解释:
先选出 7 和 8,得到 1,所以数组转换为 [2,4,1,1,1],
再选出 2 和 4,得到 2,所以数组转换为 [2,1,1,1],
接着是 2 和 1,得到 1,所以数组转换为 [1,1,1],
最后选出 1 和 1,得到 0,最终数组转换为 [1],这就是最后剩下那块石头的重量。

提示:

1 <= stones.length <= 30
1 <= stones[i] <= 1000

class Solution:
    def lastStoneWeight(self, stones: list) -> int:
        while 1:
            if len(stones) == 0:
                return 0
            if len(stones) == 1:
                return stones[0]
            Max1 = max(stones)
            stones.pop(stones.index(Max1))
            Max2 = max(stones)
            stones.pop(stones.index(Max2))
            if Max1 != Max2:
                stones.append(Max1 - Max2 if Max1 > Max2 else Max2 - Max1)


    #### heapq 实现大根堆
    def lastStoneWeight_1(self, stones: List[int]) -> int:
        # 初始化
        heap = [-stone for stone in stones]
        heapq.heapify(heap)

        # 模拟
        while len(heap) > 1:
            x,y = heapq.heappop(heap),heapq.heappop(heap)
            if x != y:
                heapq.heappush(heap,x-y)

        if heap: return -heap[0]
        return 0

我的代码用了好多函数和方法,感觉有点无耻。但是 C++ 里的 vector 也有类似的功能吧。

我也不太确定做这种题 方法和函数 怎么用才不算过分。

1232–缀点成线

在一个 XY 坐标系中有一些点,我们用数组 coordinates 来分别记录它们的坐标,其中 coordinates[i] = [x, y] 表示横坐标为 x、纵坐标为 y 的点。

请你来判断,这些点是否在该坐标系中属于同一条直线上,是则返回 true,否则请返回 false。

示例 1:

输入:coordinates = [[1,2],[2,3],[3,4],[4,5],[5,6],[6,7]]
输出:true
示例 2:

输入:coordinates = [[1,1],[2,2],[3,4],[4,5],[5,6],[7,7]]
输出:false

提示:

2 <= coordinates.length <= 1000
coordinates[i].length == 2
-10^4 <= coordinates[i][0], coordinates[i][1] <= 10^4
coordinates 中不含重复的点

class Solution:
    def checkStraightLine(self, coordinates: List[List[int]]) -> bool:
        n = len(coordinates)
        if n == 2:return True
        dx = coordinates[1][0] - coordinates[0][0]
        dy = coordinates[1][1] - coordinates[0][1]

        for i in range(2,n):
            dx1 = coordinates[i][0] - coordinates[0][0]
            dy1 = coordinates[i][1] - coordinates[0][1]
            if dx1 * dy != dy1 * dx: 
                return False
        return True

注意,比较斜率相等与否,把除法转换成乘法,一方面可以避免除 0 的问题发生,另一方面乘法的计算似乎比除法简单一些。

123–买股票的最佳时机(三)

给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:

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

输入:prices = [1]
输出:0

提示:

1 <= prices.length <= 105
0 <= prices[i] <= 105

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

可怕的动态规划。

188–买股票的最佳时机(四)

给定一个整数数组 prices ,它的第 i 个元素 prices[i] 是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

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

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

提示:
0 <= k <= 109
0 <= prices.length <= 1000
0 <= prices[i] <= 1000

class Solution:
    def maxProfit(self, k: int , prices: [int]) -> int:
        if not prices:
        	return 0

        n = len(prices)
        k = min(k, n // 2)
        buy = [[0] * (k + 1) for _ in range(n)]
        sell = [[0] * (k + 1) for _ in range(n)]

        buy[0][0],sell[0][0] = -prices[0],0

        for i in range(1,k + 1):
        	buy[0][i] = sell[0][i] = float("-inf")


        for i in range(1,k + 1):
        	buy[i][0] = max(buy[i-1][0],sell[i-1][0] - prices[i])
        	for j in range(1,n):
        		buy[i][j] = max(buy[i - 1][j],sell[i - 1][j] - prices[i])
        		sell[i][j] = max(sell[i - 1][j],buy[i-1][j-1] + prices[i])

        return max(sell[n-1])
    def maxProfit_Opt(self, k:int , prices:[int]) -> int:
    	if not prices:
    		return 0

    	n = len(prices)
    	k = min(k, n // 2)
    	buy = [0] * (k+1)
    	sell = [0] * (K+1)

    	buy[0],sell[0] = -prices[0] ,0
    	for i in range(1,k + 1):
    		buy[i] = sell[i] = float("-inf")

    	for i in range(1,n):
    		buy[0] = max(buy[0],sell[0] - prices[i])
    		for j in range(1 , k + 1):
    			buy[j] = max(buy[j],sell[j] - prices[i])
    			sell[j] = max(sell[j],buy[j-1] + prices[i])

    	return max(sell)

可怕的动态规划。

239–滑动窗口最大值

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回滑动窗口中的最大值。

示例 1:

输入: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 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7
示例 2:

输入:nums = [1], k = 1
输出:[1]
示例 3:

输入:nums = [1,-1], k = 1
输出:[1,-1]
示例 4:

输入:nums = [9,11], k = 2
输出:[11]
示例 5:

输入:nums = [4,-2], k = 2
输出:[4]

提示:

1 <= nums.length <= 105
-104 <= nums[i] <= 104
1 <= k <= nums.length

class Solution:
    def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
        '''
        头脑简单的超时解法
        '''
        result = []
        for i in range(len(nums) - k + 1):
            result.append(max(nums[i:i+k]))
        return result
    def maxSlidingWindow_2(self, nums: List[int], k: int) -> List[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

可怕的优先队列(堆)。

684–冗余连接

在本问题中, 树指的是一个连通且无环的无向图。

输入一个图,该图由一个有着N个节点 (节点值不重复1, 2, …, N) 的树及一条附加的边构成。附加的边的两个顶点包含在1到N中间,这条附加的边不属于树中已存在的边。

结果图是一个以边组成的二维数组。每一个边的元素是一对[u, v] ,满足 u < v,表示连接顶点u 和v的无向图的边。

返回一条可以删去的边,使得结果图是一个有着N个节点的树。如果有多个答案,则返回二维数组中最后出现的边。答案边 [u, v] 应满足相同的格式 u < v。

示例 1:

输入: [[1,2], [1,3], [2,3]]
输出: [2,3]
解释: 给定的无向图为:
1
/
2 - 3
示例 2:

输入: [[1,2], [2,3], [3,4], [1,4], [1,5]]
输出: [1,4]
解释: 给定的无向图为:
5 - 1 - 2
| |
4 - 3
注意:

输入的二维数组大小在 3 到 1000。
二维数组中的整数在1到N之间,其中N是输入数组的大小。

class Solution:
    def findRedundantConnection(self, edges: List[List[int]]) -> List[int]:

        nodesCount = len(edges)
        parent = list(range(nodesCount + 1))

        def find(index: int) -> int:
            if parent[index] != index:
                parent[index] = find(parent[index])
            return parent[index]
        
        def union(index1: int, index2: int):
            parent[find(index1)] = find(index2)

        for node1, node2 in edges:
            if find(node1) != find(node2):
                union(node1, node2)
            else:
                return [node1, node2]
        
        return []

可怕的并查集。

803–敲砖块

有一个 m x n 的二元网格,其中 1 表示砖块,0 表示空白。砖块 稳定(不会掉落)的前提是:

一块砖直接连接到网格的顶部,或者
至少有一块相邻(4 个方向之一)砖块 稳定 不会掉落时
给你一个数组 hits ,这是需要依次消除砖块的位置。每当消除 hits[i] = (rowi, coli) 位置上的砖块时,对应位置的砖块(若存在)会消失,然后其他的砖块可能因为这一消除操作而掉落。一旦砖块掉落,它会立即从网格中消失(即,它不会落在其他稳定的砖块上)。

返回一个数组 result ,其中 result[i] 表示第 i 次消除操作对应掉落的砖块数目。

注意,消除可能指向是没有砖块的空白位置,如果发生这种情况,则没有砖块掉落。

示例 1:

输入:grid = [[1,0,0,0],[1,1,1,0]], hits = [[1,0]]
输出:[2]
解释:
网格开始为:
[[1,0,0,0],
[1,1,1,0]]
消除 (1,0) 处加粗的砖块,得到网格:
[[1,0,0,0]
[0,1,1,0]]
两个加粗的砖不再稳定,因为它们不再与顶部相连,也不再与另一个稳定的砖相邻,因此它们将掉落。得到网格:
[[1,0,0,0],
[0,0,0,0]]
因此,结果为 [2] 。
示例 2:

输入:grid = [[1,0,0,0],[1,1,0,0]], hits = [[1,1],[1,0]]
输出:[0,0]
解释:
网格开始为:
[[1,0,0,0],
[1,1,0,0]]
消除 (1,1) 处加粗的砖块,得到网格:
[[1,0,0,0],
[1,0,0,0]]
剩下的砖都很稳定,所以不会掉落。网格保持不变:
[[1,0,0,0],
[1,0,0,0]]
接下来消除 (1,0) 处加粗的砖块,得到网格:
[[1,0,0,0],
[0,0,0,0]]
剩下的砖块仍然是稳定的,所以不会有砖块掉落。
因此,结果为 [0,0] 。

提示:

m == grid.length
n == grid[i].length
1 <= m, n <= 200
grid[i][j] 为 0 或 1
1 <= hits.length <= 4 * 104
hits[i].length == 2
0 <= xi <= m - 1
0 <= yi <= n - 1
所有 (xi, yi) 互不相同

class UnionFind:
	def __init__(self):
		self.father = {}
		self.size_of_set = {}

	def get_size_of_set(self,x):
		'''
		获取所在连通块的大小
		'''
		return self.size_of_set[self.find(x)]

	def find(size,x):
		root = x

		while self.father[root] != None:
			root = self.father[root]

		# 路径压缩
		while x != root:
			original_father = self.father[x]
			self.father[x] = root
			x = original_father
		
		return root

	def is_connected(self,x,y):
		return self.find(x) == self.find(y)

	def merge(self,x,y):
		root_x,root_y = self.find(x),self.find(y)

		if root_x != root_y:
			self.father[root_x] = root_y
			# 更新根节点连通块数量
			self.size_of_set[root_y] += self.size_of_set[root_x]
			del self.size_of_set[root_x]

	def add(self,x):
		if x not in self.father:
			self.father[x] = None
			self.size_of_set[x] = 1



class Solution:
	def __init__(self):
		self.CEILING = (-1,-1)
		self.DIRECTION = ((1,0),(-1,0),(0,1),(0,-1))

	def initalize(self,uf,m,n,grid,hits):
		'''
		初始化
		'''
		# 添加天花板
		uf.add(self.CEILING)

		# 敲掉所有要敲掉的砖块
		for x,y in hits:
			grid[x][y] -= 1

		# 连接,合并剩余的砖块
		for i in range(m):
			for j in range(n):
				if grid[i][j] == 1:
					uf.add((i,j))

		for i in range(m):
			for j in range(n):
				if grid[i][j] == 1:
					self.merge_neighbors(uf,m,n,grid,i,j)

		# 与天花板合并
		for j in range(n):
			if grid[0][j] == 1:
				uf.merge((0,j),self.CEILING)

	def is_valid(self,x,y,grid,m,n):
		return 0 <= x < m and 0 <= y < n and grid[x][y] == 1

	def merge_neighbors(self,uf,m,n,grid,x,y):
		'''
		与上下左右的砖块合并
		'''
		for dx,dy in self.DIRECTION:
			nx,ny = x + dx,y+dy
			if not self.is_valid(nx,ny,grid,m,n):
				continue
			uf.merge((x,y),(nx,ny))


    def hitBricks(self, grid: List[List[int]], hits: List[List[int]]) -> List[int]:
    	uf = UnionFind()
    	m,n = len(grid),len(grid[0])
    	res = [0] + len(hits)

    	# 初始化
    	self.initalize(uf,m,n,grid,hits)

    	for i in range(len(hits)-1,-1,-1):
    		x,y = hits[i][0],hits[i][1]

    		# 还原敲击
    		grid[x][y] += 1

    		# 敲的地方原本就不是砖块
    		if grid[x][y] != 1:
    			continue

哎,练练敲代码吧。

947–移除最多的同行或同列石头

n 块石头放置在二维平面中的一些整数坐标点上。每个坐标点上最多只能有一块石头。

如果一块石头的 同行或者同列 上有其他石头存在,那么就可以移除这块石头。

给你一个长度为 n 的数组 stones ,其中 stones[i] = [xi, yi] 表示第 i 块石头的位置,返回 可以移除的石子 的最大数量。

示例 1:

输入:stones = [[0,0],[0,1],[1,0],[1,2],[2,1],[2,2]]
输出:5
解释:一种移除 5 块石头的方法如下所示:

  1. 移除石头 [2,2] ,因为它和 [2,1] 同行。
  2. 移除石头 [2,1] ,因为它和 [0,1] 同列。
  3. 移除石头 [1,2] ,因为它和 [1,0] 同行。
  4. 移除石头 [1,0] ,因为它和 [0,0] 同列。
  5. 移除石头 [0,1] ,因为它和 [0,0] 同行。
    石头 [0,0] 不能移除,因为它没有与另一块石头同行/列。
    示例 2:

输入:stones = [[0,0],[0,2],[1,1],[2,0],[2,2]]
输出:3
解释:一种移除 3 块石头的方法如下所示:

  1. 移除石头 [2,2] ,因为它和 [2,0] 同行。
  2. 移除石头 [2,0] ,因为它和 [0,0] 同列。
  3. 移除石头 [0,2] ,因为它和 [0,0] 同行。
    石头 [0,0] 和 [1,1] 不能移除,因为它们没有与另一块石头同行/列。
    示例 3:

输入:stones = [[0,0]]
输出:0
解释:[0,0] 是平面上唯一一块石头,所以不可以移除它。

提示:

1 <= stones.length <= 1000
0 <= xi, yi <= 104
不会有两块石头放在同一个坐标点上

class Solution:
	def removeStones(self, stones: List[List[int]]) -> int:
		'''
		优化建图 + 深度优先
		'''
		n = len(stones)
		edge = collections.defaultdict(list)
		rec = collections.defaultdict(list)
		for i,(x,y) in enumerate(stones):
			rec[x].append(i)
			rec[1000 + y].append(i)

		for vec in rec.values():
			k = len(vec)
			for i in range(1,k):
				edge[vec[i]].append(vec[i-1])
				edge[vec[i-1]].append(vec[i])

		def dfs(node):
			vis.add(node)
			for y in edge[node]:
				if y not in vis:
					dfs(y)

		vis = set()
		num = 0
		for i in range(n):
			if i not in vis:
				num += 1
				dfs(i)

		return n - num




    def removeStones_1(self, stones: List[List[int]]) -> int:
    	'''
    	深度优先
    	'''
        n = len(stones)
        edge = collections.defaultdict(list)
        for i, (x1, y1) in enumerate(stones):
            for j, (x2, y2) in enumerate(stones):
                if x1 == x2 or y1 == y2:
                    edge[i].append(j)
        
        def dfs(x: int):
            vis.add(x)
            for y in edge[x]:
                if y not in vis:
                    dfs(y)
        
        vis = set()
        num = 0
        for i in range(n):
            if i not in vis:
                num += 1
                dfs(i)
        
        return n - num







    def removeStones_2(self, stones: List[List[int]]) -> int:
    	'''
    	优化建图 + 并查集
    	'''
        dsu = DisjointSetUnion()
        for x, y in stones:
            dsu.unionSet(x, y + 10000)
        return len(stones) - dsu.numberOfConnectedComponent()
class DisjointSetUnion:
    def __init__(self):
        self.f = dict()
        self.rank = dict()
    
    def find(self, x: int) -> int:
        if x not in self.f:
            self.f[x] = x
            self.rank[x] = 1
            return x
        if self.f[x] == x:
            return x
        self.f[x] = self.find(self.f[x])
        return self.f[x]
    
    def unionSet(self, x: int, y: int):
        fx, fy = self.find(x), self.find(y)
        if fx == fy:
            return
        if self.rank[fx] < self.rank[fy]:
            fx, fy = fy, fx
        self.rank[fx] += self.rank[fy]
        self.f[fy] = fx

    def numberOfConnectedComponent(self) -> int:
        return sum(1 for x, fa in self.f.items() if x == fa)

对于这种题,我现在的目标只有一个,就是看见能看出题目背景下隐藏的是什么数据结构或者算法问题。

刚看第一眼以为是图?看着不像啊。动态规划?也不会。手工算法都想不出来,更别提写代码了。😪

1202–交换字符串中的元素

给你一个字符串 s,以及该字符串中的一些「索引对」数组 pairs,其中 pairs[i] = [a, b] 表示字符串中的两个索引(编号从 0 开始)。

你可以 任意多次交换 在 pairs 中任意一对索引处的字符。

返回在经过若干次交换后,s 可以变成的按字典序最小的字符串。

示例 1:

输入:s = “dcab”, pairs = [[0,3],[1,2]]
输出:“bacd”
解释:
交换 s[0] 和 s[3], s = “bcad”
交换 s[1] 和 s[2], s = “bacd”
示例 2:

输入:s = “dcab”, pairs = [[0,3],[1,2],[0,2]]
输出:“abcd”
解释:
交换 s[0] 和 s[3], s = “bcad”
交换 s[0] 和 s[2], s = “acbd”
交换 s[1] 和 s[2], s = “abcd”
示例 3:

输入:s = “cba”, pairs = [[0,1],[1,2]]
输出:“abc”
解释:
交换 s[0] 和 s[1], s = “bca”
交换 s[1] 和 s[2], s = “bac”
交换 s[0] 和 s[1], s = “abc”

提示:

1 <= s.length <= 10^5
0 <= pairs.length <= 10^5
0 <= pairs[i][0], pairs[i][1] < s.length
s 中只含有小写英文字母

class DisjointSetUnion:
	'''
	并查集 还要学 先记录下来
	'''
    def __init__(self, n: int):
        self.n = n
        self.rank = [1] * n
        self.f = list(range(n))
    
    def find(self, x: int) -> int:
        if self.f[x] == x:
            return x
        self.f[x] = self.find(self.f[x])
        return self.f[x]
    
    def unionSet(self, x: int, y: int):
        fx, fy = self.find(x), self.find(y)
        if fx == fy:
            return
        if self.rank[fx] < self.rank[fy]:
            fx, fy = fy, fx
        self.rank[fx] += self.rank[fy]
        self.f[fy] = fx
        
class Solution:
    def smallestStringWithSwaps(self, s: str, pairs: List[List[int]]) -> str:
        dsu = DisjointSetUnion(len(s))
        for x, y in pairs:
            dsu.unionSet(x, y)
        
        mp = collections.defaultdict(list)
        for i, ch in enumerate(s):
            mp[dsu.find(i)].append(ch)
        
        for vec in mp.values():
            vec.sort(reverse=True)
        
        ans = list()
        for i in range(len(s)):
            x = dsu.find(i)
            ans.append(mp[x][-1])
            mp[x].pop()
        
        return "".join(ans)

可怕的并查集。

1203–项目管理

公司共有 n 个项目和 m 个小组,每个项目要不无人接手,要不就由 m 个小组之一负责。

group[i] 表示第 i 个项目所属的小组,如果这个项目目前无人接手,那么 group[i] 就等于 -1。(项目和小组都是从零开始编号的)小组可能存在没有接手任何项目的情况。

请你帮忙按要求安排这些项目的进度,并返回排序后的项目列表:

同一小组的项目,排序后在列表中彼此相邻。
项目之间存在一定的依赖关系,我们用一个列表 beforeItems 来表示,其中 beforeItems[i] 表示在进行第 i 个项目前(位于第 i 个项目左侧)应该完成的所有项目。
如果存在多个解决方案,只需要返回其中任意一个即可。如果没有合适的解决方案,就请返回一个 空列表 。

示例 1:

输入:n = 8, m = 2, group = [-1,-1,1,0,0,1,0,-1], beforeItems = [[],[6],[5],[6],[3,6],[],[],[]]
输出:[6,3,4,1,5,2,0,7]
示例 2:

输入:n = 8, m = 2, group = [-1,-1,1,0,0,1,0,-1], beforeItems = [[],[6],[5],[6],[3],[],[4],[]]
输出:[]
解释:与示例 1 大致相同,但是在排序后的列表中,4 必须放在 6 的前面。

提示:

1 <= m <= n <= 3 * 104
group.length == beforeItems.length == n
-1 <= group[i] <= m - 1
0 <= beforeItems[i].length <= n - 1
0 <= beforeItems[i][j] <= n - 1
i != beforeItems[i][j]
beforeItems[i] 不含重复元素

class Solution:
	'''
	两次拓扑排序
	'''
    def topological_sort(self,items,indegree,neighbors):
        # 建立队列和访问顺序
        queue = collections.deque()
        res = []
        
        # 初始化队列
        for item in items:
            if not indegree[item]:
                queue.append(item)
                
        if not queue: return []
        
        # BFS
        while queue:
            cur = queue.popleft()
            res.append(cur)
            
            # 遍历邻居节点
            for neighbor in neighbors[cur]:
                indegree[neighbor] -= 1
                if not indegree[neighbor]:
                    queue.append(neighbor)
                    
        return res
    
    
    def sortItems(self, n: int, m: int, group: List[int], beforeItems: List[List[int]]) -> List[int]:
        max_group_id = m
        for task in range(n):
            if group[task] == -1:
                group[task] = max_group_id
                max_group_id += 1
                
        task_indegree = [0] * n    
        group_indegree = [0] * max_group_id
        task_neighbors = [[] for _ in range(n)]
        group_neighbors = [[] for _ in range(max_group_id)]
        group_to_tasks = [[] for _ in range(max_group_id)]
        
        for task in range(n):
            group_to_tasks[group[task]].append(task)
            
            for prerequisite in beforeItems[task]:
                
                # 判断相关联的两个项目是否属于同一组
                if group[prerequisite] != group[task]:
                    
                    # 不是同组,给小组建图
                    group_indegree[group[task]] += 1
                    group_neighbors[group[prerequisite]].append(group[task])
                else:
                    # 同组,给组内项目建图
                    task_indegree[task] += 1
                    task_neighbors[prerequisite].append(task)
                    

        res = []
        
        # 得到小组的访问顺序
        group_queue = self.topological_sort([i for i in range(max_group_id)],group_indegree,group_neighbors)

        if len(group_queue) != max_group_id: return []
        
        for group_id in group_queue:
            # 得到每组项目的访问顺序
            task_queue = self.topological_sort(group_to_tasks[group_id],task_indegree,task_neighbors)

            if len(task_queue) != len(group_to_tasks[group_id]):
                return []
            res += task_queue
                    
        return res

可怕的拓扑排序。