字节跳动-算法题整理

快排:

def quick_sort(arr):
    if len(arr) < 2:
        return arr
    mid = arr[len(arr)//2]
    left, right = [], []
    arr.remove(mid)
    for item in arr:
        if item > mid:
            right.append(item)
        else:
            left.append(item)
    return quick_sort(left)+[mid]+quick_sort(right)

b = [11, 99, 33, 69, 77, 88, 55, 11, 33, 36, 39, 66, 44, 22]
print(quick_sort(b))

挑战字符串:

1、无重复字符的最长子串

Given "abcabcbb", the answer is "abc", which the length is 3.

Given "bbbbb", the answer is "b", with the length of 1.

Given "pwwkew", the answer is "wke", with the length of 3. 


class Solution(object):
    def lengthOfLongestSubstring(self, s: str) -> int:
        # 字符串为空则返回零
        if not s:
            return 0
        window = []     # 滑动窗口数组
        max_length = 0  # 最长串长度
        # 遍历字符串
        for c in s:
            # 如果字符不在滑动窗口中,则直接扩展窗口
            if c not in window:
                # 使用当前字符扩展窗口
                window.append(c)
            # 如果字符在滑动窗口中,则
            # 1. 从窗口中移除重复字符及之前的字符串部分
            # 2. 再扩展窗口
            else:
                # 从窗口中移除重复字符及之前的字符串部分,新字符串即为无重复字符的字符串
                window[:] = window[window.index(c) + 1:]
                # 扩展窗口
                window.append(c)

            # 更新最大长度
            max_length = max(len(window), max_length)

        return max_length if max_length != 0 else len(s)

s = Solution()
strs = 'abcabcbb'
result = s.lengthOfLongestSubstring(strs)
print(result)

2、最长公共前缀

输入: ["flower","flow","flight"]
输出: "fl"

输入: ["dog","racecar","car"]
输出: ""
解释: 输入不存在公共前缀。

class Solution:
    def longestCommonPrefix(self, strs: List[str]) -> str:
        if not strs: return ""
        str0 = min(strs)    # min会找出list中首字母顺序最靠前的
        str1 = max(strs)    # max会找出list中首字母最考后的,如果第一个相同则看第二个
        for i in range(len(str0)):
            if str0[i] != str1[i]:
                return str0[:i]
        return str0

str = ["flower", "flow", "flight"]
s = Solution()
result = s.longestCommonPrefix(str)
print(result)

3、字符串的排列

示例1:
输入: s1 = "ab" s2 = "eidbaooo"
输出: True
解释: s2 包含 s1 的排列之一 ("ba").
 

示例2:
输入: s1= "ab" s2 = "eidboaoo"
输出: False

思考过程:
遇到问题先想暴力法,问题是s1的排列是不是s2的子串。
方法一 暴力法 [超过时间限制]
最简单的方法是生成短字符串s1的所有排列,并检查生成的排列是否是s2字符串的子字符串。
(生成所有可能的配对,可以采用交换位置法)

方法二 排序 [超过时间限制]
如何优化上诉暴力法呢?那就是采用排序统计全部字符出现次数,简化问题,如下:
只有当两个字符串包含相同次数的相同字符时,一个字符串才是另一个字符串的排列。
即sorted(x)= sorted(y)时,一个字符串x才​​是字符串 y 的排列。
为了检查这一点,我们可以用循环以一定滑动窗口在s2上面进行遍历,并比较sorted(x)= sorted(y)
显然这种算法是无法满足时间复杂度要求的,因为每个窗口都要sort一遍,sort最快也要o(n)

方法三 使用单纯哈希表 [通过]
方法二中sort排序太耗时,其实排列是一种排列组合,不包含顺序,那很自然可以用哈希表统计。
只有当两个字符串包含具有相同频率的相同字符时,一个字符串才是另一个字符串的排列,其实就是省略了方法二中的有序性的时间。
可以采用循环以一定滑动窗口在s2上面进行遍历,并检查出现在s1和s2中的字符出现的频率。如果每个字母的频率完全匹配,
则只有 s1 的排列可以是 s2 的子字符串。
显然这种算法虽然省略了有序性,但是构造哈希表也比较耗时

方法四 使用数组(字符哈希) [通过,用c++比较好实现]
方法三的哈希表构造比较耗时,题目中只有小写字母,因此很自然可以想到字符哈希,他是一种数组形式。
普及字符哈希:
因为小写字母对应ASCII码,因此字典一般是这样{字母-频率}{a:2},由于ASCII码,可以变为{a的ASCII码:2},此时的key就变味了数字,
所以就有了字符哈希,采用数组的形式,数组下标就是对应的key,也就是字母,数组的值就是对应的value,也就是该字母出现的频率。
可以使用更简单的数组数据结构来存储频率,而不是仅使用特殊的哈希表数据结构来存储字符出现的频率。
给定字符串仅包含小写字母('a'到'z')。因此我们需要采用大小为 26 的数组。其余过程与最后一种方法保持一致。

方法五 滑动窗口(更新边界法) [通过,在方法四基础上进行实现更快,也可以在方法三上实现加速]:
可以为 s2中的第一个窗口创建一次哈希表,而不是为 s2中考虑的每个窗口重新生成哈希表。
此时,滑动窗口每次滑动,其实只改变了边界情况,即删除了一个最前面的字符,加入了一个最后面的字符。

方法六 优化的滑动窗口(变量统计法) [通过]:
如果面试官还让你优化,那么就考虑哪些信息是无用,保留更少的信息,时间复杂度就越低,
显然并不用保存一个哈希表,哪怕只是更新边界都没有必要,只需要统计26个小写字母,哪些符合要求即可,如下;
不是比较每个更新的 s2map 的哈希表的所有元素,而是对应于 s2考虑的每个窗口,
我们会跟踪先前哈希表中已经匹配的元素数量当我们向右移动窗口时,只更新匹配元素的数量。
(这里就是26个字母出现的次数,比如s1有ab,那么对于s2滑动窗口,保证ab次数出现为1,其余为0,用一个cnt变量记录即可)
为此,我们维护一个 count变量,该变量存储字符数(s1出现的全部字母),这些字符在 s1中具有相同的出现频率,
当前窗口在 s2中。当我们滑动窗口时,如果扣除第一个元素并添加新元素导致任何字符的新频率匹配,我们将 count递增1.
如果不是,我们保持 count 不变或者-1。如果在移动窗口后,count的计算结果为26,则表示所有字符的频率完全匹配,返回True。
-------------------------------------华丽的分割线---------------------------------------
至此,你已经掌握了本题的求解思想,此时回头看答案就很easy
对于答案代码:
方法1:其实就是滑动窗口哈希表(更新边界法),对应上面的方法五
class Solution(object):
    def checkInclusion(self, s1, s2):
        """
        :type s1: str
        :type s2: str
        :rtype: bool
        """
        l1, l2 = len(s1), len(s2)
        c1 = collections.Counter(s1) #s1的哈希表,实质是字典
        c2 = collections.Counter() #实例化一个counter类
        p = q = 0  #设定下标初始化为0,滑动窗口就是[p,q]
        #下面就是不断在s2上面进行滑动窗口,不断更新哈希表进行比较,这是采用的边界更新法哦,因此是方法五,而不是方法三
        #这里补充一下,为什么滑动窗口用while没用for,其实都是一样的,你也可以改成for
        #但是对于有些情况,就只能用while,比如在回溯算法里面,即循环变量需要频繁的加减,显然此题并不是
        #因此对于此题,用for也可以,整体来说while的应用场合更加广泛
        while q < l2:
            c2[s2[q]] += 1   #统计字典哈希表
            if c1 == c2:
                return True  #注意,这种结果性条件判断一定是写在前面
            q += 1           #s2滑动窗口,下标后移
            if q - p + 1 > l1:   #为什么有这个呢?因为第一个滑动窗口比较特殊,要先构造第一个完整的滑动窗口,后面才是更新边界
                c2[s2[p]] -= 1   #字典哈希表移除最前面的字符
                if c2[s2[p]] == 0:  #由于counter特性,如果value为0,就删除它
                #否则会出现s1的map没有a,但是s2的map的a为0,此时是成立的,但是导致了这两个map不相等,结果出错
                    del c2[s2[p]]
                p += 1     #最前面的下标右移动
        return False  #遍历所有滑动窗口过后,仍然没返回true,那就是不合题意
        
方法2:优化的滑动窗口(变量统计法),其实就是上面的方法六
class Solution(object):
    def checkInclusion(self, s1, s2):
        """
        :type s1: str
        :type s2: str
        :rtype: bool
        """
        l1, l2 = len(s1), len(s2)
        c1 = collections.Counter(s1)
        c2 = collections.Counter()
        cnt = 0 #统计变量,全部26个字符,频率相同的个数,当cnt==s1字母的个数的时候,就是全部符合题意,返回真
        p = q = 0 #滑动窗口[p,q]
        while q < l2:
            c2[s2[q]] += 1
            if c1[s2[q]] == c2[s2[q]]: #对于遍历到的字母,如果出现次数相同
                cnt += 1               #统计变量+1
            if cnt == len(c1):         #判断结果写在前面,此时证明s2滑动窗口和s1全部字符相同,返回真
                return True
            q += 1                     #滑动窗口右移
            if q - p + 1 > l1:         #这是构造第一个滑动窗口的特殊判断,里面内容是维护边界滑动窗口
                if c1[s2[p]] == c2[s2[p]]:    #判断性的if写在前面,因为一旦频率变化,这个统计变量就减1
                    cnt -= 1
                c2[s2[p]] -= 1                #字典哈希表移除最前面的字符
                if c2[s2[p]] == 0:            #由于counter特性,如果value为0,必须删除它
                    del c2[s2[p]]
                p += 1                        #最前面的下标右移动
        return False
'''
class Solution(object):
    def checkInclusion(self, s1, s2):
        """
        :type s1: str
        :type s2: str
        :rtype: bool
        """
        l1, l2 = len(s1), len(s2)
        c1 = collections.Counter(s1)
        c2 = collections.Counter()
        cnt = 0 #统计变量,全部26个字符,频率相同的个数,当cnt==s1字母的个数的时候,就是全部符合题意,返回真
        p = q = 0 #滑动窗口[p,q]
        while q < l2:
            c2[s2[q]] += 1
            if c1[s2[q]] == c2[s2[q]]: #对于遍历到的字母,如果出现次数相同
                cnt += 1               #统计变量+1
            if cnt == len(c1):         #判断结果写在前面,此时证明s2滑动窗口和s1全部字符相同,返回真
                return True
            q += 1                     #滑动窗口右移
            if q - p + 1 > l1:         #这是构造第一个滑动窗口的特殊判断,里面内容是维护边界滑动窗口
                if c1[s2[p]] == c2[s2[p]]:    #判断性的if写在前面,因为一旦频率变化,这个统计变量就减1
                    cnt -= 1
                c2[s2[p]] -= 1                #字典哈希表移除最前面的字符
                if c2[s2[p]] == 0:            #由于counter特性,如果value为0,必须删除它
                    del c2[s2[p]]
                p += 1                        #最前面的下标右移动
        return False

5、字符串相乘

示例 1:

输入: num1 = "2", num2 = "3"
输出: "6"
示例 2:

输入: num1 = "123", num2 = "456"
输出: "56088"

# 将两个数组都反向后,然后顺序乘加到一个数组里面;最后再将该数组反转回来即可。
class Solution(object):
    def multiply(self, num1, num2):
        """
        :type num1: str
        :type num2: str
        :rtype: str
        """
        if num1 == "0" or num2 == "0":
            return "0"
        num2 = num2[::-1]
        num1 = num1[::-1]
        lenNum = len(num1) + len(num2) # 保存最终最大的数字
        returnNum = [0 for c in range(lenNum)] # 用list先存储
        for index2 in range(len(num2)):
            multiplier2 = int(num2[index2]) # 就直接按照顺序放,最后再反过来!
            for index1 in range(len(num1)):
                multiplier1 = int(num1[index1])
                temp = multiplier2 * multiplier1 + returnNum[index1 + index2]
                if temp >= 10:
                    returnNum[index1 + index2] = temp % 10
                    returnNum[index1 + index2 + 1] += int(temp / 10)
                else:
                    returnNum[index1 + index2] = temp
        returnNum = returnNum[::-1]
        while returnNum and returnNum[0] == 0:
            del returnNum[0]
        returnNum = [str(c) for c in returnNum]
        return ''.join(returnNum)

x = Solution()
print(x.multiply("0", "0"))

6、翻转字符串里的单词

示例 1:
输入: "the sky is blue"
输出: "blue is sky the"

示例 2:
输入: "  hello world!  "
输出: "world! hello"
解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。


class Solution:
    def reverseWords(self, s: str) -> 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 s[i] == ' ': i -= 1 # 跳过单词间空格
            j = i # j 指向下个单词的尾字符
        return ' '.join(res) # 拼接并返回

方法2:
class Solution:
    def reverseWords(self, s: str) -> str:
        s = s.strip() # 删除首尾空格
        strs = s.split() # 分割字符串
        strs.reverse() # 翻转单词列表
        return ' '.join(strs) # 拼接为字符串并返回

方法3:
class Solution:
    def reverseWords(self, s: str) -> str:
        return ' '.join(s.strip().split()[::-1])

7、简化路径

示例 1:
输入:"/home/"
输出:"/home"
解释:注意,最后一个目录名后面没有斜杠。
示例 2:
输入:"/../"
输出:"/"
解释:从根目录向上一级是不可行的,因为根是你可以到达的最高级。
示例 3:
输入:"/home//foo/"
输出:"/home/foo"
解释:在规范路径中,多个连续斜杠需要用一个斜杠替换。
示例 4:
输入:"/a/./b/../../c/"
输出:"/c"
示例 5:
输入:"/a/../../b/../c//.//"
输出:"/c"
示例 6:
输入:"/a//bc/d//././/.."
输出:"/a/b/c"


class Solution:
    def simplifyPath(self, path: str) -> str:
        stack = []
        path = path.split("/")

        for item in path:
            if item == "..":
                if stack : stack.pop()
            elif item and item != ".":
                stack.append(item)
        return "/" + "/".join(stack)

8、复原IP地址

给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。
有效的 IP 地址正好由四个整数(每个整数位于 0 到 255 之间组成),整数之间用 '.' 分隔。

示例:
输入: "25525511135"
输出: ["255.255.11.135", "255.255.111.35"]


class Solution:
    def restoreIpAddresses(self, s: str) -> List[str]:
        r = []
        def restore(count=0, ip='', s=''): # count record split times, ip record ip, s record remaining string
            if count == 4:
                if s == '':
                    r.append(ip[:-1])
                return
            if len(s) > 0:
                restore(count+1, ip+s[0]+'.', s[1:])
            if len(s) > 1 and s[0] != '0':
                restore(count+1, ip+s[:2]+'.', s[2:])
            if len(s) > 2 and s[0] != '0' and int(s[0:3]) < 256:
                restore(count+1, ip+s[:3]+'.', s[3:])
        restore(0, '', s)
        return r

 

数组与排序

1、三数之和

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。

示例:
给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
  [-1, 0, 1],
  [-1, -1, 2]
]


参考:https://leetcode-cn.com/problems/3sum/solution/3sumpai-xu-shuang-zhi-zhen-yi-dong-by-jyd/
class Solution:
    def threeSum(self, nums: [int]) -> [[int]]:
        nums.sort()
        res, k = [], 0
        for k in range(len(nums) - 2):
            if nums[k] > 0: break # 1. because of j > i > k.
            if k > 0 and nums[k] == nums[k - 1]: continue # 2. skip the same `nums[k]`.
            i, j = k + 1, len(nums) - 1
            while i < j: # 3. double pointer
                s = nums[k] + nums[i] + nums[j]
                if s < 0:
                    i += 1
                    while i < j and nums[i] == nums[i - 1]: i += 1
                elif s > 0:
                    j -= 1
                    while i < j and nums[j] == nums[j + 1]: j -= 1
                else:
                    res.append([nums[k], nums[i], nums[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

2、岛屿的最大面积

给定一个包含了一些 0 和 1 的非空二维数组 grid 。
一个 岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在水平或者竖直方向上相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。
找到给定的二维数组中最大的岛屿面积。(如果没有岛屿,则返回面积为 0 。)

示例 1:
[[0,0,1,0,0,0,0,1,0,0,0,0,0],
 [0,0,0,0,0,0,0,1,1,1,0,0,0],
 [0,1,1,0,1,0,0,0,0,0,0,0,0],
 [0,1,0,0,1,1,0,0,1,0,1,0,0],
 [0,1,0,0,1,1,0,0,1,1,1,0,0],
 [0,0,0,0,0,0,0,0,0,0,1,0,0],
 [0,0,0,0,0,0,0,1,1,1,0,0,0],
 [0,0,0,0,0,0,0,1,1,0,0,0,0]]
对于上面这个给定矩阵应返回 6。注意答案不应该是 11 ,因为岛屿只能包含水平或垂直的四个方向的 1 。

示例 2:
[[0,0,0,0,0,0,0,0]]
对于上面这个给定的矩阵, 返回 0。

3、搜索旋转排序数组

假设按照升序排序的数组在预先未知的某个点上进行了旋转。

( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。

搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。

你可以假设数组中不存在重复的元素。

你的算法时间复杂度必须是 O(log n) 级别。

示例 1:

输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4
示例 2:

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


关键点解析参考:https://leetcode-cn.com/problems/search-in-rotated-sorted-array/solution/pythonjs-er-fen-fa-33-sou-suo-xuan-zhuan-pai-xu-sh/
二分法
找出有序区间,然后根据target是否在有序区间舍弃一半元素
class Solution:
    def search(self, nums: List[int], target: int) -> int:
        """用二分法,先判断左右两边哪一边是有序的,再判断是否在有序的列表之内"""
        if len(nums) <= 0:
            return -1

        left = 0
        right = len(nums) - 1
        while left < right:
            mid = (right - left) // 2 + left
            if nums[mid] == target:
                return mid
            
            # 如果中间的值大于最左边的值,说明左边有序
            if nums[mid] > nums[left]:
                if nums[left] <= target <= nums[mid]:
                    right = mid
                else:
                    # 这里 +1,因为上面是 <= 符号
                    left = mid + 1
            # 否则右边有序
            else:
                # 注意:这里必须是 mid+1,因为根据我们的比较方式,mid属于左边的序列
                if nums[mid+1] <= target <= nums[right]:
                    left = mid + 1
                else:
                    right = mid
                    
        return left if nums[left] == target else -1


复杂度分析

时间复杂度:O(logN)O(logN)
空间复杂度:O(1)O(1)

5、最长连续递增序列

给定一个未经排序的整数数组,找到最长且连续的的递增序列,并返回该序列的长度。

 

示例 1:

输入: [1,3,5,4,7]
输出: 3
解释: 最长连续递增序列是 [1,3,5], 长度为3。
尽管 [1,3,5,7] 也是升序的子序列, 但它不是连续的,因为5和7在原数组里被4隔开。 
示例 2:

输入: [2,2,2,2,2]
输出: 1
解释: 最长连续递增序列是 [2], 长度为1。

def findLengthOfLCIS(nums):
    if not nums:
        return 0
    maxCount, count = 1, 1
    for i in range(len(nums) - 1):
        # 处于递增中
        if nums[i + 1] > nums[i]:
            count += 1
        else:
            # 计算最大长度
            maxCount = max(maxCount, count)
            count = 1
    # 最大长度在数组末尾的情况
    return max(maxCount, count)

print(findLengthOfLCIS([1,3,5,4,7]))
print(findLengthOfLCIS([2,2,2,2,2]))

6、数组中的第K个最大元素

在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

示例 1:

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

输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4

def quecksort_breif(a):
    '''
    NB的python思维实现
    :param a:
    :return:
    '''
    # 确定基条件
    if len(a) < 2:
        return a
    else:
        # 选择基准
        povit = a[0]

        # 确定基准的左边
        less = [i for i in a[1:] if i <= povit]

        # 确定基准的右边
        greater = [i for i in a[1:] if i > povit]

        # 递归调用
        return quecksort_breif(less) + [povit] + quecksort_breif(greater)

if __name__ == "__main__":
    a = [6,8,2,3,4,5,7]
    print("一般思维快排")
    quecksort_breif(a)

7、最长连续序列

给定一个未排序的整数数组,找出最长连续序列的长度。

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

示例:

输入: [100, 4, 200, 1, 3, 2]
输出: 4
解释: 最长连续序列是 [1, 2, 3, 4]。它的长度为 4。


class Solution:
    def longestConsecutive(self, nums: List[int]) -> int:
        nums = set(nums)        #去重复
        res = 0
        for num in nums:
        	# 判断是否是第一个数字
            if num - 1 not in nums:
                tmp = 1
                while num + 1 in nums:
                    num += 1
                    tmp += 1
                res = max(res, tmp)
        return res

8、第k个排列

给出集合 [1,2,3,…,n],其所有元素共有 n! 种排列。

按大小顺序列出所有排列情况,并一一标记,当 n = 3 时, 所有排列如下:

"123"
"132"
"213"
"231"
"312"
"321"
给定 n 和 k,返回第 k 个排列。

说明:

给定 n 的范围是 [1, 9]。
给定 k 的范围是[1,  n!]。
示例 1:

输入: n = 3, k = 3
输出: "213"
示例 2:

输入: n = 4, k = 9
输出: "2314"

参考:https://leetcode-cn.com/problems/permutation-sequence/solution/golang-100-faster-by-a-bai-152/

class Solution:
    def getPermutation(self, n: int, k: int) -> str:
        import math
        tokens = [str(i) for i in range(1, n+1)]
        res = ''
        k = k-1
        while n > 0:
            n -= 1
            a, k = divmod(k, math.factorial(n))
            res += tokens.pop(a)
        return res

9、朋友圈

10、合并区间

给出一个区间的集合,请合并所有重叠的区间。

示例 1:

输入: [[1,3],[2,6],[8,10],[15,18]]
输出: [[1,6],[8,10],[15,18]]
解释: 区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
示例 2:

输入: [[1,4],[4,5]]
输出: [[1,5]]
解释: 区间 [1,4] 和 [4,5] 可被视为重叠区间。

class Solution:
    def merge(self, intervals: List[List[int]]) -> List[List[int]]:
        if not intervals:
            return []
        # 去除重叠区间
        # 第一步,按照start升序排列
        intervals.sort(key = lambda x: x[0])
        # 第二步
        start = intervals[0][0]
        end   = intervals[0][1]
        ret   = []
        for idx in range(1, len(intervals)):
            curStart, curEnd = intervals[idx]
            # 如果有重叠,则更新end为更长的哪一个
            if curStart <= end:
                end = max(end, curEnd)
            else:
            # 如果没有重叠,则添加合并后区间,并更新下一个起点和终点
                ret.append([start, end])
                start = curStart
                end   = curEnd
        # 不要忘了最后这部,还剩下一组没有添加到
        else:
            ret.append([start, end])
        return ret

11、接雨水

 

链表与树

1、合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 

示例:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4

参考:递归讲解:https://leetcode-cn.com/problems/merge-two-sorted-lists/solution/yi-kan-jiu-hui-yi-xie-jiu-fei-xiang-jie-di-gui-by-/

class Solution:
    def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
        if not l1: return l2  # 终止条件,直到两个链表都空
        if not l2: return l1
        if l1.val <= l2.val:  # 递归调用
            l1.next = self.mergeTwoLists(l1.next,l2)
            return l1
        else:
            l2.next = self.mergeTwoLists(l1,l2.next)
            return l2

2、反转链表

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

算法思想:

第一步:先声明两个指针:

用来记录当前节点 cur
记录前面的节点 pre
第二步:换方向,本来 1 --> 2,我们反转就要变成 1 <-- 2

因为 cur.next 要换方向,所以此时需要一个临时节点 tmp保存当前节点下一个位置,tmp = cur.next
换方向 cur.next = pre
第三步:换完方向后,此时 1 <-- 2 3, 中间2跟3的连接断了,所以我们需要往后挪位置,让2和3联系起来,也就是

pre = cur
cur = tmp
最后:返回pre


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

class Solution:
    def reverseList(self, head: ListNode) -> ListNode:

        cur, pre = head, None
        while cur:
            cur.next, pre, cur = pre, cur, cur.next
        return pre

3、两数相加

给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。

如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。

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

示例:

输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807

class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None
class Solution:
    def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
        # 官方的解释:此为type hints,是Python 3.5新加的功能。类型建议符并非强制规定和检查,
        # 也就是说即使传入的实际参数与建议参数不符,也不会报错。
        # 函数参数中的“:”是参数的类型建议符(告诉程序员希望传入的实参的类型)。
        # 函数后面跟着的“->”是函数返回值的类型建议符(用来说明该函数返回的值是什么类型)。
        prenode = ListNode(0)
        lastnode = prenode
        val = 0
        while val or l1 or l2:
            val, cur = divmod(val + (l1.val if l1 else 0) + (l2.val if l2 else 0), 10)
            # val, l1.val, l2.val = 0, 1, 9; val, cur = 1, 0
            # val, l1.val, l2.val = 1, 5, 1; val, cur = 0, 7
            # val, l1.val, l2.val = 0, 8, 2; val, cur = 1, 0
            # val, l1.val, l2.val = 0, None, 9; val, cur = 1, 0
            # 函数把除数和余数运算结果结合起来,返回一个包含商和余数的元组(a // b, a % b)
            lastnode.next = ListNode(cur)
            lastnode = lastnode.next
            l1 = l1.next if l1 else None
            l2 = l2.next if l2 else None
        return prenode.next
 
 
def generateList(l: list) -> ListNode:
    preNode = ListNode(0)
    curNode = preNode
    for val in l:
        curNode.next = ListNode(val)
        curNode = curNode.next
    return preNode.next
 
def printList(l: ListNode):
    while(l):
        print("%d" % l.val, end=', ')
        l = l.next
 
if __name__ == "__main__":
    l1 = generateList([1, 5, 8])
    l2 = generateList([9, 1, 2, 9])
    printList(l1)
    printList(l2)
    s = Solution()
    sum = s.addTwoNumbers(l1, l2)
    printList(sum)

5、排序链表

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

示例 1:

输入: 4->2->1->3
输出: 1->2->3->4
示例 2:

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

class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None
def genList(l):
    preNode = ListNode(0)
    curNode = preNode
    for val in l:
        curNode.next = ListNode(val)
        curNode = curNode.next
    return preNode.next
def printList(l):
    while l:
        print("%d" % l.val, end=', ')
        l = l.next

class Solution:
    def sortList(self, head: ListNode) -> ListNode:
        if not (head and head.next): return head
        pre, slow, fast = None, head, head
        while fast and fast.next: pre, slow, fast = slow, slow.next, fast.next.next
        pre.next = None
        return self.mergeTwoLists(*map(self.sortList, (head, slow)))

    def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
        if l1 and l2:
            if l1.val > l2.val: l1, l2 = l2, l1
            l1.next = self.mergeTwoLists(l1.next, l2)
        return l1 or l2

ll = [4, 2, 1, 3]
ll_list = genList(ll)
printList(ll_list)
print('')
s = Solution()
result = s.sortList(ll_list)
printList(result)

6、环形链表 II

判断链表是否有环并找到入环节点:(快慢指针)
1、如果快指针走到空,无环
2、如果二者相遇,有环
3、相遇后,快指针返回链表开头,慢指针不动
4、两个指针同时走,每次均走一步,相遇点即为入环点

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

7、相交链表-题目:https://leetcode-cn.com/explore/interview/card/bytedance/244/linked-list-and-tree/1024/


我们通常做这种题的思路是设定两个指针分别指向两个链表头部,一起向前走直到其中一个到达末端,另一个与末端距离则是两链表的长度差。再通过长链表走到最后后将其指针指向短链表头,短链表走到最后后将其指针指向长链表的头,这时两个指针相遇的地方即为交点,最终两链表即可同时走到相交点。


class Solution(object):
    def getIntersectionNode(self, headA, headB):
        ha, hb = headA, headB
        while ha != hb:
            ha = ha.next if ha else headB
            hb = hb.next if hb else headA
        return ha

8、合并K个排序链表

合并 k 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。

示例:

输入:
[
  1->4->5,
  1->3->4,
  2->6
]
输出: 1->1->2->3->4->4->5->6

方法一:暴力法

遍历所有链表,将所有节点的值放到一个数组中。
将这个数组排序,然后遍历所有元素得到正确顺序的值。
用遍历得到的值,创建一个新的有序链表。
class Solution:
    def mergeKLists(self, lists: List[ListNode]) -> ListNode:
        nodes = []
        head = point = ListNode(0)
        for l in lists:
            while l:
                nodes.append(l.val)
                l = l.next
        for x in sorted(nodes):
            point.next = ListNode(x)
            point = point.next
        return head.next

9、二叉树的最近公共祖先:例如,给定如下二叉树:  root = [3,5,1,6,2,0,8,null,null,7,4]

示例 1:

输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。
示例 2:

输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。

class TreeNode:
    def __init__(self, val):
        self.val = val
        self.left, self.right = None, None

class Solution:
    def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
        # 思路:找就近的祖先节点,采用递归方法,考虑3种边界情况,
        #     边界情况:
        #       1. p,q在左右两边
        #       2. p,q都在左子树或右子树
        #       3. p或q有一个是根节点
        #       递归结束:如果not root, 找到节点为p或q,return root
        #       当前层递归:
        #       返回值:在左子树和右子树进行递归,并判断如果左子树返回值为空,则说明左子树不存在,则返回右子树,同理右子树; 如果左右子树同时存在值则返回 root为最近祖先节点
        if not root or root==p or root == q: return root
        left = self.lowestCommonAncestor(root.left, p, q)
        right = self.lowestCommonAncestor(root.right, p, q)
        if not left: return right
        if not right: return left
        return root # 如果left 和 right都不为空,则返回根节点

root = Tree = TreeNode(3)
b = Tree.left = TreeNode(5)
c = Tree.right = TreeNode(1)
d = b.left = TreeNode(6)
e = b.right = TreeNode(2)
f = c.left = TreeNode(0)
h = c.right = TreeNode(8)
i = e.left = TreeNode(7)
j = e.right = TreeNode(4)

s = Solution()
print(s.lowestCommonAncestor(root, f, h).val)

10、二叉树的锯齿形层次遍历

 

动态或贪心

1、买卖股票的最佳时机

2、买卖股票的最佳时机 II

3、最大正方形

5、最大子序和

6、三角形最小路径和

7、俄罗斯套娃信封问题

数据结构

1、最小栈

2、LRU缓存机制

3、全 O(1) 的数据结构

 

 

  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值