代码随想录算法训练营第二十八天 | Leetcode随机抽题检测

Leetcode随机抽题检测--使用题库:Leetcode热题100

1 两数之和

未看解答自己编写的青春版

哈希哈希,觉得这题应该不用去看题解了。

python 复制代码
class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        table = {}
        n = len(nums)
        for i in range(n):
            if len(table) == 0 :
                table[nums[i]] = table.get(nums[i],0) + i
            else :
                temp = target - nums[i]
                if temp in table :
                    return [i,table[temp]]
                else :
                    table[nums[i]] = table.get(nums[i],0) + i
        return None

重点

题解的代码

日后再次复习重新写

49 字母异位词分组

未看解答自己编写的青春版

但是时间上仅打败5%,是哪里出了问题?好像也没法加速了啊

python 复制代码
class Solution:
    def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
        if len(strs) == 1 :
            return [strs]
        strs_sorted = [sorted(i) for i in strs]
        n = len(strs)
        used = [False]*n
        res = []
        for i in range(n):
            if used[i] == False :
                level = [strs[i]]
                used[i] = True
                for j in range(i+1,n):
                    if used[j] == False :
                    # 这句比较,引起了时间花费比字典直接索引大很多
                        if strs_sorted[i]==strs_sorted[j] :
                            level.append(strs[j])
                            used[j] = True
            
                res.append(level)

        return res

重点

为什么用了字典,就比我的used数组快呢?

我目前认为是:if strs_sorted[i]==strs_sorted[j] :

这句比较,引起了时间花费比字典直接索引大很多

题解的代码

python 复制代码
class Solution:
    def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
        dic = {}
        for s in strs:
            keys = "".join(sorted(s))
            if keys not in dic:
                dic[keys] = [s]
            else:
                dic[keys].append(s)
        return list(dic.values())

日后再次复习重新写

128 最长连续序列

未看解答自己编写的青春版

没思路。错误的代码:

python 复制代码
class Solution:
    def longestConsecutive(self, nums: List[int]) -> int:
        maxlength = 1
        table = {}
        for i in nums :
            if i not in table :
                table[i] = 1
                if i-1 in table :
                    table[i] += table[i-1]
                    table[i-1] = table[i]
                if i+1 in table :
                    table[i] += table[i+1]
                    table[i+1] = table[i]
                
            maxlength = max(maxlength,table[i])
            print(table)
        return maxlength

重点

关于 left 和 right 的疑问?

代码中 if num not in hash_dict就保证了 left 和 right 不会有交叠 进来的这个值没有在字典中出现过,意味着目前还没有任何区间包含这个值,如果 left 和 right 出现重叠了的,就意味着这个值之前已经在区间中了,矛盾了。

不可能有交叉,不可能一个没出现过的数,既属于left又属于right

为什么只需要更新 num-left 和 num+right ?

因为我们在判断时,只看当前数的前一个数和后一个数,所以在更新时,只需要更新边界,不在序列中的新数如果要使用已有的最大长度,一定会碰到边界,碰不到边界,那么值一定就是 1 。

题解的代码

利用 get 函数,可以少写很多判断的风格:

python 复制代码
class Solution(object):
    def longestConsecutive(self, nums):
        hash_dict = dict()
        
        max_length = 0
        for num in nums:
            if num not in hash_dict:
                left = hash_dict.get(num - 1, 0)
                right = hash_dict.get(num + 1, 0)
                
                cur_length = 1 + left + right
                if cur_length > max_length:
                    max_length = cur_length
                
                hash_dict[num] = cur_length
                hash_dict[num - left] = cur_length
                hash_dict[num + right] = cur_length
                
        return max_length

判断 i-1 和 i+1 的风格:

python 复制代码
class Solution {
public:
    int longestConsecutive(vector<int>& nums) {
        unordered_map<int,int> consecutive;
        int maxlong=0;
        for(int x:nums){
            if(consecutive.count(x)) continue;
            int nowlong=1;
            if(consecutive.count(x+1)&&consecutive.count(x-1)){
                nowlong+=consecutive[x+1]+consecutive[x-1];
                consecutive[x-consecutive[x-1]]=nowlong;
                consecutive[x+consecutive[x+1]]=nowlong;
            }
            else{
                if(consecutive.count(x-1)){
                    nowlong+=consecutive[x-1];
                    consecutive[x-consecutive[x-1]]=nowlong;
                }
                if(consecutive.count(x+1)){
                    nowlong+=consecutive[x+1];
                    consecutive[x+consecutive[x+1]]=nowlong;
                }
            }
            consecutive[x]=nowlong;
            maxlong=max(maxlong,nowlong);
        }
        return maxlong;
    }
};

看了解答后,自己又写了一遍的风格:

python 复制代码
class Solution:
    def longestConsecutive(self, nums: List[int]) -> int:
        maxlength = 0
        table = {}
        for i in nums :
            if i not in table :
                if i-1 in table and i+1 in table :
                    table[i] = table[i-1] + table[i+1] + 1
                    table[i-table[i-1]] = table[i] 
                    table[i+table[i+1]] = table[i]                
                elif i-1 in table :
                    table[i] = table[i-1] + 1
                    table[i-table[i-1]] = table[i]
                elif i+1 in table :
                    table[i] = table[i+1] + 1
                    table[i+table[i+1]] = table[i]
                else :
                    table[i] = 1

                
            maxlength = max(maxlength,table[i])
            
        return maxlength

日后再次复习重新写

283 移动零

双指针,过

11 盛最多水的容器

未看解答自己编写的青春版

没思路,知道是双指针,但是不清楚怎么移动。

看了解答后明白了,让高小的移动。

重点

对O(n)的算法写一下自己的理解,一开始两个指针一个指向开头一个指向结尾,此时容器的底是最大的,接下来随着指针向内移动,会造成容器的底变小,在这种情况下想要让容器盛水变多,就只有在容器的高上下功夫。 那我们该如何决策哪个指针移动呢?我们能够发现不管是左指针向右移动一位,还是右指针向左移动一位,容器的底都是一样的,都比原来减少了 1。这种情况下我们想要让指针移动后的容器面积增大,就要使移动后的容器的高尽量大,所以我们选择指针所指的高较小的那个指针进行移动,这样我们就保留了容器较高的那条边,放弃了较小的那条边,以获得有更高的边的机会。

python 复制代码
class Solution:
    def maxArea(self, height: List[int]) -> int:
        maxv = 0
        n = len(height)
        left = 0
        right = n-1
        while left < right :
            value = min(height[left],height[right])*(right-left)
            maxv = max(maxv,value)
            if height[left] >= height[right] :
                right -= 1
            else :
                left += 1
        return maxv

题解的代码

日后再次复习重新写

15 三数之和

过。

42 接雨水

未看解答自己编写的青春版

不会。

重点

这道题是属于双指针法的题目,但是还是不清楚怎么用双指针,根本原因是对如何计算雨水,不清楚。

诸如类似计算雨水的题目,有两种计算方式,横向和竖向。

本题,我将会学习四种解法,暴力,DP,双指针,单调栈。其中,前三种方法属于一类,DP和双指针其实都是对暴力的优化,在本题上并没有体现出自身方法的优势。单调栈的使用,本题是一个很经典的题目。

需要注意的是,因为前三种方法,我们要做的都是依次遍历数组,所以使用的计算方式是:按列计算雨水,这也是符合直觉的。

单调栈因为涉及弹入弹出,是按行计算雨水。

自己理解后的代码,这部分代码都是按我自己的风格编写的,会和题解有一些出入,比如大部分题解中,位置 i 的左右最大高度,初始值就是height[i],考虑了自己,但我没有考虑自己,初始值是0

暴力法:超时

python 复制代码
class Solution:
    def trap(self, height: List[int]) -> int:
        total = 0
        n = len(height)
        # 第一个和最后一个不可能接雨水
        for i in range(1,n-1):
            maxleft = 0
            maxright = 0
            j = i-1
            while j > -1 :
                maxleft = max(maxleft,height[j])
                j -= 1
            j = i+1
            while j < n :
                maxright = max(maxright,height[j])
                j += 1
            # 由于我前面编写的风格,要将结果和0做个判断,只保留非负数
            total += max(min(maxleft,maxright)-height[i],0)
        return total

DP:是不是可以提前保存好每个位置的左右最大值?其实就是对最大高度进行记忆化。

python 复制代码
class Solution:
    def trap(self, height: List[int]) -> int:
        total = 0
        n = len(height)
        dp = [[0]*2 for _ in range(n)]
        # dp[i][0] : 第i个位置的maxleft ; dp[i][1] : 第i个位置的maxright
        for i in range(1,n):
            dp[i][0] = max(dp[i-1][0],height[i-1])
        for i in range(n-2,-1,-1): 
            dp[i][1] = max(dp[i+1][1],height[i+1])
 
        # 第一个和最后一个不可能接雨水
        for i in range(1,n-1):
            maxleft = dp[i][0]
            maxright = dp[i][1] 
            # 由于我前面编写的风格,要将结果和0做个判断,只保留非负数
            total += max(min(maxleft,maxright)-height[i],0)
        return total

双指针:这里存在了一点冲突,我参考的两篇题解,在这里有一些分歧,卡哥的双指针法,其实就是完全手册里的二维DP,都是用两个一维数组,去储存每个位置的maxleft和maxright。

但是手册中的双指针思路如下:

要注意这种双指针法的编写逻辑,和上一题,"盛最多水的容器",相似。如何确定是移动 left 还是 right ? 看当前 mexleft 和 maxright 哪个小。因为我们在计算当前位置所能接的雨水时,首先要判断 min(maxleft , maxright) ,那么当前应该计算的雨水位置,就是小值的那一侧,假如maxleft小,那么不管后面的循环中,maxright再如何变化,在当前left处,min(maxleft , maxright) 的值不会再变化了(注意,当前left位置的maxleft,是由该位置之前的元素值决定的),所以,如果maxleft小,那么就计算left处的雨水,然后更新maxleft,然后移动left。right同理。

python 复制代码
class Solution:
    def trap(self, height: List[int]) -> int:
        total = 0
        n = len(height)
        maxleft = height[0]
        maxright = height[n-1]
        left = 1
        right = n-2
        while left <= right :
            if maxleft <= maxright :
                # 由于我前面编写的风格,要将结果和0做个判断,只保留非负数
                total += max(maxleft-height[left],0)
                # 注意逻辑,注意编写顺序,这里的更新要写在left更新之前
                # 因为在我的编写逻辑中,maxleft和maxright是不考虑当前值的
                # 如果先更新left了,maxleft就会漏掉当前left位置的值
                maxleft = max(maxleft,height[left])
                left += 1
                
            else :  
                # 由于我前面编写的风格,要将结果和0做个判断,只保留非负数
                total += max(maxright-height[right],0)
                # 注意逻辑,注意编写顺序,这里的更新要写在right更新之前
                maxright = max(maxright,height[right])
                right -= 1
                
        return total

单调栈:

本题要让0入栈,不让0入栈是不自然的,然后计算当前能承接水的体积的方式是:当前最外层遍历的元素是 i , 叫做right ,pop()出来的元素是 mid (这个值也是下标) , 当前单调栈stack内 (pop后的单调栈,单调栈的栈顶元素 stack[-1] ),此时的栈顶元素 stack[-1],叫做 left (这个值也是下标) 。计算以当前位置 mid 为底,所能承载的水的体积是:(min(nums[left] , nums[right]) - nums[mid]) * (right-left-1) 。

本题还有一个要注意的是:如果两个数值相等,怎么处理?照常入栈,顶替掉前一个!

python 复制代码
class Solution:
    def trap(self, height: List[int]) -> int:
        total = 0
        n = len(height)
        # 栈里放的是索引
        stack = [0]
        for i in range(1,n):  
            
            while stack != [] and height[i] >= height[stack[-1]] :
                mid = stack.pop()
                if stack != []:
                    left = stack[-1]
                    total += (min(height[left],height[i])-height[mid])*(i-left-1)
                
            stack.append(i)
            
        return total

两篇题解的地址

接雨水问题的超完全手册

代码随想录

日后再次复习重新写

84 柱状图中最大的矩形

未看解答自己编写的青春版

重点

题解的代码

日后再次复习重新写

3 无重复字符的最长子串

未看解答自己编写的青春版

一开始想用字典的,但是发现不行,因为要求子序列,要求必须连续,就想到了用双向队列。

python 复制代码
from collections import deque
class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        dq = deque()
        maxcount = 0
        count = 0
        for i in s :
            if i in dq :
                while dq[0] != i :
                    dq.popleft()
                    count -= 1
                dq.popleft()
                dq.append(i)
            else :
                dq.append(i)
                count += 1
                maxcount = max(count,maxcount)
        return maxcount

重点

注意:这道题是属于滑动窗口的题目。

用 set 要比我用 deque 来的更巧妙一些。因为本题所要求为去重的子串,而 set 自带去重特性。

python 复制代码
class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        ans = left = 0
        window = set()  # 维护从下标 left 到下标 right 的字符
        for right, c in enumerate(s):
            while c in window:  # 加入 c 后,窗口内会有重复元素
                window.remove(s[left])
                left += 1  # 缩小窗口
            window.add(c)
            ans = max(ans, right - left + 1)  # 更新窗口长度最大值
        return ans

下面一版的代码解读:(下面这份代码不好理解,写的很简略,我觉得初学者还是先理解上面基于双向队列或者set的做法)

i是截至j,以j为最后一个元素的最长不重复子串的起始位置,即索引范围是[i,j]的子串是以索引j为最后一个元素的最长子串。 当索引从j-1增加到j时,原来的子串[i,j-1]新增了一个元素变为[i,j],需要判断j是否与[i,j-1]中元素有重复。所以if s[j] in st:是判断s[j]相同元素上次出现的位置,和i孰大孰小。如果i大,说明[i,j-1]中没有与s[j]相同的元素,起始位置仍取i;如果i小,则在[i,j-1]中有了与s[j]相同的元素,所以起始位置变为st[s[j]]+1,即[st[sj]+1,j]。而省略掉的else部分,由于s[j]是第一次出现所以前面必然没有重复的,仍然用i作为起始位置即可。 后面的ans=max(ans,j-i+1)中,括号中前者ans是前j-1个元素最长子串长度,j-i+1是以s[j]结尾的最长子串长度,两者(最长子串要么不包括j,要么包括j)取最大即可更新ans,遍历所有i后得到整个输入的最长子串长度。

python 复制代码
class Solution:
    def lengthOfLongestSubstring(self, s):
        """
        :type s: str
        :rtype: int
        """
        st = {}
        i, ans = 0, 0
        for j in range(len(s)):
            if s[j] in st:
                i = max(st[s[j]], i)
            ans = max(ans, j - i + 1)
            st[s[j]] = j + 1
        return ans;

题解的代码

日后再次复习重新写

集合方法复写:

python 复制代码
class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        
        maxcount = 0
        window = set()
        left = 0
        for index,value in enumerate(s):
            while value in window :
                window.remove(s[left])
                left += 1
            window.add(value)
            maxcount = max(maxcount,index-left+1)

           
        return maxcount

438 找到字符串中所有字母异位词

未看解答自己编写的青春版

排序方法,时间复杂度过高。

python 复制代码
class Solution:
    def findAnagrams(self, s: str, p: str) -> List[int]:
        n = len(s)
        m = len(p)
        res = []
        target = sorted(p)
        for i in range(0,n-m+1):
            temp = s[i:i+m]
            if sorted(temp) == target :
                res.append(i)
        return res

用字典记录:注意特殊情况的处理( m > n),注意初始化,不要放在循环里,放在外面初始化。注意收获结果的判断逻辑的放置位置,要放在循环逻辑的最后,所以在初始化后就应该立刻进行一次判断。

python 复制代码
class Solution:
    def findAnagrams(self, s: str, p: str) -> List[int]:
        table = {}
        m = len(p)
        n = len(s)
        if m > n :
            return []
        left = 0
        right = 0
        res = []
        for i in p :
            table[i] = table.get(i,0)+1
        
        temp = {}
        while right < m :
            temp[s[right]] = temp.get(s[right],0)+1
            right += 1
        if temp == table :
            res.append(left)
        for i in range(right,n) :
            temp[s[left]]-=1
            if temp[s[left]] == 0 :
                del temp[s[left]]
            left += 1
            temp[s[right]] = temp.get(s[right],0)+1
            right += 1
            if temp == table :
                res.append(left)
            #print(temp)
        return res

根本不需要,上面,这么复杂的,初始化,以及 left right 走来走去,精简版:

python 复制代码
class Solution:
    def findAnagrams(self, s: str, p: str) -> List[int]:
        table = {}
        m = len(p)
        n = len(s)
        if m > n :
            return []
        res = []
        temp = {}
        for i in range(m) :
            table[p[i]] = table.get(p[i],0)+1
            temp[s[i]] = temp.get(s[i],0)+1     
        if temp == table :
            res.append(0)
        for i in range(m,n) :
            temp[s[i-m]]-=1
            if temp[s[i-m]] == 0 :
                del temp[s[i-m]]
            temp[s[i]] = temp.get(s[i],0)+1 
            if temp == table :
                res.append(i-m+1)
            #print(temp)
        return res

用哈希,注意到题目说明:两个字符串均只包含小写字母。

python 复制代码
class Solution:
    def findAnagrams(self, s: str, p: str) -> List[int]:
    	if len(s) < len(p):
    		return []
    	Num = []
    	n = len(p)
    	A = [0] * 26
    	for i in range(n):
    		A[ord(p[i]) - ord('a')] += 1
    		A[ord(s[i]) - ord('a')] -= 1
    	if A == [0] * 26:
    		Num.append(0)
    	for i in range(n, len(s)):
    		A[ord(s[i]) - ord('a')] -= 1
    		A[ord(s[i - n]) - ord('a')] += 1
    		if A == [0] * 26:
    			Num.append(i + 1 - n)
    	return Num

数组哈希方法,复写:

python 复制代码
class Solution:
    def findAnagrams(self, s: str, p: str) -> List[int]:
        
        m = len(p)
        n = len(s)
        if m > n :
            return []
        res = []
        A = [0]*26
        for i in range(m) :
            A[ord(p[i])-ord('a')] += 1
            A[ord(s[i])-ord('a')] -= 1
        if A == [0]*26 :
            res.append(0)
        for i in range(m,n) :
            A[ord(s[i])-ord('a')] -= 1
            A[ord(s[i-m])-ord('a')] += 1
            
            if A == [0]*26 :
                res.append(i-m+1)
            #print(temp)
        return res

重点

注意:这道题是属于滑动窗口的题目。

题解的代码

日后再次复习重新写

560 和为 K 的子数组

未看解答自己编写的青春版

不会。

重点

为什么这题不可以用双指针/滑动窗口:因为nums[i]可以小于0,也就是说右指针i向后移1位不能保证区间会增大,左指针j向后移1位也不能保证区间和会减小。给定j,i的位置没有二段性,vice versa。

我也想到了不可以用双指针。

暴力:超时

python 复制代码
class Solution:
    def subarraySum(self, nums: List[int], k: int) -> int:
        count = 0
        n = len(nums)
        for i in range(n):
            sums = 0
            for j in range(i,n):
                sums += nums[j]
                if sums == k :
                    count += 1
        return count

前缀和 + 哈希表优化:

这方法,如果不是见过,真的很难想到啊。

题解的代码

python 复制代码
class Solution:
    def subarraySum(self, nums: List[int], k: int) -> int:
        table = {}
        table[0] = 1
        total = 0
        count = 0
        n = len(nums)
        for i in range(n):
            total += nums[i]
            if total-k in table :
                count += table[total-k]
            table[total] = table.get(total,0)+1
        return count

日后再次复习重新写

239 滑动窗口最大值

未看解答自己编写的青春版

单调队列,我觉得这题考察的就是,如何根据题意,模拟出答案更新的过程,然后发现其中有什么规律,自然而然地去设计一个这样的单调队列。只保留大的数。

以及弹出元素和加入元素的逻辑,判断条件。

python 复制代码
from collections import deque
class DQ:
    def __init__(self):
        self.dq = deque()
    def push(self,val):
        if len(self.dq)==0 :
            self.dq.append(val)
        else :
            while self.dq and self.dq[-1] < val :
                self.dq.pop()
            self.dq.append(val)
        
    def top(self):
        return self.dq[0]
        
    def popleft(self):
        self.dq.popleft()
        
                
class Solution:
    def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
        dq = DQ()
        n = len(nums)
        
        for i in range(k):
            dq.push(nums[i])
        res = [dq.top()]
        for i in range(k,n):
            remove = i-k
            if nums[remove]==dq.top():
                dq.popleft()
            dq.push(nums[i])
            res.append(dq.top())
        return res

重点

当然我觉得我能顺利地解决这道题,还是之前已经学过了的缘故。
滑动窗口最大值

题解的代码

日后再次复习重新写

76 最小覆盖子串

未看解答自己编写的青春版

又没写出来,这道题的巧妙不仅仅在于,用一个字典或者哈希数组,去存储每个字符,而是用字符串 t 的长度,作为一个指示变量,来指导我们什么时候该上前推进了,而不是每次加入一个元素时,都要进行比较。

错误代码:只让 t 中有的元素,进入字典,这样做完全是自己给自己添麻烦,后续的移动也是一团糟。

python 复制代码
class Solution:
    def minWindow(self, s: str, t: str) -> str:
        n = len(s)
        m = len(t)
        if n < m :
            return ''
        table = {}
        for i in t :
            table[i] = table.get(i,0)+1
        left = 0
        
        rightmin = inf
        leftmin = 0
        while s[left] not in table :
            left += 1
        right = left
        flag = False
        while right < n :
            if s[right] in table :
                table[s[right]] -= 1
                if self.isright(table):
                    flag = True
                    break
            right += 1
        if not flag :
            return ''
        if right-left < rightmin-leftmin :
            rightmin,leftmin = right,left

        table[s[left]] += 1   
        left += 1

        while left < n and right < n :
            while s[left] not in table :
                left += 1
            if self.isright(table):
                if right-left < rightmin-leftmin :
                    rightmin,leftmin = right,left
                table[s[left]] += 1   
                left += 1
            else :
                right += 1
                flag = False
                while right < n :
                    if s[right] in table :
                        table[s[right]] -= 1
                        if self.isright(table):
                            flag = True
                            break
                    right += 1
                if flag :
                    if right-left < rightmin-leftmin :
                        rightmin,leftmin = right,left
                    table[s[left]] += 1   
                    left += 1
                else :
                    break
        return s[leftmin:rightmin+1]

    def isright(self,table):
        for i in table :
            if table[i] > 0 :
                return False
        return True
          

重点

暂时不想写了,这道题,后面再复写。看是看懂了。

题解的代码

python 复制代码
from collections import defaultdict
class Solution:
    def minWindow(self, s: str, t: str) -> str:
        char_t = defaultdict(int)
        nt = len(t)
        ns = len(s)
        for i in range(nt):
            char_t[t[i]] += 1

        leftmin = 0
        rightmin = 2*ns
        left = 0
        right = 0
        while right < ns :
        	# 这里必须是大于0,因为对于不存在的元素,defaultdict的返回值也是0
            if char_t[s[right]] > 0 :
                char_t[s[right]] -= 1
                nt -= 1
            else :
                char_t[s[right]] -= 1 # 不是t的值也要记录

            if nt == 0 :
            # 这里必须是小于0,因为对于不存在的元素,defaultdict的返回值也是0
                while char_t[s[left]] < 0 :
                    char_t[s[left]] += 1
                    left += 1
                if rightmin - leftmin > right-left :
                    rightmin , leftmin = right , left 
                nt = 1
                # 要理解下面两行代码所代表的逻辑,如果进入判断nt==0,那么一定是在前面,新加入了一个
                # 属于t的字符,并且t中所有字符目前的值是0,而上面的while循环,只能寻找小于0的字符,
                # 当while循环终止时,找到的是最左边的为0的字符,那么此时,如果我们还想让left移动,
                # 那么就要在right的右边,找一个和该最左边字符相等的字符,所以要让char_t[s[left]] += 1
                # 同时也记得让left移动一格,这样在后面找到该字符时,向左移动的while循环是合法的
                char_t[s[left]] += 1
                left += 1
            right += 1
        
        if rightmin == 2* ns :
            return ''
        else :
            return s[leftmin:rightmin+1]

日后再次复习重新写

复写:

python 复制代码
class Solution:
    def minWindow(self, s: str, t: str) -> str:
        table = {}
        m = len(t)
        n = len(s)
        for i in t:
            table[i] = table.get(i,0)+1
        left = 0
        right = 0
        rightmin = 2*n
        leftmin = 0
        while right < n :
            if table.get(s[right],0) > 0 :
                table[s[right]] -= 1
                m -= 1
            else :
                # 这里处理的是两种情况,一是s[right]不在t中
                # 二是s[right]是t中的值,但是前面已经出现过,将其值减为0或者更小了,
                # 这里再次出现,也要减一,举例:s='ABBBC' t='ABC',其中的后两个B
                table[s[right]] = table.get(s[right],0)-1
            if m == 0 :
                while table[s[left]] < 0 :
                    table[s[left]]+=1
                    left+=1
                if rightmin-leftmin > right-left:
                    rightmin,leftmin = right,left
                m = 1
                table[s[left]]+=1
                left+=1
            right += 1
        if rightmin == 2*n :
            return ''
        else :
            return s[leftmin:rightmin+1]

53 最大子数组和

未看解答自己编写的青春版

这份代码其实有点牵强,单独写了,如果数组中全是负数的情况。

python 复制代码
class Solution:
    def maxSubArray(self, nums):
        maxcount = max(nums)
        if maxcount < 0 :
            return maxcount
        count = 0
        for i in nums:
            count += i
            if count < 0:
                count = 0
            maxcount = max(maxcount,count)
        return maxcount

重点

贪心 or DP 。

题解的代码

标准代码,不自主赋值,全部用数组中自己的值:

python 复制代码
class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        result = nums[0]
        maxsum = nums[0]
        n = len(nums)

        for i in range(1,n):

            if maxsum <= 0 :    
                maxsum = nums[i]
            else :
                maxsum += nums[i]

           
            result = max(maxsum,result)

        return result

日后再次复习重新写

56 合并区间

未看解答自己编写的青春版

python 复制代码
class Solution:
    def merge(self, intervals: List[List[int]]) -> List[List[int]]:
        intervals.sort()
        res = []
        n = len(intervals)
        temp = intervals[0]
        for i in range(1,n):
            if temp[1] >= intervals[i][0] :
                # 这里要注意,因为我只是先按照第一个坐标进行排序,所以前一个区间的右边界
                # 不一定比后一个区间的右边界小
                temp[1] = max(temp[1],intervals[i][1])
            else :
                res.append(temp)
                temp = intervals[i]
        # 这里注意,根据前面的逻辑,不管最后一层循环走的是if还是else,都没有将最后的结果放入结果集中
        res.append(temp)
        return res

重点

注意两个点就好了,都写在了代码的注释中。

题解的代码

日后再次复习重新写

189 轮转数组

未看解答自己编写的青春版

能想到两种方法,第一种是先反转整个列表,再分别反转两段。第二种是,一次移动一个,移动K次。

错误代码:

错误原因:像这种要求在原数组上修改的,那么就不能用原数组进行一些赋值操作,就必须在上面操作,赋值之后,虽然新变量名字和原数组一样,但是这也是两个变量了,所以,本题,首先不能用python自带的切片,因为倒序切片没法部分倒序,一处理就是整个序列倒序,但这是不行的,会涉及申请新空间问题 (temp = nums[0:k]) ; 那么就必须自主编写倒序函数,也不能编写为 self.reverse 的格式,因为这意味着必须要有返回值,而承接返回值就意味着赋值,就不是原数组修改了!

要学习下面这种在函数内定义函数的方式。

python 复制代码
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
        nums = nums[::-1]
        nums = self.reverse(0,k-1,nums)
        nums = self.reverse(k,n-1,nums)
        
    def reverse(self,i,j,nums):
        while i < j :
            nums[i],nums[j] = nums[j],nums[i]
            i += 1
            j -= 1
        return nums

一次向右循环移动一个数字,也是只需要O(1),但是时间复杂度上是O(k*n),这里不写这种方法的代码了。

重点

还有一种方法,就是申请一个K的空间,先把后K个数存起来。

python 复制代码
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:n]
        if n > k :
            for i in range(n-k-1,-1,-1):
                nums[i+k] = nums[i]
            nums[:k] = temp

题解的代码

日后再次复习重新写

238 除自身以外数组的乘积

未看解答自己编写的青春版

时间复杂度和空间复杂度均是 O(n) 的方法。

python 复制代码
class Solution:
    def productExceptSelf(self, nums: List[int]) -> List[int]:
        n = len(nums)
        res = [0]*n
        left = [0]*n
        right = [0]*n
        temp = 1
        for i in range(1,n):
            temp = temp * nums[i-1]
            left[i] = temp
        temp = 1
        for i in range(n-2,-1,-1):
            temp = temp*nums[i+1]
            right[i] = temp
        for i in range(n):
            if i == 0 :
                res[i] = right[i]
            elif i == n-1 :
                res[i] = left[i]
            else :
                res[i] = left[i] * right[i]

        return res

进阶,空间复杂度如何优化到 O(1) ,同时保持时间复杂度还是 O(n) ?

这种方法想不到。

重点

空间复杂度 O(1) 方法:

左边走一遍,右边走一遍,其中规律,自己试一试就能发现,我一开始总想着双指针,才一直想不出来。

python 复制代码
class Solution:
    def productExceptSelf(self, nums: List[int]) -> List[int]:
        n = len(nums)
        res = [1]*n        
        left = 1
        right = 1
        for i in range(n):
            res[i] = left
            left = left * nums[i]      
        for i in range(n-1,-1,-1):
            res[i] = res[i] * right
            right = right * nums[i]               
        return res

题解的代码

日后再次复习重新写

41 缺失的第一个正数

未看解答自己编写的青春版

不会,hard题没有思路。

间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案,想不出来。

重点

主要思路:

第一遍把所有负数换成INT_MAX。第二遍,将出现的数的绝对值对应的位置的数置为负数。第三遍,不是负数就输出位置。

两个要点:

1、取abs的操作很巧妙,这样就可以避免前面的操作,覆盖了后面的值。

2、抓住本题的循环不变量:只要该元素出现过了,那么该位置的就必须是负数,所以必须加abs

题解的代码

python 复制代码
class Solution:
    def firstMissingPositive(self, nums: List[int]) -> int:
        n = len(nums)
        for i in range(n):
            if nums[i] <= 0 :
                nums[i] = inf
        for i in range(n) :
            # 这里取abs的操作也很巧妙,这样就可以避免前面的操作,覆盖了后面的值
            temp = abs(nums[i])
            if temp > 0 and temp < n+1 :
                # 这里的abs太关键了,防止的就是多次操作的情况
                # 抓住本题的循环不变量:只要该元素出现过了,那么该位置的就必须是负数,所以必须加abs
                nums[temp-1] = -abs(nums[temp-1])
        for i in range(n) :
            if nums[i] > 0 :
                return i+1
        return n+1

日后再次复习重新写

73 矩阵置零

未看解答自己编写的青春版

O(1) 方法,用 inf 对"原本0"进行标记 :(感觉我这有些投机取巧)

python 复制代码
class Solution:
    def setZeroes(self, matrix: List[List[int]]) -> None:
        """
        Do not return anything, modify matrix in-place instead.
        """
        m = len(matrix)
        n = len(matrix[0])
        for i in range(m):
            for j in range(n):
                if matrix[i][j] == 0 :
                    matrix[i][j] = inf
        for i in range(m):
            for j in range(n):
                if matrix[i][j] == inf :
                    for k in range(n) :
                        if matrix[i][k] != inf :
                            matrix[i][k] = 0
                    for k in range(m) :
                        if matrix[k][j] != inf :
                            matrix[k][j] = 0
        for i in range(m):
            for j in range(n):
                if matrix[i][j] == inf :
                    matrix[i][j] = 0

O(mn)方法

必须用深拷贝,copy.deepcopy() ,我真是涨知识了。

python 复制代码
import copy
class Solution:
    def setZeroes(self, matrix: List[List[int]]) -> None:
        """
        Do not return anything, modify matrix in-place instead.
        """
        copy_matrix = copy.deepcopy(matrix)
        m = len(matrix)
        n = len(matrix[0])
        
        for i in range(m):
            for j in range(n):
                if matrix[i][j] == 0 and copy_matrix[i][j] == 0 :
                    
                    for k in range(n) :
                        matrix[i][k] = 0
                    for k in range(m) :
                        matrix[k][j] = 0

O(m+n)方法:

python 复制代码
class Solution:
    def setZeroes(self, matrix: List[List[int]]) -> None:
        """
        Do not return anything, modify matrix in-place instead.
        """
        
        m = len(matrix)
        n = len(matrix[0])
        exist_m = [False]*m
        exist_n = [False]*n
        for i in range(m):
            for j in range(n):
                if matrix[i][j] == 0 :
                    exist_m[i] = True
                    exist_n[j] = True
        
        for i in range(m):
            for j in range(n):
                if matrix[i][j] == 0 :
                    # 注意这里,对行操作还是对列操作
                    # 逻辑和前面给exist_m(n)是相反的
                    if exist_n[j] :
                        for k in range(m) :
                            # 这是对一列进行操作
                            matrix[k][j] = 0
        
                    if exist_m[i] :
                        for k in range(n) :
                            # 这是对一行进行操作
                            matrix[i][k] = 0

重点

本题要求用三种不同的空间复杂度的方法。

今天做了这道题真是涨知识了,原来在前面二叉树和回溯算法章节,我一直使用的,list.copy() 方法,一直都是浅拷贝,是共享内存的,卡哥一直用的 list[::-1] ,但是我经过试验,一维列表的.copy(),拷贝后数据是不随原数据的更改而更改的,但是多维列表会更改!

多维就要用深拷贝,copy.deepcopy() 。

随便找的一份讲解

题解的代码

看了一下评论的解答,为我愚蠢的矩阵赋值方式感到抓狂。

思路:

O(1) :

python 复制代码
class Solution:
    def setZeroes(self, matrix: List[List[int]]) -> None:
        """
        Do not return anything, modify matrix in-place instead.
        """
        
        m = len(matrix)
        n = len(matrix[0])
        row = False
        col = False

        '''错误逻辑
        for i in range(0,m):
            for j in range(0,n):
                # 注意看,这段逻辑是错误的,后面的修改逻辑可能导致 col row 的值发生错误
                # 而让每个位置都判断左右两边,是没有道理的
                if matrix[i][0] == 0 :
                    col = True
                if matrix[0][j] == 0 :
                    row = True
                if matrix[i][j] == 0 :
                    matrix[0][j] = 0
                    matrix[i][0] = 0
        '''
        for i in range(0,m):
            for j in range(0,n):
                # 正确逻辑,先判断此位置是不是0
                if matrix[i][j] == 0 :
                    if j == 0 :
                        col = True
                    if i == 0 :
                        row = True
                    matrix[0][j] = 0
                    matrix[i][0] = 0
                    
        
        for i in range(1,m):
            for j in range(1,n):
                if matrix[0][j] == 0 or matrix[i][0] == 0 :
                    matrix[i][j] = 0
        if row :
            for i in range(n):
                matrix[0][i] = 0
        if col :
            for i in range(m):
                matrix[i][0] = 0

O(m+n)方法:

python 复制代码
class Solution:
    def setZeroes(self, matrix: List[List[int]]) -> None:
        """
        Do not return anything, modify matrix in-place instead.
        """
        
        m = len(matrix)
        n = len(matrix[0])
        exist_m = [False]*m
        exist_n = [False]*n
        for i in range(m):
            for j in range(n):
                if matrix[i][j] == 0 :
                    exist_m[i] = True
                    exist_n[j] = True
        
        for i in range(m):
            for j in range(n):
                if exist_m[i] == True or exist_n[j] == True :                
                    matrix[i][j] = 0

O(mn)的方法好难想,真的不需要这么大的空间!不考虑这种方法了,直接学习O(1)的方法不好吗!

日后再次复习重新写

54 螺旋矩阵

未看解答自己编写的青春版

加一减一好像有些乱的版本,因为我给 right 和 down 的定义都是 m n 。所以:在索引时,要记得减一。在倒序时,因为第一个值是可以取到的,所以要记得减一。在倒序时,因为最后一个值是不能取到的,所以要对 left 和 up 减一。

python 复制代码
class Solution:
    def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
        m = len(matrix)
        n = len(matrix[0])
        left = 0
        right = n
        up = 0
        down = m
        total = m*n
        res = [0]*total
        count = 0
        while count < total :
            for i in range(left,right):
                res[count] = matrix[up][i]
                count += 1
            up += 1
            if up >= down :
                break
            for i in range(up,down):
                res[count] = matrix[i][right-1]
                count += 1
            right -= 1
            if right <= left :
                break
            for i in range(right-1,left-1,-1):
                res[count] = matrix[down-1][i]
                count += 1
            down -= 1
            if up >= down :
                break
            for i in range(down-1,up-1,-1):
                res[count] = matrix[i][left]
                count += 1
            left += 1
            if right <= left :
                break
            
        return res

更改下标逻辑:给 right 和 down 的定义变为 m-1 n-1 。值得注意的是,下标逻辑改变,判断跳出的逻辑也要相应改变。

python 复制代码
class Solution:
    def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
        m = len(matrix)
        n = len(matrix[0])
        left = 0
        right = n-1
        up = 0
        down = m-1
        total = m*n
        res = [0]*total
        count = 0
        while count < total :
            for i in range(left,right+1):
                res[count] = matrix[up][i]
                count += 1
            up += 1
            if up > down :
                break
            for i in range(up,down+1):
                res[count] = matrix[i][right]
                count += 1
            right -= 1
            if right < left :
                break
            for i in range(right,left-1,-1):
                res[count] = matrix[down][i]
                count += 1
            down -= 1
            if up > down :
                break
            for i in range(down,up-1,-1):
                res[count] = matrix[i][left]
                count += 1
            left += 1
            if right < left :
                break
            
        return res

重点

本题要注意的是,对各个指标的定义,会影响判断跳出的逻辑,这个要举例来验证了。但是从直觉上说,还是第二种方法好,不管是在边界处理上更为简洁,还是在跳出逻辑上更符合直觉。

相等时,应该会再有加入元素的操作的,不能跳出。

学习上面第二种的写法。

题解的代码

日后再次复习重新写

48 旋转图像

未看解答自己编写的青春版

没思路

重点

这道题太巧妙了!已经练到这种程度了,如果做题的时候发现没思路,就不要按照老思路一直想下去了,很有可能是思路错了!改变思路,从其他方向入手!

本题思路:先转置,再镜像对称。

或者,先上下翻转,再转置也行。

先转置,再镜像对称,的代码:

python 复制代码
class Solution:
    def rotate(self, matrix: List[List[int]]) -> None:
        """
        Do not return anything, modify matrix in-place instead.
        """
        m = len(matrix)
        n = len(matrix[0])
        for i in range(m):
            for j in range(i+1,n):
                matrix[i][j],matrix[j][i] = matrix[j][i],matrix[i][j]
        middle = n // 2
        for i in range(m):
            for j in range(middle):
                matrix[i][j],matrix[i][n-1-j] = matrix[i][n-1-j],matrix[i][j]
        

题解的代码

日后再次复习重新写

240 搜索二维矩阵 II

未看解答自己编写的青春版

倒序遍历,这道题正序遍历不行。

python 复制代码
class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        m = len(matrix)
        n = len(matrix[0])
        row = m-1
        col = n-1
        while row > -1 and matrix[row][0] > target :
            row -= 1
        while col > -1 and matrix[0][col] > target :
            col -= 1
        for i in range(row+1):
            for j in range(col+1):
                if matrix[i][j] == target :
                    return True

        return False

重点

另一种思路:

python 复制代码
class Solution:
    def searchMatrix(self, matrix, target):
        """
        :type matrix: List[List[int]]
        :type target: int
        :rtype: bool
        """
        m = len(matrix)
        if m == 0:
            return False
        n = len(matrix[0])
        if n == 0:
            return False

        i = m - 1
        j = 0
        while i >= 0 and j < n:
            if matrix[i][j] == target:
                return True
            elif matrix[i][j] < target:
                j = j + 1
            else:
                i = i - 1
        return False

还是这种思路厉害,时间复杂度是O(n),我的方法严格来说,还是O(n^2)

题解的代码

日后再次复习重新写

相关推荐
ChoSeitaku26 分钟前
链表循环及差集相关算法题|判断循环双链表是否对称|两循环单链表合并成循环链表|使双向循环链表有序|单循环链表改双向循环链表|两链表的差集(C)
c语言·算法·链表
Fuxiao___35 分钟前
不使用递归的决策树生成算法
算法
我爱工作&工作love我40 分钟前
1435:【例题3】曲线 一本通 代替三分
c++·算法
白-胖-子1 小时前
【蓝桥等考C++真题】蓝桥杯等级考试C++组第13级L13真题原题(含答案)-统计数字
开发语言·c++·算法·蓝桥杯·等考·13级
workflower1 小时前
数据结构练习题和答案
数据结构·算法·链表·线性回归
好睡凯1 小时前
c++写一个死锁并且自己解锁
开发语言·c++·算法
Sunyanhui12 小时前
力扣 二叉树的直径-543
算法·leetcode·职场和发展
一个不喜欢and不会代码的码农2 小时前
力扣105:从先序和中序序列构造二叉树
数据结构·算法·leetcode
前端郭德纲2 小时前
浏览器是加载ES6模块的?
javascript·算法
SoraLuna2 小时前
「Mac玩转仓颉内测版10」PTA刷题篇1 - L1-001 Hello World
算法·macos·cangjie