LeetCode刷题记录:1~4

1. 两数之和

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。

示例:

给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

思路:

遍历一遍数组,用一个字典存储遍历结果的值和索引,由于只有一个对应答案(不考虑多答案的情况),索引不需要使用列表的形式存储。
遍历过程如下:
索引i对应的值为x,先从字典中查找是否有target-x的key,有即返回该key对应的value和i,没有则将x:i存储于字典

代码:

class Solution(object):
    def twoSum(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        num_index = {}
        for i in range(len(nums)):
            x = nums[i]
            target_x = target - x

            if target_x in num_index.keys():
                return [num_index[target_x], i]
            num_index[x] = i

        return []

分析:

时间复杂度:只需要扫描一遍nums即可,所以时间复杂度为O(n)
空间复杂度:用到一个字典储存,O(n)

2.两数相加

给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字 0 之外,这两个数都不会以 0 开头。

示例:

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

思路:

使用哑结点技巧
需要使用carry储存进位,l1当前值(l1为None时,只为0), l2当前值(l2为None时,只为0)和carry进行计算,最终循环终止条件,l1 == None and l2 == None, 最后判断carry是否为1,为1需要进位

代码:

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

class Solution(object):
    def addTwoNumbers(self, l1, l2):
        """
        :type l1: ListNode
        :type l2: ListNode
        :rtype: ListNode
        """
        # 使用哑结点,更好处理边界情况
        dummy_node = ListNode(0)
        cur_node = dummy_node
        carry = 0
        # 当l1和l2均为None时,退出循环,l1,l1_value需要根据三元表达式得出
        while l1 != None or l2 != None:
            l1_value = 0 if l1 == None else l1.val
            l2_value = 0 if l2 == None else l2.val
            cur_value = l1_value + l2_value + carry
            carry = cur_value // 10         # 进位值
            cur_value = cur_value % 10      # 当前值

            cur_node.next = ListNode(cur_value)
            cur_node = cur_node.next

            l1 = None if l1 == None else l1.next
            l2 = None if l2 == None else l2.next

        # 最后判断carry是否为1
        if carry == 1:
            cur_node.next = ListNode(1)

        return dummy_node.next

分析:

时间复杂度:O(max(n,m)), n,m分别是l1和l2的长度
空间复杂度:O(max(n,m)),需要两个链表中最大长度链表的空间(可能存在最高位进位,此时空间需要加1)

3.无重复字符的最长子串

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

示例:

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

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

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

思路:

使用i,j两个指针,分别指向某一时刻最长子串的开头和结尾,j从字符串的第一个元素一直扫描到最后一个元素,使用一个字典储存扫描到每一个字符出现的索引位置并不断更新,i是否更新取决于j指向的字符在字典中的索引位置与i本身索引位置的关系,i较大,则不更新,i较小则i更新。
每次循环需要做的事情:
1.根据条件决定是否移动i
2.储存s[j]
3.计算以i,j子字符串的长度,决定是否更新max_length
4.j += 1

代码:

class Solution(object):
    def lengthOfLongestSubstring(self, s):
        """
        :type s: str
        :rtype: int
        """
        i = 0
        j = 0
        max_length = 0
        char_index = {}

        while j < len(s):
            # 只有在以下条件满足时会移动i,注意条件顺序不要更改
            if j != i and s[j] in char_index and char_index[s[j]] >= i:
                i = char_index[s[j]] + 1
            # 储存/更新s[j]
            char_index[s[j]] = j
            # 计算i,j子字符串的长度
            cur_length = j - i + 1
            if cur_length > max_length:
                max_length = cur_length
            # j += 1
            j += 1
        return max_length

分析:

时间复杂度:只扫描一遍字符串,时间复杂度O(n)
空间复杂度:使用一个字典储存扫描过的字符位置,需要O(n)复杂度,如果字符串的字符有限,例如"a-z",则只需要O(1)的空间

4.寻找两个有序数组的中位数

给定两个大小为 m 和 n 的有序数组 nums1 和 nums2。
请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。
你可以假设 nums1 和 nums2 不会同时为空。

示例:

示例 1:
nums1 = [1, 3]
nums2 = [2]
则中位数是 2.0

示例 2:
nums1 = [1, 2]
nums2 = [3, 4]
则中位数是 (2 + 3)/2 = 2.5

思路:

由于算法的时间复杂度为 O(log(m + n)),则不能使用线性扫描之类的方法,需要使用类似二分查找的方法
首先,我们以较短的数组作为二分查找的基准,每次二分查找后较短数组分为左右两部分,将较长的数组也分为左右两部分,使得两个数组的左部分长度之和等于右部分长度之和,或者左部分长度之和比右部分小于1。
如果左部分最大值小于等于右部分最小值,那么分隔正确,输出结果,否则继续二分查找。
重要技巧:
使用划分点i切分数组,注意i的取值是0到n,n+1种取值,注意i,索引,左侧数组长度之间的关系
需要考虑的特殊情况:
1.一个数组为空的时候
2.切分点在较短数组的左右端点

代码:

class Solution(object):
    def findMedianSortedArrays(self, nums1, nums2):
        """
        :type nums1: List[int]
        :type nums2: List[int]
        :rtype: float
        """
        # 特殊情况
        if len(nums1) == 0 and len(nums2) == 0:
            return None

        # 设置nums1为较短的有序数组
        if len(nums1) > len(nums2):
            nums1, nums2 = nums2, nums1
        n = len(nums1)
        m = len(nums2)
        odd = 1 if (n + m) % 2  == 1 else 0  # 判断奇偶性

        # 划分点i从0到n,一共n+1种情况,nums1[:i]一共有i个元素,nums1[i-1]是左边的最大元素
        imin = 0
        imax = n
        while imin <= imax : # 循环结束一定出结果
            # i和j之和是nums1和nums2左边数组的长度和,可能是总长度一半(总长度为偶数时),或者偏少的一半(总长度为奇数时)
            # 这样的划分,导致max_left稍微复杂一点
            i = (imin + imax) // 2
            j = (n + m) // 2 - i
            # 下面确定max_left和min_right,需要考虑边界情况
            if i != 0 and j != 0:
                max_left = max(nums2[j - 1], nums1[i - 1])
            elif i != 0:
                max_left = nums1[i - 1]
            elif j != 0:
                max_left = nums2[j - 1]
            else: # i和j可能同时为0
                max_left = None

            if i != n and j != m:
                min_right = min(nums1[i], nums2[j])
            elif i != n:
                min_right = nums1[i]
            elif j != m:
                min_right =  nums2[j]
            else: # 当两数组不均为空时,min_right不会是None
                min_right = None

            # 进行二分查找
            if max_left == None or max_left <= min_right : # max_left == None, 一定是两个数组一个是空,一个是1个元素,返回min_right即可
                return min_right if odd else (max_left + min_right) / 2
            elif  i != 0 and nums1[i - 1] > nums2[j]: # nums1[i - 1]较大,切分点左移
                imax = i - 1
            elif j != 0 and nums2[j - 1] > nums1[i]:  # nums1[i]较小,切分点右移
                imin = i + 1

分析:

时间复杂度:O(log(min(n,m))),只对较短数组进行二分查找
空间复杂度:O(1),只需要常数空间存储变量即可

猜你喜欢

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