LeetCode刷题记录:21~30

21.合并两个有序链表

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

示例:

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

思路:

题目要求:新链表是通过拼接给定的两个链表的所有节点组成的,所以不能使用一个新的链表。使用dumb帮助最后的返回,使用cur连接所有链表节点,不重不漏。

代码:

class ListNode(object):
    def __init__(self, x):
        self.val = x
        self.next = None

class Solution(object):
    def mergeTwoLists(self, l1, l2):
        """
        :type l1: ListNode
        :type l2: ListNode
        :rtype: ListNode
        """
        # 使用哑结点,一般还需要一个cur与之配合
        dumb = ListNode(0)
        cur = dumb

        while l1 != None and l2 != None:
            if l1.val < l2.val: 
                cur.next = l1
                cur = cur.next
                l1 = l1.next
            else:
                cur.next = l2
                cur = cur.next
                l2 = l2.next
        if l1:
            cur.next = l1
            return dumb.next
        if l2:
            cur.next = l2
            return dumb.next

分析:

时间复杂度:O(n), 线性扫描一遍l1和l2
空间复杂度:O(1), 常数空间储存dumb和cur即可

22.括号生成

给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合。

示例:

例如,给出 n = 3,生成结果为:
[
“((()))”,
“(()())”,
“(())()”,
“()(())”,
“()()()”
]

思路:

回溯法:使用回溯法穷举所有的可能组合,回溯的过程中,根据条件去掉了很多不可能的情况,回溯法不是简单的穷举法
回溯法一般使用递归实现,参数选择很重要,详情可见回溯算法超通俗易懂详尽分析和例题

代码:

class Solution:
    def generateParenthesis(self, n):
        result = []
        ans = ""
        l = 0
        r = 0
        self.parenthesis(n, l, r, ans, result)
        return result

    def parenthesis(self, n, l, r, ans, result):
        """
        :param n: 一共几对括号
        :param l: ans中左括号的数量
        :param r: ans中右括号的数量
        :param ans: 当前的括号组合
        :param result: 符合条件的括号组合
        :return:
        """
        if l == n and r == n:
            result.append(ans)
            return
        else:
            # 有两种情况可以添加括号,一种是l<n时,添加左括号,另一种是r<l时,添加右括号
            if l < n:
                self.parenthesis(n, l+1, r, ans+"(", result)
            if r < l:
                self.parenthesis(n, l, r+1, ans+")", result)

分析:

本题的复杂度分析叫复杂,下面是leetcode题解的分析:
在这里插入图片描述

23.合并K个排序链表

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

示例:

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

思路:

本题的难点在于时间复杂度分析,主要分为k是常数和k与n(n是链表的总长度)同阶两种情况:
方式一:将链表中所有元素全排序,O(nlogn),时间复杂度与k无关
方法二:线性扫描每一个链表头,找出最小的节点,时间复杂度O(kn), k与n同阶时,O(n2)
方式三:将每个链表头的结点构建成一个小顶堆O(k),每个取出堆顶元素,然后将该堆顶元素对应的链表的头元素插入堆O(logk),一共是n次,时间复杂度为O(k)+O(nlogk),当k与n同阶时,为O(n)+O(nlogn)
方式四:分治法,每次合并两个链表, 时间复杂度是O(nlogk), 当k与n同阶时,O(nlogn)
综上,方式三和方式四比较好

代码:

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

class Solution(object):
    def mergeKLists(self, lists):
        """
        :type lists: List[ListNode]
        :rtype: ListNode
        """
        # 使用分治法进行k个链表的合并
        if len(lists) == 0:
            return None
        elif len(lists) == 1:
            return lists[0]
        elif len(lists) == 2:
            return self.merge_two_lists(lists[0], lists[1])

        mid = len(lists) // 2
        l1 = lists[:mid]
        l2 = lists[mid:]

        return self.merge_two_lists(self.mergeKLists(l1), self.mergeKLists(l2))

    def merge_two_lists(self, l1, l2):
        dumb = ListNode(0)
        cur = dumb
        
        while l1 != None and l2 != None:
            if l1.val < l2.val:
                cur.next = l1
                cur = cur.next
                l1 = l1.next
            else:
                cur.next = l2
                cur = cur.next
                l2 = l2.next
        
        if l1:
            cur.next = l1
        if l2:
            cur.next = l2
        
        return dumb.next

分析:

时间复杂度:
空间复杂度:

24.两两交换链表中的节点

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

示例:

示例:
给定 1->2->3->4, 你应该返回 2->1->4->3.

思路:

首先想明白整个交换的过程,一共涉及三个指针l,cur,r,每次交换由两部分组成,一是改变指针的连接顺序,二是移动指针的位置

代码:

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

class Solution(object):
    def swapPairs(self, head):
        """
        :type head: ListNode
        :rtype: ListNode
        """
        if head == None:
            return None

        dumb = ListNode(0)
        cur = dumb
        # 初始化三个指针
        l = cur
        cur.next = head
        cur = cur.next
        r = cur.next

        while cur != None and cur.next != None :
            # 节点连接改变
            tmp = r.next
            cur.next = tmp
            r.next = cur
            l.next = r

            # 移动三个指针
            l = cur
            cur = cur.next
            if cur != None:
                r = cur.next

        return dumb.next

分析:

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

25.k个一组翻转链表

给出一个链表,每 k 个节点一组进行翻转,并返回翻转后的链表。
k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么将最后剩余节点保持原有顺序。

示例:

示例 :
给定这个链表:1->2->3->4->5
当 k = 2 时,应当返回: 2->1->4->3->5
当 k = 3 时,应当返回: 3->2->1->4->5

说明 :
你的算法只能使用常数的额外空间。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

思路:

同上题类似,使用l, cur, r三个指针进行位置变换,其中,cur和r每轮变换

代码:

class Solution(object):
    def reverseKGroup(self, head, k):
        """
        :type head: ListNode
        :type k: int
        :rtype: ListNode
        """
        if head == None:
            return None

        dumb = ListNode(0)
        l = dumb
        cur = head
        r = cur.next
        l.next = cur

        while True:
            # 先判断包括cur以及之后的k个节点是否存在, 不存在则返回
            flag_node = cur
            for _ in range(k):
                if flag_node == None:
                    return dumb.next
                flag_node =  flag_node.next

            # 进行k个一组的翻转过程, 一共k-1步
            new_l = cur
            for _ in range(k - 1):
                tmp = r.next
                r.next = cur
                cur = r
                r = tmp

            # 进行连接和指针位置变换
            l.next = cur
            l = new_l
            cur = r
            l.next = cur
            if cur:
                r = cur.next

分析:

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

26.删除排序数组中的重复项

给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。

示例:

示例 1:
给定数组 nums = [1,1,2],
函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。
你不需要考虑数组中超出新长度后面的元素。

示例 2:
给定 nums = [0,0,1,1,1,2,2,3,3,4],
函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。
你不需要考虑数组中超出新长度后面的元素。

说明:
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以“引用”方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:

// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝
int len = removeDuplicates(nums);
// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。
for (int i = 0; i < len; i++) {
print(nums[i]);
}

思路:

使用指针i,j,指针i遍历数组,如果j和i不一样且j和i的值不一样,那么j+=1, nums[j] = nums[i]。

代码:

class Solution(object):
    def removeDuplicates(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        if len(nums) <= 1:
            return len(nums)

        j = 0
        for i in range(len(nums)):
            if i == j:  # i和j位于同一位置时,i+=1
                continue
            if nums[j] != nums[i]: # nums[j] != nums[i]时,j += 1 然后nums[j] = nums[i]
                j += 1
                nums[j] = nums[i]
        return j + 1

分析:

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

27.移除元素

给定一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

示例:

示例 1:
给定 nums = [3,2,2,3], val = 3,
函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。
你不需要考虑数组中超出新长度后面的元素。

示例 2:
给定 nums = [0,1,2,2,3,0,4,2], val = 2,
函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。
注意这五个元素可为任意顺序。

你不需要考虑数组中超出新长度后面的元素。
说明:
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以“引用”方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
// nums 是以“引用”方式传递的。也就是说,不对实参作任何拷贝
int len = removeElement(nums, val);
// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。
for (int i = 0; i < len; i++) {
print(nums[i]);
}

思路:

同上题类似,使用两个指针i,j,先将指针j移动到第一个val元素处,然后i从j+1位置开始移动如果i处的值和val不一样,就交换ij的值,然后继续移动j

代码:

class Solution(object):
    def removeElement(self, nums, val):
        """
        :type nums: List[int]
        :type val: int
        :rtype: int
        """
        if nums == []:
            return 0

        for j in range(len(nums)):
            if nums[j] != val:
                continue
            for i in range(j+1, len(nums)):
                if nums[i] != val:
                    nums[j], nums[i] = nums[i], nums[j]
                    break
                if i == len(nums) - 1: # 当i到达nums最后一个元素,且nums[i]==val时,返回j
                    return j

        # 退出上面循环时,判断nums的最后一个元素与val的关系
        if nums[-1] != val:
            return len(nums)
        else:
            return len(nums) - 1

分析:

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

28.实现strStr()

实现 strStr() 函数。
给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回 -1。

示例:

示例 1:
输入: haystack = “hello”, needle = “ll”
输出: 2

示例 2:
输入: haystack = “aaaaa”, needle = “bba”
输出: -1

说明:
当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。
对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与C语言的 strstr() 以及 Java的 indexOf() 定义相符。

思路:

比较简单的方法是:遍历haystack的所有的字符(其实不是所有的字符,是range(len(haystack) - len(needle) + 1)),与needle相比较,第一个字符匹配,尝试匹配第二个字符…这样的时间复杂度比较高,如果想要减少时间复杂度,可是牺牲空间复杂度,储存一些外部表,用以在haystack跳转遍历,详情可以见数据结构,字符串匹配一章。

代码:

class Solution(object):
    def strStr(self, haystack, needle):
        """
        :type haystack: str
        :type needle: str
        :rtype: int
        """
        if needle == "":
            return 0

        for i in range(len(haystack) - len(needle) + 1):  #
            for j in range(len(needle)):
                if haystack[i+j] != needle[j]:  # haystack[i+j] != needle[j]结束内循环
                    break
                elif j == len(needle) - 1:      # haystack[i+j] == needle[j]并且j是needle的最后一位时,返回i
                    return i

        return -1

分析:

时间复杂度:O(nm)(n是haystack的长度,m是needle的长度), 如果使用储存表的形式,可以将时间复杂度优化至O(n),同时的空间复杂度为O(m)
空间复杂度:O(1)

29.两数相除

给定两个整数,被除数 dividend 和除数 divisor。将两数相除,要求不使用乘法、除法和 mod 运算符。
返回被除数 dividend 除以除数 divisor 得到的商。

示例:

示例 1:
输入: dividend = 10, divisor = 3
输出: 3

示例 2:
输入: dividend = 7, divisor = -3
输出: -2

说明:
被除数和除数均为 32 位有符号整数。
除数不为 0。
假设我们的环境只能存储 32 位有符号整数,其数值范围是 [−231, 231 − 1]。本题中,如果除法结果溢出,则返回 231 − 1。

思路:

关键点:使用位运算代替乘法,除法和乘方运算

代码:

class Solution(object):
    def divide(self, dividend, divisor):
        """
        :type dividend: int
        :type divisor: int
        :rtype: int
        """
        if dividend == 0:
            return 0

        # 先判断dividend和divisor是否同号,结果1先按两个正数来做,如果两者异号,结果为(-结果1 - 1)(LEETCODE的结果不一样,他认为结果为-结果1)
        negative = dividend ^ divisor < 0

        dividend = abs(dividend)
        divisor = abs(divisor)

        # 主循环,找到diviend大于等于divisor右移最大的位数,此时说明diviend至少包含1 << i个divisor, 则dividend -= divisor << i和result += 1 << i
        result = 0
        while dividend >= divisor:
            for i in range(31, -1, -1):
                if dividend >> i >= divisor:
                    result += 1 << i
                    dividend -= divisor << i
                    break

        # 判断result范围
        if (result >= 1 << 31 and not negative) or (result > 1 << 31 + 1 and negative):
            return (1 << 31) - 1

        return -result if negative else result

分析:

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

30.串联所有单词的子串

给定一个字符串 s 和一些长度相同的单词 words。找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置。
注意子串要与 words 中的单词完全匹配,中间不能有其他字符,但不需要考虑 words 中单词串联的顺序。

示例:

示例 1:
输入:
s = “barfoothefoobarman”,
words = [“foo”,“bar”]
输出:[0,9]
解释:
从索引 0 和 9 开始的子串分别是 “barfoor” 和 “foobar” 。
输出的顺序不重要, [9,0] 也是有效答案。

示例 2:
输入:
s = “wordgoodgoodgoodbestword”,
words = [“word”,“good”,“best”,“word”]
输出:[]

思路:

方式一:s的长度为n,word的长度为s,一共t个word,st = m, 则将s分为n-m+1个字符串,然后子字符串与words进行匹配,这是需要使用递归的方式进行匹配

代码:

class Solution(object):
    def findSubstring(self, s, words):
        """
        :type s: str
        :type words: List[str]
        :rtype: List[int]
        """
        result = []
        if s == "" or words == []:
            return result

        words_length =len(words) * len(words[0])
        for index in range(len(s)-words_length+1):
            flag = self.match_substring(index, s, words.copy()) # 注意传入的是words的复制,否则words会越来越小
            if flag:
                result.append(index)

        return result

    def match_substring(self, index, s, words):
        if words == []:
            return True

        word_length = len(words[0])
        s_word = s[index: index+word_length]
        if s_word in words:
            words.remove(s_word)
            return self.match_substring(index+word_length, s, words)  # 这里需要加return
        else:
            return False

分析:

时间复杂度:最坏的情况下一共有n-m+1次比较,每次比较最坏m次,则时间复复杂度为O((n-m)m)
空间复杂度:O(1),需要常数空间进行储存数据

猜你喜欢

转载自blog.csdn.net/weixin_40014471/article/details/89395984